From 425decdf7e9284d15aa726e3ae96b9942fb0e3ea Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Sun, 16 Feb 2020 03:40:06 +0000 Subject: create tremded branch --- src/asm/matha.s | 55 + src/asm/qasm-inline.h | 41 + src/asm/qasm.h | 39 + src/asm/snapvector.asm | 91 + src/asm/snapvector.c | 75 + src/asm/snd_mixa.s | 217 + src/asm/vm_x86_64.asm | 59 + src/cgame/CMakeLists.txt | 83 + src/cgame/binaryshader.h | 42 + src/cgame/cg_animation.c | 13 +- src/cgame/cg_animmapobj.c | 15 +- src/cgame/cg_attachment.c | 9 +- src/cgame/cg_buildable.c | 410 +- src/cgame/cg_consolecmds.c | 293 +- src/cgame/cg_draw.c | 2981 ++++--- src/cgame/cg_drawtools.c | 107 +- src/cgame/cg_ents.c | 226 +- src/cgame/cg_event.c | 474 +- src/cgame/cg_local.h | 463 +- src/cgame/cg_main.c | 837 +- src/cgame/cg_marks.c | 11 +- src/cgame/cg_mem.c | 202 - src/cgame/cg_particles.c | 737 +- src/cgame/cg_players.c | 416 +- src/cgame/cg_playerstate.c | 34 +- src/cgame/cg_predict.c | 51 +- src/cgame/cg_ptr.c | 81 - src/cgame/cg_public.h | 413 +- src/cgame/cg_rangemarker.c | 399 + src/cgame/cg_scanner.c | 165 +- src/cgame/cg_servercmds.c | 1235 ++- src/cgame/cg_snapshot.c | 20 +- src/cgame/cg_syscalls.asm | 4 +- src/cgame/cg_syscalls.c | 54 +- src/cgame/cg_syscalls_11.asm | 3 +- src/cgame/cg_trails.c | 61 +- src/cgame/cg_tutorial.c | 280 +- src/cgame/cg_view.c | 563 +- src/cgame/cg_weapons.c | 657 +- src/client/CMakeLists.txt | 209 + src/client/cl_avi.cpp | 664 ++ src/client/cl_cgame.cpp | 1172 +++ src/client/cl_cin.cpp | 1937 +++++ src/client/cl_console.cpp | 877 ++ src/client/cl_curl.cpp | 364 + src/client/cl_curl.h | 101 + src/client/cl_input.cpp | 1194 +++ src/client/cl_keys.cpp | 1665 ++++ src/client/cl_main.cpp | 5083 ++++++++++++ src/client/cl_net_chan.cpp | 190 + src/client/cl_parse.cpp | 961 +++ src/client/cl_rest.cpp | 117 + src/client/cl_rest.h | 18 + src/client/cl_scrn.cpp | 588 ++ src/client/cl_ui.cpp | 1269 +++ src/client/cl_updates.cpp | 476 ++ src/client/cl_updates.h | 8 + src/client/client.h | 716 ++ src/client/keycodes.h | 42 +- src/client/keys.h | 66 + src/client/libmumblelink.cpp | 190 + src/client/libmumblelink.h | 41 + src/client/qal.cpp | 337 + src/client/qal.h | 252 + src/client/snd_adpcm.cpp | 329 + src/client/snd_codec.cpp | 239 + src/client/snd_codec.h | 109 + src/client/snd_codec_ogg.cpp | 479 ++ src/client/snd_codec_opus.cpp | 452 + src/client/snd_codec_wav.cpp | 293 + src/client/snd_dma.cpp | 1644 ++++ src/client/snd_local.h | 267 + src/client/snd_main.cpp | 566 ++ src/client/snd_mem.cpp | 297 + src/client/snd_mix.cpp | 792 ++ src/client/snd_openal.cpp | 2737 ++++++ src/client/snd_public.h | 88 + src/client/snd_wavelet.cpp | 252 + src/game/CMakeLists.txt | 70 + src/game/bg_alloc.c | 242 + src/game/bg_lib.c | 1390 +++- src/game/bg_lib.h | 52 +- src/game/bg_local.h | 16 +- src/game/bg_misc.c | 6130 +++++--------- src/game/bg_pmove.c | 1164 ++- src/game/bg_public.h | 912 +- src/game/bg_shared.h | 0 src/game/bg_slidemove.c | 38 +- src/game/bg_voice.c | 653 ++ src/game/g_active.c | 1329 ++- src/game/g_admin.c | 9672 ++++++---------------- src/game/g_admin.h | 314 +- src/game/g_buildable.c | 4915 ++++++----- src/game/g_client.c | 1428 ++-- src/game/g_cmds.c | 6160 +++++--------- src/game/g_combat.c | 1479 ++-- src/game/g_local.h | 1011 +-- src/game/g_main.c | 1808 ++-- src/game/g_maprotation.c | 1546 ++-- src/game/g_mem.c | 216 - src/game/g_misc.c | 60 +- src/game/g_missile.c | 266 +- src/game/g_mover.c | 453 +- src/game/g_namelog.c | 128 + src/game/g_physics.c | 42 +- src/game/g_playermodel.c | 193 + src/game/g_ptr.c | 143 - src/game/g_public.h | 342 +- src/game/g_session.c | 68 +- src/game/g_spawn.c | 366 +- src/game/g_svcmds.c | 898 +- src/game/g_syscalls.asm | 3 +- src/game/g_syscalls.c | 47 +- src/game/g_target.c | 109 +- src/game/g_team.c | 457 +- src/game/g_trigger.c | 193 +- src/game/g_utils.c | 464 +- src/game/g_weapon.c | 1158 ++- src/game/g_weapondrop.c | 199 + src/game/tremulous.h | 424 +- src/granger/COPYING | 622 ++ src/granger/Dockerfile | 4 + src/granger/README.md | 139 + src/granger/appveyor.yml | 32 + src/granger/misc/docker-build.sh | 7 + src/granger/src/CMakeLists.txt | 60 + src/granger/src/getopt.h | 653 ++ src/granger/src/lnettlelib.c | 181 + src/granger/src/lnettlelib.h | 25 + src/granger/src/lua/CMakeLists.txt | 57 + src/granger/src/main.c | 97 + src/granger/src/nettle/CMakeLists.txt | 23 + src/granger/src/nettle/macros.h | 245 + src/granger/src/nettle/md5-compress.c | 174 + src/granger/src/nettle/md5.c | 93 + src/granger/src/nettle/md5.h | 86 + src/granger/src/nettle/nettle-stdint.h | 6 + src/granger/src/nettle/nettle-types.h | 110 + src/granger/src/nettle/nettle-write.h | 58 + src/granger/src/nettle/sha2.h | 206 + src/granger/src/nettle/sha256-compress.c | 199 + src/granger/src/nettle/sha256.c | 162 + src/granger/src/nettle/version.h | 58 + src/granger/src/nettle/write-be32.c | 77 + src/granger/src/nettle/write-le32.c | 69 + src/granger/src/premake/CMakeLists.txt | 32 + src/granger/src/premake/os_access.c | 58 + src/granger/src/premake/os_chdir.c | 32 + src/granger/src/premake/os_copyfile.c | 34 + src/granger/src/premake/os_elevate.c | 240 + src/granger/src/premake/os_getcwd.c | 36 + src/granger/src/premake/os_is64bit.c | 30 + src/granger/src/premake/os_isdir.c | 34 + src/granger/src/premake/os_isfile.c | 30 + src/granger/src/premake/os_match.c | 181 + src/granger/src/premake/os_mkdir.c | 33 + src/granger/src/premake/os_pathsearch.c | 84 + src/granger/src/premake/os_rmdir.c | 33 + src/granger/src/premake/os_stat.c | 46 + src/granger/src/premake/path_getabsolute.c | 102 + src/granger/src/premake/path_getrelative.c | 80 + src/granger/src/premake/path_isabsolute.c | 27 + src/granger/src/premake/path_join.c | 58 + src/granger/src/premake/path_normalize.c | 77 + src/granger/src/premake/path_translate.c | 61 + src/granger/src/premake/premake.c | 171 + src/granger/src/premake/premake.h | 90 + src/granger/src/premake/string_endswith.c | 28 + src/granger/src/strvec.c | 156 + src/granger/src/strvec.h | 36 + src/granger/test/main.lua | 11 + src/granger/test/test-nettle.lua | 33 + src/granger/test/test-os-access.lua | 17 + src/null/null_client.cpp | 95 + src/null/null_glimp.cpp | 63 + src/null/null_input.cpp | 37 + src/null/null_main.cpp | 83 + src/null/null_net.cpp | 55 + src/null/null_snddma.cpp | 62 + src/qcommon/CMakeLists.txt | 54 + src/qcommon/alternatePlayerstate.h | 75 + src/qcommon/cdefs.h | 79 + src/qcommon/cm_load.cpp | 1022 +++ src/qcommon/cm_local.h | 225 + src/qcommon/cm_patch.cpp | 1801 ++++ src/qcommon/cm_patch.h | 105 + src/qcommon/cm_polylib.cpp | 737 ++ src/qcommon/cm_polylib.h | 74 + src/qcommon/cm_public.h | 79 + src/qcommon/cm_test.cpp | 526 ++ src/qcommon/cm_trace.cpp | 1801 ++++ src/qcommon/cmd.cpp | 941 +++ src/qcommon/cmd.h | 115 + src/qcommon/common.cpp | 3662 ++++++++ src/qcommon/crypto.cpp | 92 + src/qcommon/crypto.h | 45 + src/qcommon/cvar.cpp | 1498 ++++ src/qcommon/cvar.h | 199 + src/qcommon/files.cpp | 3986 +++++++++ src/qcommon/files.h | 286 + src/qcommon/huffman.cpp | 558 ++ src/qcommon/huffman.h | 59 + src/qcommon/ioapi.cpp | 373 + src/qcommon/ioapi.h | 173 + src/qcommon/json.h | 353 + src/qcommon/md4.cpp | 202 + src/qcommon/md4.h | 6 + src/qcommon/md5.cpp | 312 + src/qcommon/msg.cpp | 2248 +++++ src/qcommon/msg.h | 79 + src/qcommon/net.h | 145 + src/qcommon/net_chan.cpp | 697 ++ src/qcommon/net_ip.cpp | 1842 +++++ src/qcommon/parse.cpp | 3725 +++++++++ src/qcommon/puff.cpp | 759 ++ src/qcommon/puff.h | 43 + src/qcommon/q3_lauxlib.cpp | 46 + src/qcommon/q3_lauxlib.h | 46 + src/qcommon/q_math.c | 610 +- src/qcommon/q_platform.h | 112 +- src/qcommon/q_shared.c | 634 +- src/qcommon/q_shared.h | 761 +- src/qcommon/qcommon.h | 413 + src/qcommon/qfiles.h | 634 +- src/qcommon/surfaceflags.h | 21 +- src/qcommon/unzip.cpp | 1951 +++++ src/qcommon/unzip.h | 321 + src/qcommon/vm.cpp | 1020 +++ src/qcommon/vm.h | 67 + src/qcommon/vm_interpreted.cpp | 904 ++ src/qcommon/vm_local.h | 204 + src/qcommon/vm_x86.cpp | 1840 +++++ src/renderer/tr_types.h | 234 - src/renderercommon/CMakeLists.txt | 77 + src/renderercommon/iqm.h | 131 + src/renderercommon/qgl.h | 570 ++ src/renderercommon/tr_common.h | 166 + src/renderercommon/tr_font.cpp | 562 ++ src/renderercommon/tr_image_bmp.cpp | 240 + src/renderercommon/tr_image_jpg.cpp | 479 ++ src/renderercommon/tr_image_pcx.cpp | 177 + src/renderercommon/tr_image_png.cpp | 2486 ++++++ src/renderercommon/tr_image_tga.cpp | 317 + src/renderercommon/tr_noise.cpp | 93 + src/renderercommon/tr_public.h | 199 + src/renderercommon/tr_types.h | 222 + src/renderergl1/CMakeLists.txt | 67 + src/renderergl1/tr_animation.cpp | 523 ++ src/renderergl1/tr_backend.cpp | 1163 +++ src/renderergl1/tr_bsp.cpp | 1869 +++++ src/renderergl1/tr_cmds.cpp | 601 ++ src/renderergl1/tr_curve.cpp | 627 ++ src/renderergl1/tr_flares.cpp | 540 ++ src/renderergl1/tr_image.cpp | 1680 ++++ src/renderergl1/tr_init.cpp | 1299 +++ src/renderergl1/tr_light.cpp | 402 + src/renderergl1/tr_local.h | 1603 ++++ src/renderergl1/tr_main.cpp | 1394 ++++ src/renderergl1/tr_marks.cpp | 459 + src/renderergl1/tr_mesh.cpp | 413 + src/renderergl1/tr_model.cpp | 1120 +++ src/renderergl1/tr_model_iqm.cpp | 1187 +++ src/renderergl1/tr_scene.cpp | 413 + src/renderergl1/tr_shade.cpp | 1522 ++++ src/renderergl1/tr_shade_calc.cpp | 1212 +++ src/renderergl1/tr_shader.cpp | 3158 +++++++ src/renderergl1/tr_shadows.cpp | 327 + src/renderergl1/tr_sky.cpp | 796 ++ src/renderergl1/tr_subs.cpp | 50 + src/renderergl1/tr_surface.cpp | 1239 +++ src/renderergl1/tr_world.cpp | 670 ++ src/renderergl2/CMakeLists.txt | 146 + src/renderergl2/glsl/bokeh_fp.glsl | 70 + src/renderergl2/glsl/bokeh_vp.glsl | 13 + src/renderergl2/glsl/calclevels4x_fp.glsl | 60 + src/renderergl2/glsl/calclevels4x_vp.glsl | 13 + src/renderergl2/glsl/depthblur_fp.glsl | 82 + src/renderergl2/glsl/depthblur_vp.glsl | 16 + src/renderergl2/glsl/dlight_fp.glsl | 32 + src/renderergl2/glsl/dlight_vp.glsl | 92 + src/renderergl2/glsl/down4x_fp.glsl | 34 + src/renderergl2/glsl/down4x_vp.glsl | 13 + src/renderergl2/glsl/fogpass_fp.glsl | 9 + src/renderergl2/glsl/fogpass_vp.glsl | 117 + src/renderergl2/glsl/generic_fp.glsl | 33 + src/renderergl2/glsl/generic_vp.glsl | 239 + src/renderergl2/glsl/lightall_fp.glsl | 429 + src/renderergl2/glsl/lightall_vp.glsl | 246 + src/renderergl2/glsl/pshadow_fp.glsl | 78 + src/renderergl2/glsl/pshadow_vp.glsl | 15 + src/renderergl2/glsl/shadowfill_fp.glsl | 41 + src/renderergl2/glsl/shadowfill_vp.glsl | 89 + src/renderergl2/glsl/shadowmask_fp.glsl | 143 + src/renderergl2/glsl/shadowmask_vp.glsl | 18 + src/renderergl2/glsl/ssao_fp.glsl | 86 + src/renderergl2/glsl/ssao_vp.glsl | 12 + src/renderergl2/glsl/texturecolor_fp.glsl | 10 + src/renderergl2/glsl/texturecolor_vp.glsl | 13 + src/renderergl2/glsl/tonemap_fp.glsl | 57 + src/renderergl2/glsl/tonemap_vp.glsl | 27 + src/renderergl2/tr_animation.cpp | 525 ++ src/renderergl2/tr_backend.cpp | 1817 ++++ src/renderergl2/tr_bsp.cpp | 3046 +++++++ src/renderergl2/tr_cmds.cpp | 672 ++ src/renderergl2/tr_curve.cpp | 741 ++ src/renderergl2/tr_dsa.cpp | 287 + src/renderergl2/tr_dsa.h | 80 + src/renderergl2/tr_extensions.cpp | 279 + src/renderergl2/tr_extramath.cpp | 248 + src/renderergl2/tr_extramath.h | 104 + src/renderergl2/tr_extratypes.h | 40 + src/renderergl2/tr_fbo.cpp | 659 ++ src/renderergl2/tr_fbo.h | 66 + src/renderergl2/tr_flares.cpp | 554 ++ src/renderergl2/tr_glsl.cpp | 1470 ++++ src/renderergl2/tr_image.cpp | 3235 ++++++++ src/renderergl2/tr_image_dds.cpp | 499 ++ src/renderergl2/tr_init.cpp | 1534 ++++ src/renderergl2/tr_light.cpp | 513 ++ src/renderergl2/tr_local.h | 2422 ++++++ src/renderergl2/tr_main.cpp | 2669 ++++++ src/renderergl2/tr_marks.cpp | 472 ++ src/renderergl2/tr_mesh.cpp | 418 + src/renderergl2/tr_model.cpp | 1419 ++++ src/renderergl2/tr_model_iqm.cpp | 1196 +++ src/renderergl2/tr_postprocess.cpp | 484 ++ src/renderergl2/tr_postprocess.h | 34 + src/renderergl2/tr_scene.cpp | 575 ++ src/renderergl2/tr_shade.cpp | 1634 ++++ src/renderergl2/tr_shade_calc.cpp | 843 ++ src/renderergl2/tr_shader.cpp | 3891 +++++++++ src/renderergl2/tr_shadows.cpp | 327 + src/renderergl2/tr_sky.cpp | 904 ++ src/renderergl2/tr_subs.cpp | 49 + src/renderergl2/tr_surface.cpp | 1320 +++ src/renderergl2/tr_vbo.cpp | 945 +++ src/renderergl2/tr_world.cpp | 811 ++ src/script/CMakeLists.txt | 35 + src/script/bind.h | 107 + src/script/client.h | 54 + src/script/cmd.h | 50 + src/script/cvar.h | 146 + src/script/http_client.h | 65 + src/script/lnettlelib.c | 94 + src/script/lnettlelib.h | 15 + src/script/nettle.h | 41 + src/script/rapidjson.h | 52 + src/script/rapidjson/LICENSE | 19 + src/script/rapidjson/document.cpp | 194 + src/script/rapidjson/file.hpp | 21 + src/script/rapidjson/luax.hpp | 82 + src/script/rapidjson/rapidjson.cpp | 414 + src/script/rapidjson/schema.cpp | 116 + src/script/rapidjson/userdata.hpp | 112 + src/script/rapidjson/values.cpp | 108 + src/script/rapidjson/values.hpp | 245 + src/sdl/CMakeLists.txt | 8 + src/sdl/sdl_gamma.cpp | 100 + src/sdl/sdl_glimp.cpp | 935 +++ src/sdl/sdl_icon.h | 138 + src/sdl/sdl_input.cpp | 1336 +++ src/sdl/sdl_snd.cpp | 298 + src/server/CMakeLists.txt | 114 + src/server/server.h | 529 ++ src/server/sv_admin.cpp | 0 src/server/sv_admin.h | 61 + src/server/sv_ccmds.cpp | 441 + src/server/sv_client.cpp | 1949 +++++ src/server/sv_game.cpp | 602 ++ src/server/sv_init.cpp | 1004 +++ src/server/sv_main.cpp | 1551 ++++ src/server/sv_net_chan.cpp | 259 + src/server/sv_snapshot.cpp | 749 ++ src/server/sv_world.cpp | 745 ++ src/sys/CMakeLists.txt | 15 + src/sys/con_log.cpp | 132 + src/sys/con_passive.cpp | 72 + src/sys/con_tty.cpp | 552 ++ src/sys/con_win32.cpp | 558 ++ src/sys/dialog.h | 40 + src/sys/sys_loadlib.h | 57 + src/sys/sys_local.h | 58 + src/sys/sys_main.cpp | 798 ++ src/sys/sys_osx.mm | 103 + src/sys/sys_shared.h | 111 + src/sys/sys_unix.cpp | 1006 +++ src/sys/sys_win32.cpp | 842 ++ src/sys/sys_win32_default_homepath.cpp | 52 + src/sys/win_resource.h | 46 + src/sys/win_resource.rc | 72 + src/tools/CMakeLists.txt | 145 + src/tools/asm/cmdlib.c | 9 +- src/tools/asm/cmdlib.h | 9 +- src/tools/asm/mathlib.h | 9 +- src/tools/asm/opstrings.h | 11 +- src/tools/asm/q3asm.c | 17 +- src/tools/asm/qfiles.h | 480 ++ src/tools/lcc/cpp/cpp.c | 621 +- src/tools/lcc/cpp/include.c | 268 +- src/tools/lcc/cpp/unix.c | 215 +- src/tools/lcc/etc/bytecode.c | 2 +- src/tools/lcc/etc/lcc.c | 1465 ++-- src/tools/lcc/lburg/gram.c | 72 +- src/tools/lcc/lburg/gram.y | 1 + src/tools/lcc/src/dag.c | 1494 ++-- src/ui/CMakeLists.txt | 67 + src/ui/menudef.h | 363 - src/ui/ui_atoms.c | 620 +- src/ui/ui_gameinfo.c | 482 +- src/ui/ui_local.h | 1455 +--- src/ui/ui_main.c | 9554 +++++++++------------ src/ui/ui_players.c | 1369 --- src/ui/ui_public.h | 303 +- src/ui/ui_shared.c | 11944 +++++++++++++++------------ src/ui/ui_shared.h | 681 +- src/ui/ui_syscalls.asm | 3 +- src/ui/ui_syscalls.c | 413 +- src/ui/ui_syscalls_11.asm | 2 + 418 files changed, 197690 insertions(+), 50792 deletions(-) create mode 100644 src/asm/matha.s create mode 100644 src/asm/qasm-inline.h create mode 100644 src/asm/qasm.h create mode 100644 src/asm/snapvector.asm create mode 100644 src/asm/snapvector.c create mode 100644 src/asm/snd_mixa.s create mode 100644 src/asm/vm_x86_64.asm create mode 100644 src/cgame/CMakeLists.txt create mode 100644 src/cgame/binaryshader.h delete mode 100644 src/cgame/cg_mem.c delete mode 100644 src/cgame/cg_ptr.c create mode 100644 src/cgame/cg_rangemarker.c create mode 100644 src/client/CMakeLists.txt create mode 100644 src/client/cl_avi.cpp create mode 100644 src/client/cl_cgame.cpp create mode 100644 src/client/cl_cin.cpp create mode 100644 src/client/cl_console.cpp create mode 100644 src/client/cl_curl.cpp create mode 100644 src/client/cl_curl.h create mode 100644 src/client/cl_input.cpp create mode 100644 src/client/cl_keys.cpp create mode 100644 src/client/cl_main.cpp create mode 100644 src/client/cl_net_chan.cpp create mode 100644 src/client/cl_parse.cpp create mode 100644 src/client/cl_rest.cpp create mode 100644 src/client/cl_rest.h create mode 100644 src/client/cl_scrn.cpp create mode 100644 src/client/cl_ui.cpp create mode 100644 src/client/cl_updates.cpp create mode 100644 src/client/cl_updates.h create mode 100644 src/client/client.h create mode 100644 src/client/keys.h create mode 100644 src/client/libmumblelink.cpp create mode 100644 src/client/libmumblelink.h create mode 100644 src/client/qal.cpp create mode 100644 src/client/qal.h create mode 100644 src/client/snd_adpcm.cpp create mode 100644 src/client/snd_codec.cpp create mode 100644 src/client/snd_codec.h create mode 100644 src/client/snd_codec_ogg.cpp create mode 100644 src/client/snd_codec_opus.cpp create mode 100644 src/client/snd_codec_wav.cpp create mode 100644 src/client/snd_dma.cpp create mode 100644 src/client/snd_local.h create mode 100644 src/client/snd_main.cpp create mode 100644 src/client/snd_mem.cpp create mode 100644 src/client/snd_mix.cpp create mode 100644 src/client/snd_openal.cpp create mode 100644 src/client/snd_public.h create mode 100644 src/client/snd_wavelet.cpp create mode 100644 src/game/CMakeLists.txt create mode 100644 src/game/bg_alloc.c create mode 100644 src/game/bg_shared.h create mode 100644 src/game/bg_voice.c delete mode 100644 src/game/g_mem.c create mode 100644 src/game/g_namelog.c create mode 100644 src/game/g_playermodel.c delete mode 100644 src/game/g_ptr.c create mode 100644 src/game/g_weapondrop.c create mode 100644 src/granger/COPYING create mode 100644 src/granger/Dockerfile create mode 100644 src/granger/README.md create mode 100644 src/granger/appveyor.yml create mode 100644 src/granger/misc/docker-build.sh create mode 100644 src/granger/src/CMakeLists.txt create mode 100644 src/granger/src/getopt.h create mode 100644 src/granger/src/lnettlelib.c create mode 100644 src/granger/src/lnettlelib.h create mode 100644 src/granger/src/lua/CMakeLists.txt create mode 100644 src/granger/src/main.c create mode 100644 src/granger/src/nettle/CMakeLists.txt create mode 100644 src/granger/src/nettle/macros.h create mode 100644 src/granger/src/nettle/md5-compress.c create mode 100644 src/granger/src/nettle/md5.c create mode 100644 src/granger/src/nettle/md5.h create mode 100644 src/granger/src/nettle/nettle-stdint.h create mode 100644 src/granger/src/nettle/nettle-types.h create mode 100644 src/granger/src/nettle/nettle-write.h create mode 100644 src/granger/src/nettle/sha2.h create mode 100644 src/granger/src/nettle/sha256-compress.c create mode 100644 src/granger/src/nettle/sha256.c create mode 100644 src/granger/src/nettle/version.h create mode 100644 src/granger/src/nettle/write-be32.c create mode 100644 src/granger/src/nettle/write-le32.c create mode 100644 src/granger/src/premake/CMakeLists.txt create mode 100644 src/granger/src/premake/os_access.c create mode 100644 src/granger/src/premake/os_chdir.c create mode 100644 src/granger/src/premake/os_copyfile.c create mode 100644 src/granger/src/premake/os_elevate.c create mode 100644 src/granger/src/premake/os_getcwd.c create mode 100644 src/granger/src/premake/os_is64bit.c create mode 100644 src/granger/src/premake/os_isdir.c create mode 100644 src/granger/src/premake/os_isfile.c create mode 100644 src/granger/src/premake/os_match.c create mode 100644 src/granger/src/premake/os_mkdir.c create mode 100644 src/granger/src/premake/os_pathsearch.c create mode 100644 src/granger/src/premake/os_rmdir.c create mode 100644 src/granger/src/premake/os_stat.c create mode 100644 src/granger/src/premake/path_getabsolute.c create mode 100644 src/granger/src/premake/path_getrelative.c create mode 100644 src/granger/src/premake/path_isabsolute.c create mode 100644 src/granger/src/premake/path_join.c create mode 100644 src/granger/src/premake/path_normalize.c create mode 100644 src/granger/src/premake/path_translate.c create mode 100644 src/granger/src/premake/premake.c create mode 100644 src/granger/src/premake/premake.h create mode 100644 src/granger/src/premake/string_endswith.c create mode 100644 src/granger/src/strvec.c create mode 100644 src/granger/src/strvec.h create mode 100644 src/granger/test/main.lua create mode 100644 src/granger/test/test-nettle.lua create mode 100644 src/granger/test/test-os-access.lua create mode 100644 src/null/null_client.cpp create mode 100644 src/null/null_glimp.cpp create mode 100644 src/null/null_input.cpp create mode 100644 src/null/null_main.cpp create mode 100644 src/null/null_net.cpp create mode 100644 src/null/null_snddma.cpp create mode 100644 src/qcommon/CMakeLists.txt create mode 100644 src/qcommon/alternatePlayerstate.h create mode 100644 src/qcommon/cdefs.h create mode 100644 src/qcommon/cm_load.cpp create mode 100644 src/qcommon/cm_local.h create mode 100644 src/qcommon/cm_patch.cpp create mode 100644 src/qcommon/cm_patch.h create mode 100644 src/qcommon/cm_polylib.cpp create mode 100644 src/qcommon/cm_polylib.h create mode 100644 src/qcommon/cm_public.h create mode 100644 src/qcommon/cm_test.cpp create mode 100644 src/qcommon/cm_trace.cpp create mode 100644 src/qcommon/cmd.cpp create mode 100644 src/qcommon/cmd.h create mode 100644 src/qcommon/common.cpp create mode 100644 src/qcommon/crypto.cpp create mode 100644 src/qcommon/crypto.h create mode 100644 src/qcommon/cvar.cpp create mode 100644 src/qcommon/cvar.h create mode 100644 src/qcommon/files.cpp create mode 100644 src/qcommon/files.h create mode 100644 src/qcommon/huffman.cpp create mode 100644 src/qcommon/huffman.h create mode 100644 src/qcommon/ioapi.cpp create mode 100644 src/qcommon/ioapi.h create mode 100644 src/qcommon/json.h create mode 100644 src/qcommon/md4.cpp create mode 100644 src/qcommon/md4.h create mode 100644 src/qcommon/md5.cpp create mode 100644 src/qcommon/msg.cpp create mode 100644 src/qcommon/msg.h create mode 100644 src/qcommon/net.h create mode 100644 src/qcommon/net_chan.cpp create mode 100644 src/qcommon/net_ip.cpp create mode 100644 src/qcommon/parse.cpp create mode 100644 src/qcommon/puff.cpp create mode 100644 src/qcommon/puff.h create mode 100644 src/qcommon/q3_lauxlib.cpp create mode 100644 src/qcommon/q3_lauxlib.h create mode 100644 src/qcommon/qcommon.h create mode 100644 src/qcommon/unzip.cpp create mode 100644 src/qcommon/unzip.h create mode 100644 src/qcommon/vm.cpp create mode 100644 src/qcommon/vm.h create mode 100644 src/qcommon/vm_interpreted.cpp create mode 100644 src/qcommon/vm_local.h create mode 100644 src/qcommon/vm_x86.cpp delete mode 100644 src/renderer/tr_types.h create mode 100644 src/renderercommon/CMakeLists.txt create mode 100644 src/renderercommon/iqm.h create mode 100644 src/renderercommon/qgl.h create mode 100644 src/renderercommon/tr_common.h create mode 100644 src/renderercommon/tr_font.cpp create mode 100644 src/renderercommon/tr_image_bmp.cpp create mode 100644 src/renderercommon/tr_image_jpg.cpp create mode 100644 src/renderercommon/tr_image_pcx.cpp create mode 100644 src/renderercommon/tr_image_png.cpp create mode 100644 src/renderercommon/tr_image_tga.cpp create mode 100644 src/renderercommon/tr_noise.cpp create mode 100644 src/renderercommon/tr_public.h create mode 100644 src/renderercommon/tr_types.h create mode 100644 src/renderergl1/CMakeLists.txt create mode 100644 src/renderergl1/tr_animation.cpp create mode 100644 src/renderergl1/tr_backend.cpp create mode 100644 src/renderergl1/tr_bsp.cpp create mode 100644 src/renderergl1/tr_cmds.cpp create mode 100644 src/renderergl1/tr_curve.cpp create mode 100644 src/renderergl1/tr_flares.cpp create mode 100644 src/renderergl1/tr_image.cpp create mode 100644 src/renderergl1/tr_init.cpp create mode 100644 src/renderergl1/tr_light.cpp create mode 100644 src/renderergl1/tr_local.h create mode 100644 src/renderergl1/tr_main.cpp create mode 100644 src/renderergl1/tr_marks.cpp create mode 100644 src/renderergl1/tr_mesh.cpp create mode 100644 src/renderergl1/tr_model.cpp create mode 100644 src/renderergl1/tr_model_iqm.cpp create mode 100644 src/renderergl1/tr_scene.cpp create mode 100644 src/renderergl1/tr_shade.cpp create mode 100644 src/renderergl1/tr_shade_calc.cpp create mode 100644 src/renderergl1/tr_shader.cpp create mode 100644 src/renderergl1/tr_shadows.cpp create mode 100644 src/renderergl1/tr_sky.cpp create mode 100644 src/renderergl1/tr_subs.cpp create mode 100644 src/renderergl1/tr_surface.cpp create mode 100644 src/renderergl1/tr_world.cpp create mode 100644 src/renderergl2/CMakeLists.txt create mode 100644 src/renderergl2/glsl/bokeh_fp.glsl create mode 100644 src/renderergl2/glsl/bokeh_vp.glsl create mode 100644 src/renderergl2/glsl/calclevels4x_fp.glsl create mode 100644 src/renderergl2/glsl/calclevels4x_vp.glsl create mode 100644 src/renderergl2/glsl/depthblur_fp.glsl create mode 100644 src/renderergl2/glsl/depthblur_vp.glsl create mode 100644 src/renderergl2/glsl/dlight_fp.glsl create mode 100644 src/renderergl2/glsl/dlight_vp.glsl create mode 100644 src/renderergl2/glsl/down4x_fp.glsl create mode 100644 src/renderergl2/glsl/down4x_vp.glsl create mode 100644 src/renderergl2/glsl/fogpass_fp.glsl create mode 100644 src/renderergl2/glsl/fogpass_vp.glsl create mode 100644 src/renderergl2/glsl/generic_fp.glsl create mode 100644 src/renderergl2/glsl/generic_vp.glsl create mode 100644 src/renderergl2/glsl/lightall_fp.glsl create mode 100644 src/renderergl2/glsl/lightall_vp.glsl create mode 100644 src/renderergl2/glsl/pshadow_fp.glsl create mode 100644 src/renderergl2/glsl/pshadow_vp.glsl create mode 100644 src/renderergl2/glsl/shadowfill_fp.glsl create mode 100644 src/renderergl2/glsl/shadowfill_vp.glsl create mode 100644 src/renderergl2/glsl/shadowmask_fp.glsl create mode 100644 src/renderergl2/glsl/shadowmask_vp.glsl create mode 100644 src/renderergl2/glsl/ssao_fp.glsl create mode 100644 src/renderergl2/glsl/ssao_vp.glsl create mode 100644 src/renderergl2/glsl/texturecolor_fp.glsl create mode 100644 src/renderergl2/glsl/texturecolor_vp.glsl create mode 100644 src/renderergl2/glsl/tonemap_fp.glsl create mode 100644 src/renderergl2/glsl/tonemap_vp.glsl create mode 100644 src/renderergl2/tr_animation.cpp create mode 100644 src/renderergl2/tr_backend.cpp create mode 100644 src/renderergl2/tr_bsp.cpp create mode 100644 src/renderergl2/tr_cmds.cpp create mode 100644 src/renderergl2/tr_curve.cpp create mode 100644 src/renderergl2/tr_dsa.cpp create mode 100644 src/renderergl2/tr_dsa.h create mode 100644 src/renderergl2/tr_extensions.cpp create mode 100644 src/renderergl2/tr_extramath.cpp create mode 100644 src/renderergl2/tr_extramath.h create mode 100644 src/renderergl2/tr_extratypes.h create mode 100644 src/renderergl2/tr_fbo.cpp create mode 100644 src/renderergl2/tr_fbo.h create mode 100644 src/renderergl2/tr_flares.cpp create mode 100644 src/renderergl2/tr_glsl.cpp create mode 100644 src/renderergl2/tr_image.cpp create mode 100644 src/renderergl2/tr_image_dds.cpp create mode 100644 src/renderergl2/tr_init.cpp create mode 100644 src/renderergl2/tr_light.cpp create mode 100644 src/renderergl2/tr_local.h create mode 100644 src/renderergl2/tr_main.cpp create mode 100644 src/renderergl2/tr_marks.cpp create mode 100644 src/renderergl2/tr_mesh.cpp create mode 100644 src/renderergl2/tr_model.cpp create mode 100644 src/renderergl2/tr_model_iqm.cpp create mode 100644 src/renderergl2/tr_postprocess.cpp create mode 100644 src/renderergl2/tr_postprocess.h create mode 100644 src/renderergl2/tr_scene.cpp create mode 100644 src/renderergl2/tr_shade.cpp create mode 100644 src/renderergl2/tr_shade_calc.cpp create mode 100644 src/renderergl2/tr_shader.cpp create mode 100644 src/renderergl2/tr_shadows.cpp create mode 100644 src/renderergl2/tr_sky.cpp create mode 100644 src/renderergl2/tr_subs.cpp create mode 100644 src/renderergl2/tr_surface.cpp create mode 100644 src/renderergl2/tr_vbo.cpp create mode 100644 src/renderergl2/tr_world.cpp create mode 100644 src/script/CMakeLists.txt create mode 100644 src/script/bind.h create mode 100644 src/script/client.h create mode 100644 src/script/cmd.h create mode 100644 src/script/cvar.h create mode 100644 src/script/http_client.h create mode 100644 src/script/lnettlelib.c create mode 100644 src/script/lnettlelib.h create mode 100644 src/script/nettle.h create mode 100644 src/script/rapidjson.h create mode 100644 src/script/rapidjson/LICENSE create mode 100644 src/script/rapidjson/document.cpp create mode 100644 src/script/rapidjson/file.hpp create mode 100644 src/script/rapidjson/luax.hpp create mode 100644 src/script/rapidjson/rapidjson.cpp create mode 100644 src/script/rapidjson/schema.cpp create mode 100644 src/script/rapidjson/userdata.hpp create mode 100644 src/script/rapidjson/values.cpp create mode 100644 src/script/rapidjson/values.hpp create mode 100644 src/sdl/CMakeLists.txt create mode 100644 src/sdl/sdl_gamma.cpp create mode 100644 src/sdl/sdl_glimp.cpp create mode 100644 src/sdl/sdl_icon.h create mode 100644 src/sdl/sdl_input.cpp create mode 100644 src/sdl/sdl_snd.cpp create mode 100644 src/server/CMakeLists.txt create mode 100644 src/server/server.h create mode 100644 src/server/sv_admin.cpp create mode 100644 src/server/sv_admin.h create mode 100644 src/server/sv_ccmds.cpp create mode 100644 src/server/sv_client.cpp create mode 100644 src/server/sv_game.cpp create mode 100644 src/server/sv_init.cpp create mode 100644 src/server/sv_main.cpp create mode 100644 src/server/sv_net_chan.cpp create mode 100644 src/server/sv_snapshot.cpp create mode 100644 src/server/sv_world.cpp create mode 100644 src/sys/CMakeLists.txt create mode 100644 src/sys/con_log.cpp create mode 100644 src/sys/con_passive.cpp create mode 100644 src/sys/con_tty.cpp create mode 100644 src/sys/con_win32.cpp create mode 100644 src/sys/dialog.h create mode 100644 src/sys/sys_loadlib.h create mode 100644 src/sys/sys_local.h create mode 100644 src/sys/sys_main.cpp create mode 100644 src/sys/sys_osx.mm create mode 100644 src/sys/sys_shared.h create mode 100644 src/sys/sys_unix.cpp create mode 100644 src/sys/sys_win32.cpp create mode 100644 src/sys/sys_win32_default_homepath.cpp create mode 100644 src/sys/win_resource.h create mode 100644 src/sys/win_resource.rc create mode 100644 src/tools/CMakeLists.txt create mode 100644 src/tools/asm/qfiles.h create mode 100644 src/ui/CMakeLists.txt delete mode 100644 src/ui/menudef.h delete mode 100644 src/ui/ui_players.c (limited to 'src') diff --git a/src/asm/matha.s b/src/asm/matha.s new file mode 100644 index 0000000..7bdff0a --- /dev/null +++ b/src/asm/matha.s @@ -0,0 +1,55 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// +// math.s +// x86 assembly-language math routines. + +#include "qasm.h" + + +#if id386 + + .text + +// TODO: rounding needed? +// stack parameter offset +#define val 4 + +.globl C(Invert24To16) +C(Invert24To16): + + movl val(%esp),%ecx + movl $0x100,%edx // 0x10000000000 as dividend + cmpl %edx,%ecx + jle LOutOfRange + + subl %eax,%eax + divl %ecx + + ret + +LOutOfRange: + movl $0xFFFFFFFF,%eax + ret + +#endif // id386 diff --git a/src/asm/qasm-inline.h b/src/asm/qasm-inline.h new file mode 100644 index 0000000..ed30ce6 --- /dev/null +++ b/src/asm/qasm-inline.h @@ -0,0 +1,41 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef __ASM_INLINE_I386__ +#define __ASM_INLINE_I386__ + +#include "qcommon/q_platform.h" + +#if idx64 + #define EAX "%%rax" + #define EBX "%%rbx" + #define ESP "%%rsp" + #define EDI "%%rdi" +#else + #define EAX "%%eax" + #define EBX "%%ebx" + #define ESP "%%esp" + #define EDI "%%edi" +#endif + +#endif diff --git a/src/asm/qasm.h b/src/asm/qasm.h new file mode 100644 index 0000000..8fbb6ee --- /dev/null +++ b/src/asm/qasm.h @@ -0,0 +1,39 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef __ASM_I386__ +#define __ASM_I386__ + +#include "qcommon/q_platform.h" + +#ifdef __ELF__ +.section .note.GNU-stack,"",@progbits +#endif + +#if defined(__ELF__) || defined(__WIN64__) +#define C(label) label +#else +#define C(label) _##label +#endif + +#endif diff --git a/src/asm/snapvector.asm b/src/asm/snapvector.asm new file mode 100644 index 0000000..aa5052a --- /dev/null +++ b/src/asm/snapvector.asm @@ -0,0 +1,91 @@ +; =========================================================================== +; Copyright (C) 2011 Thilo Schulz +; Copyright (C) 2015-2019 GrangerHub +; +; This file is part of Tremulous. +; +; Tremulous 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 3 of the License, +; or (at your option) any later version. +; +; Tremulous 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 Tremulous; if not, see +; +; =========================================================================== + +; MASM version of snapvector conversion function using SSE or FPU +; assume __cdecl calling convention is being used for x86, __fastcall for x64 +; +; function prototype: +; void qsnapvector(vec3_t vec) + +IFNDEF idx64 +.686p +.xmm +.model flat, c +ENDIF + +.data + + ALIGN 16 + ssemask DWORD 0FFFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh, 00000000h + ssecw DWORD 00001F80h + +.code + +IFDEF idx64 +; qsnapvector using SSE + + qsnapvectorsse PROC + movaps xmm1, ssemask ; initialize the mask register + movups xmm0, [rcx] ; here is stored our vector. Read 4 values in one go + movaps xmm2, xmm0 ; keep a copy of the original data + andps xmm0, xmm1 ; set the fourth value to zero in xmm0 + andnps xmm1, xmm2 ; copy fourth value to xmm1 and set rest to zero + cvtps2dq xmm0, xmm0 ; convert 4 single fp to int + cvtdq2ps xmm0, xmm0 ; convert 4 int to single fp + orps xmm0, xmm1 ; combine all 4 values again + movups [rcx], xmm0 ; write 3 rounded and 1 unchanged values back to memory + ret + qsnapvectorsse ENDP + +ELSE + + qsnapvectorsse PROC + mov eax, dword ptr 4[esp] ; store address of vector in eax + movaps xmm1, ssemask ; initialize the mask register for maskmovdqu + movups xmm0, [eax] ; here is stored our vector. Read 4 values in one go + movaps xmm2, xmm0 ; keep a copy of the original data + andps xmm0, xmm1 ; set the fourth value to zero in xmm0 + andnps xmm1, xmm2 ; copy fourth value to xmm1 and set rest to zero + cvtps2dq xmm0, xmm0 ; convert 4 single fp to int + cvtdq2ps xmm0, xmm0 ; convert 4 int to single fp + orps xmm0, xmm1 ; combine all 4 values again + movups [eax], xmm0 ; write 3 rounded and 1 unchanged values back to memory + ret + qsnapvectorsse ENDP + + qroundx87 macro src + fld dword ptr src + fistp dword ptr src + fild dword ptr src + fstp dword ptr src + endm + + qsnapvectorx87 PROC + mov eax, dword ptr 4[esp] + qroundx87 [eax] + qroundx87 4[eax] + qroundx87 8[eax] + ret + qsnapvectorx87 ENDP + +ENDIF + +end diff --git a/src/asm/snapvector.c b/src/asm/snapvector.c new file mode 100644 index 0000000..febbee0 --- /dev/null +++ b/src/asm/snapvector.c @@ -0,0 +1,75 @@ +/* +=========================================================================== +Copyright (C) 2011 Thilo Schulz +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "qasm-inline.h" +#include "qcommon/q_shared.h" + +/* + * GNU inline asm version of qsnapvector + * See MASM snapvector.asm for commentary + */ + +static unsigned char ssemask[16] __attribute__((aligned(16))) = +{ + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00" +}; + +void qsnapvectorsse(vec3_t vec) +{ + __asm__ volatile + ( + "movaps (%0), %%xmm1\n" + "movups (%1), %%xmm0\n" + "movaps %%xmm0, %%xmm2\n" + "andps %%xmm1, %%xmm0\n" + "andnps %%xmm2, %%xmm1\n" + "cvtps2dq %%xmm0, %%xmm0\n" + "cvtdq2ps %%xmm0, %%xmm0\n" + "orps %%xmm1, %%xmm0\n" + "movups %%xmm0, (%1)\n" + : + : "r" (ssemask), "r" (vec) + : "memory", "%xmm0", "%xmm1", "%xmm2" + ); + +} + +#define QROUNDX87(src) \ + "flds " src "\n" \ + "fistpl " src "\n" \ + "fildl " src "\n" \ + "fstps " src "\n" + +void qsnapvectorx87(vec3_t vec) +{ + __asm__ volatile + ( + QROUNDX87("(%0)") + QROUNDX87("4(%0)") + QROUNDX87("8(%0)") + : + : "r" (vec) + : "memory" + ); +} diff --git a/src/asm/snd_mixa.s b/src/asm/snd_mixa.s new file mode 100644 index 0000000..ebae0a4 --- /dev/null +++ b/src/asm/snd_mixa.s @@ -0,0 +1,217 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// +// snd_mixa.s +// x86 assembly-language sound code +// + +#include "qasm.h" + +#if id386 + + .text + +#if 0 +//---------------------------------------------------------------------- +// 8-bit sound-mixing code +//---------------------------------------------------------------------- + +#define ch 4+16 +#define sc 8+16 +#define count 12+16 + +.globl C(S_PaintChannelFrom8) +C(S_PaintChannelFrom8): + pushl %esi // preserve register variables + pushl %edi + pushl %ebx + pushl %ebp + +// int data; +// short *lscale, *rscale; +// unsigned char *sfx; +// int i; + + movl ch(%esp),%ebx + movl sc(%esp),%esi + +// if (ch->leftvol > 255) +// ch->leftvol = 255; +// if (ch->rightvol > 255) +// ch->rightvol = 255; + movl ch_leftvol(%ebx),%eax + movl ch_rightvol(%ebx),%edx + cmpl $255,%eax + jna LLeftSet + movl $255,%eax +LLeftSet: + cmpl $255,%edx + jna LRightSet + movl $255,%edx +LRightSet: + +// lscale = snd_scaletable[ch->leftvol >> 3]; +// rscale = snd_scaletable[ch->rightvol >> 3]; +// sfx = (signed char *)sc->data + ch->pos; +// ch->pos += count; + andl $0xF8,%eax + addl $20,%esi + movl (%esi),%esi + andl $0xF8,%edx + movl ch_pos(%ebx),%edi + movl count(%esp),%ecx + addl %edi,%esi + shll $7,%eax + addl %ecx,%edi + shll $7,%edx + movl %edi,ch_pos(%ebx) + addl $(C(snd_scaletable)),%eax + addl $(C(snd_scaletable)),%edx + subl %ebx,%ebx + movb -1(%esi,%ecx,1),%bl + + testl $1,%ecx + jz LMix8Loop + + movl (%eax,%ebx,4),%edi + movl (%edx,%ebx,4),%ebp + addl C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size),%edi + addl C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size),%ebp + movl %edi,C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size) + movl %ebp,C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size) + movb -2(%esi,%ecx,1),%bl + + decl %ecx + jz LDone + +// 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; + movl -8(%ebx,%ecx,4),%eax + sarl $8,%eax + cmpl $0x7FFF,%eax + jg LClampHigh + cmpl $0xFFFF8000,%eax + jnl LClampDone + movl $0xFFFF8000,%eax + jmp LClampDone +LClampHigh: + movl $0x7FFF,%eax +LClampDone: + +// val = (snd_p[i+1]*snd_vol)>>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; + movl -4(%ebx,%ecx,4),%edx + sarl $8,%edx + cmpl $0x7FFF,%edx + jg LClampHigh2 + cmpl $0xFFFF8000,%edx + jnl LClampDone2 + movl $0xFFFF8000,%edx + jmp LClampDone2 +LClampHigh2: + movl $0x7FFF,%edx +LClampDone2: + shll $16,%edx + andl $0xFFFF,%eax + orl %eax,%edx + movl %edx,-4(%edi,%ecx,2) + +// } + subl $2,%ecx + jnz LWLBLoopTop + +// snd_p += snd_linear_count; + + popl %ebx + popl %edi + + ret + +#endif // id386 diff --git a/src/asm/vm_x86_64.asm b/src/asm/vm_x86_64.asm new file mode 100644 index 0000000..692cee9 --- /dev/null +++ b/src/asm/vm_x86_64.asm @@ -0,0 +1,59 @@ +; =========================================================================== +; Copyright (C) 2011 Thilo Schulz +; Copyright (C) 2015-2019 GrangerHub +; +; This file is part of Tremulous. +; +; Tremulous 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 3 of the License, +; or (at your option) any later version. +; +; Tremulous 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 Tremulous; if not, see +; +; =========================================================================== + +; Call wrapper for vm_x86 when built with MSVC in 64 bit mode, +; since MSVC does not support inline x64 assembler code anymore. +; +; assumes __fastcall calling convention + +.code + +; Call to compiled code after setting up the register environment for the VM +; prototype: +; uint8_t qvmcall64(int *programStack, int *opStack, intptr_t *instructionPointers, byte *dataBase); + +qvmcall64 PROC + push rsi ; push non-volatile registers to stack + push rdi + push rbx + ; need to save pointer in rcx so we can write back the programData value to caller + push rcx + + ; registers r8 and r9 have correct value already thanx to __fastcall + xor rbx, rbx ; opStackOfs starts out being 0 + mov rdi, rdx ; opStack + mov esi, dword ptr [rcx] ; programStack + + call qword ptr [r8] ; instructionPointers[0] is also the entry point + + pop rcx + + mov dword ptr [rcx], esi ; write back the programStack value + mov al, bl ; return opStack offset + + pop rbx + pop rdi + pop rsi + + ret +qvmcall64 ENDP + +end diff --git a/src/cgame/CMakeLists.txt b/src/cgame/CMakeLists.txt new file mode 100644 index 0000000..026e6eb --- /dev/null +++ b/src/cgame/CMakeLists.txt @@ -0,0 +1,83 @@ +# +## ____ _ +## / ___|__ _ __ _ _ __ ___ ___ ___ ___ __| | ___ +##| | / _` |/ _` | '_ ` _ \ / _ \ / __/ _ \ / _` |/ _ \ +##| |__| (_| | (_| | | | | | | __/ | (_| (_) | (_| | __/ +## \____\__, |\__,_|_| |_| |_|\___| \___\___/ \__,_|\___| +## |___/ +# + +set(CMAKE_INSTALL_NAME_DIR ${PROJECT_BINARY_DIR}/gpp) + +set(BG_SOURCE_DIR ../game) +set(QC_SOURCE_DIR ../qcommon) +set(UI_SOURCE_DIR ../ui) +set(RC_SOURCE_DIR ../renderercommon) + +add_definitions( -DCGAME ) + +set( CGAME_SOURCES + cg_main.c # Must be listed first + cg_public.h + cg_local.h + binaryshader.h + ${BG_SOURCE_DIR}/bg_lib.h + ${BG_SOURCE_DIR}/bg_public.h + ${BG_SOURCE_DIR}/bg_alloc.c + ${BG_SOURCE_DIR}/bg_lib.c + ${BG_SOURCE_DIR}/bg_misc.c + ${BG_SOURCE_DIR}/bg_voice.c + ${BG_SOURCE_DIR}/bg_pmove.c + ${BG_SOURCE_DIR}/bg_slidemove.c + cg_animation.c + cg_animmapobj.c + cg_attachment.c + cg_buildable.c + cg_consolecmds.c + cg_draw.c + cg_drawtools.c + cg_ents.c + cg_event.c + cg_marks.c + cg_particles.c + cg_players.c + cg_playerstate.c + cg_predict.c + cg_rangemarker.c + cg_scanner.c + cg_servercmds.c + cg_snapshot.c + cg_trails.c + cg_tutorial.c + cg_view.c + cg_weapons.c + # + ${UI_SOURCE_DIR}/ui_shared.h + ${UI_SOURCE_DIR}/ui_shared.c + # + ${QC_SOURCE_DIR}/q_shared.h + ${QC_SOURCE_DIR}/q_shared.c + ${QC_SOURCE_DIR}/q_math.c + # + ${RC_SOURCE_DIR}/tr_types.h + ) + +add_library( cgame SHARED ${CGAME_SOURCES} cg_syscalls.c ) + +target_include_directories( + cgame PUBLIC + ${BG_SOURCE_DIR} + ${QC_SOURCE_DIR} + ${RC_SOURCE_DIR} + ${UI_SOURCE_DIR} + ) + +include( ${CMAKE_SOURCE_DIR}/cmake/AddQVM.cmake ) +add_qvm( cgame ${CGAME_SOURCES} cg_syscalls.asm ) + + +add_custom_command( + TARGET cgame POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_CURRENT_BINARY_DIR}/libcgame${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gpp/cgame${CMAKE_SHARED_LIBRARY_SUFFIX} + ) diff --git a/src/cgame/binaryshader.h b/src/cgame/binaryshader.h new file mode 100644 index 0000000..371f758 --- /dev/null +++ b/src/cgame/binaryshader.h @@ -0,0 +1,42 @@ +#ifndef BINARY_SHADER_H +#define BINARY_SHADER_H 1 + +typedef struct +{ + byte color[ 3 ]; + qboolean drawIntersection; + qboolean drawFrontline; +} cgBinaryShaderSetting_t; + +#define NUM_BINARY_SHADERS 256 + +typedef struct +{ + qhandle_t f1; + qhandle_t f2; + qhandle_t f3; + qhandle_t b1; + qhandle_t b2; + qhandle_t b3; +} cgMediaBinaryShader_t; + +typedef enum +{ + SHC_DARK_BLUE, + SHC_LIGHT_BLUE, + SHC_GREEN_CYAN, + SHC_VIOLET, + SHC_YELLOW, + SHC_ORANGE, + SHC_LIGHT_GREEN, + SHC_DARK_GREEN, + SHC_RED, + SHC_PINK, + SHC_GREY, + SHC_NUM_SHADER_COLORS +} shaderColorEnum_t; + +extern const vec3_t cg_shaderColors[ SHC_NUM_SHADER_COLORS ]; + + +#endif diff --git a/src/cgame/cg_animation.c b/src/cgame/cg_animation.c index c370c53..78311b9 100644 --- a/src/cgame/cg_animation.c +++ b/src/cgame/cg_animation.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -31,7 +32,7 @@ Sets cg.snap, cg.oldFrame, and cg.backlerp cg.time should be between oldFrameTime and frameTime after exit =============== */ -void CG_RunLerpFrame( lerpFrame_t *lf ) +void CG_RunLerpFrame( lerpFrame_t *lf, float scale ) { int f, numFrames; animation_t *anim; @@ -61,7 +62,9 @@ void CG_RunLerpFrame( lerpFrame_t *lf ) lf->frameTime = lf->oldFrameTime + anim->frameLerp; f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= scale; numFrames = anim->numFrames; + if( anim->flipflop ) numFrames *= 2; diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c index 1225314..9edbed4 100644 --- a/src/cgame/cg_animmapobj.c +++ b/src/cgame/cg_animmapobj.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "cg_local.h" - /* =============== CG_DoorAnimation @@ -31,7 +31,7 @@ CG_DoorAnimation */ static void CG_DoorAnimation( centity_t *cent, int *old, int *now, float *backLerp ) { - CG_RunLerpFrame( ¢->lerpFrame ); + CG_RunLerpFrame( ¢->lerpFrame, 1.0f ); *old = cent->lerpFrame.oldFrame; *now = cent->lerpFrame.frame; @@ -117,7 +117,7 @@ static void CG_AMOAnimation( centity_t *cent, int *old, int *now, float *backLer cent->lerpFrame.frameTime += delta; } - CG_RunLerpFrame( ¢->lerpFrame ); + CG_RunLerpFrame( ¢->lerpFrame, 1.0f ); cent->miscTime = cg.time; } @@ -147,7 +147,6 @@ void CG_AnimMapObj( centity_t *cent ) memset( &ent, 0, sizeof( ent ) ); - VectorCopy( es->angles, cent->lerpAngles ); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.hModel = cgs.gameModels[ es->modelindex ]; diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c index 0d3c8ea..2c52c06 100644 --- a/src/cgame/cg_attachment.c +++ b/src/cgame/cg_attachment.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,8 +16,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c index e678ebd..7d7596b 100644 --- a/src/cgame/cg_buildable.c +++ b/src/cgame/cg_buildable.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,12 +17,11 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ - #include "cg_local.h" char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] = @@ -95,6 +95,7 @@ void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ) #define CREEP_SIZE 64.0f +#define CREEP_DISTANCE 64.0f /* ================== @@ -107,7 +108,7 @@ static void CG_Creep( centity_t *cent ) float size, frac; trace_t tr; vec3_t temp, origin; - int scaleUpTime = BG_FindBuildTimeForBuildable( cent->currentState.modelindex ); + int scaleUpTime = BG_Buildable( cent->currentState.modelindex )->buildTime; int time; time = cent->currentState.time; @@ -121,7 +122,7 @@ static void CG_Creep( centity_t *cent ) else frac = 1.0f; } - else + else if( time < 0 ) { msec = cg.time + time; if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME ) @@ -131,7 +132,7 @@ static void CG_Creep( centity_t *cent ) } VectorCopy( cent->currentState.origin2, temp ); - VectorScale( temp, -4096, temp ); + VectorScale( temp, -CREEP_DISTANCE, temp ); VectorAdd( temp, cent->lerpOrigin, temp ); CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID ); @@ -140,7 +141,7 @@ static void CG_Creep( centity_t *cent ) size = CREEP_SIZE * frac; - if( size > 0.0f ) + if( size > 0.0f && tr.fraction < 1.0f ) CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); } @@ -168,12 +169,13 @@ static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_ // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -258,12 +260,13 @@ static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t bu // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( len <= 0 ) + if ( len < 0 ) return qfalse; - if ( len >= sizeof( text ) - 1 ) + if ( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -335,7 +338,7 @@ void CG_InitBuildables( void ) for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) { - buildableName = BG_FindNameForBuildable( i ); + buildableName = BG_Buildable( i )->name; //animation.cfg Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName ); @@ -350,7 +353,8 @@ void CG_InitBuildables( void ) //models for( j = 0; j <= 3; j++ ) { - if( ( modelFile = BG_FindModelsForBuildable( i, j ) ) ) + modelFile = BG_BuildableConfig( i )->models[ j ]; + if( strlen( modelFile ) > 0 ) cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile ); } @@ -372,7 +376,7 @@ void CG_InitBuildables( void ) else { //file doesn't exist - use default - if( BG_FindTeamForBuildable( i ) == BIT_ALIENS ) + if( BG_Buildable( i )->team == TEAM_ALIENS ) cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ]; else cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ]; @@ -426,24 +430,15 @@ cg.time should be between oldFrameTime and frameTime after exit */ static void CG_RunBuildableLerpFrame( centity_t *cent ) { - int f, numFrames; buildable_t buildable = cent->currentState.modelindex; lerpFrame_t *lf = ¢->lerpFrame; - animation_t *anim; buildableAnimNumber_t newAnimation = cent->buildableAnim & ~( ANIM_TOGGLEBIT|ANIM_FORCEBIT ); - // debugging tool to get no animations - if( cg_animSpeed.integer == 0 ) - { - lf->oldFrame = lf->frame = lf->backlerp = 0; - return; - } - // see if the animation sequence is switching if( newAnimation != lf->animationNumber || !lf->animation ) { if( cg_debugRandom.integer ) - CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: %d\n", + CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: 0x%p\n", newAnimation, lf->animationNumber, lf->animation ); CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation ); @@ -453,7 +448,7 @@ static void CG_RunBuildableLerpFrame( centity_t *cent ) { if( cg_debugRandom.integer ) CG_Printf( "Sound for animation %d for a %s\n", - newAnimation, BG_FindHumanNameForBuildable( buildable ) ); + newAnimation, BG_Buildable( buildable )->humanName ); trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cg_buildables[ buildable ].sounds[ newAnimation ].sound ); @@ -465,72 +460,14 @@ static void CG_RunBuildableLerpFrame( centity_t *cent ) trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_buildables[ buildable ].sounds[ lf->animationNumber ].sound ); - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if( cg.time >= lf->frameTime ) - { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; + CG_RunLerpFrame( lf, 1.0f ); - // get the next frame based on the animation - anim = lf->animation; - if( !anim->frameLerp ) - return; // shouldn't happen - - if ( cg.time < lf->animationTime ) - lf->frameTime = lf->animationTime; // initial lerp - else - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - numFrames = anim->numFrames; - if(anim->flipflop) - numFrames *= 2; - - if( f >= numFrames ) - { - f -= numFrames; - if( anim->loopFrames ) - { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } - else - { - f = numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = cg.time; - cent->buildableAnim = cent->currentState.torsoAnim; - } - } - - if( anim->reversed ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - f; - else if( anim->flipflop && f >= anim->numFrames ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames ); - else - lf->frame = anim->firstFrame + f; - - if( cg.time > lf->frameTime ) - { - lf->frameTime = cg.time; - if( cg_debugAnim.integer ) - CG_Printf( "Clamp lf->frameTime\n"); - } + // animation ended + if( lf->frameTime == cg.time ) + { + cent->buildableAnim = cent->currentState.torsoAnim; + cent->buildableIdleAnim = qtrue; } - - if( lf->frameTime > cg.time + 200 ) - lf->frameTime = cg.time; - - if( lf->oldFrameTime > cg.time ) - lf->oldFrameTime = cg.time; - - // calculate current lerp value - if( lf->frameTime == lf->oldFrameTime ) - lf->backlerp = 0; - else - lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); } /* @@ -544,10 +481,13 @@ static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *b //if no animation is set default to idle anim if( cent->buildableAnim == BANIM_NONE ) + { cent->buildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } //display the first frame of the construction anim if not yet spawned - if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( !( es->eFlags & EF_B_SPAWNED ) ) { animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ]; @@ -572,12 +512,23 @@ static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *b CG_Printf( "%d->%d l:%d t:%d %s(%d)\n", cent->oldBuildableAnim, cent->buildableAnim, es->legsAnim, es->torsoAnim, - BG_FindHumanNameForBuildable( es->modelindex ), es->number ); + BG_Buildable( es->modelindex )->humanName, es->number ); if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT ) + { cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim; + cent->buildableIdleAnim = qfalse; + } else + { cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } + } + else if( cent->buildableIdleAnim == qtrue && + cent->buildableAnim != es->torsoAnim ) + { + cent->buildableAnim = es->torsoAnim; } CG_RunBuildableLerpFrame( cent ); @@ -588,7 +539,7 @@ static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *b } } -#define TRACE_DEPTH 64.0f +#define TRACE_DEPTH 15.0f /* =============== @@ -600,8 +551,9 @@ static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t const vec3_t mins, const vec3_t maxs, vec3_t outAxis[ 3 ], vec3_t outOrigin ) { - vec3_t forward, start, end; + vec3_t forward, end; trace_t tr; + float fraction; AngleVectors( angles, forward, NULL, NULL ); VectorCopy( normal, outAxis[ 2 ] ); @@ -620,16 +572,21 @@ static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t outAxis[ 1 ][ 2 ] = -outAxis[ 1 ][ 2 ]; VectorMA( inOrigin, -TRACE_DEPTH, normal, end ); - VectorMA( inOrigin, 1.0f, normal, start ); - CG_CapTrace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID ); - if( tr.fraction == 1.0f ) + CG_CapTrace( &tr, inOrigin, mins, maxs, end, skipNumber, + CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + + fraction = tr.fraction; + if( tr.startsolid ) + fraction = 0; + else if( tr.fraction == 1.0f ) { - //erm we missed completely - try again with a box trace - CG_Trace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID ); + // this is either too far off of the bbox to be useful for gameplay purposes + // or the model is positioned in thin air anyways. + fraction = 0; } - VectorMA( inOrigin, tr.fraction * -TRACE_DEPTH, normal, outOrigin ); + VectorMA( inOrigin, fraction * -TRACE_DEPTH, normal, outOrigin ); } /* @@ -650,15 +607,18 @@ void CG_GhostBuildable( buildable_t buildable ) memset( &ent, 0, sizeof( ent ) ); - BG_FindBBoxForBuildable( buildable, mins, maxs ); + BG_BuildableBoundingBox( buildable, mins, maxs ); BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, CG_Trace, entity_origin, angles, &tr ); + if( cg_rangeMarkerForBlueprint.integer && tr.entityNum != ENTITYNUM_NONE ) + CG_GhostBuildableRangeMarker( buildable, entity_origin, tr.plane.normal ); + CG_PositionAndOrientateBuildable( ps->viewangles, entity_origin, tr.plane.normal, ps->clientNum, mins, maxs, ent.axis, ent.origin ); //offset on the Z axis if required - VectorMA( ent.origin, BG_FindZOffsetForBuildable( buildable ), tr.plane.normal, ent.origin ); + VectorMA( ent.origin, BG_BuildableConfig( buildable )->zOffset, tr.plane.normal, ent.origin ); VectorCopy( ent.origin, ent.lightingOrigin ); VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all @@ -671,7 +631,7 @@ void CG_GhostBuildable( buildable_t buildable ) ent.customShader = cgs.media.redBuildShader; //rescale the model - scale = BG_FindModelScaleForBuildable( buildable ); + scale = BG_BuildableConfig( buildable )->modelScale; if( scale != 1.0f ) { @@ -696,14 +656,14 @@ CG_BuildableParticleEffects static void CG_BuildableParticleEffects( centity_t *cent ) { entityState_t *es = ¢->currentState; - buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex ); - int health = es->generic1 & B_HEALTH_MASK; - float healthFrac = (float)health / B_HEALTH_MASK; + team_t team = BG_Buildable( es->modelindex )->team; + int health = es->misc; + float healthFrac = (float)health / BG_Buildable( es->modelindex )->health; - if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( !( es->eFlags & EF_B_SPAWNED ) ) return; - if( team == BIT_HUMANS ) + if( team == TEAM_HUMANS ) { if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) { @@ -718,7 +678,7 @@ static void CG_BuildableParticleEffects( centity_t *cent ) else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) CG_DestroyParticleSystem( ¢->buildablePS ); } - else if( team == BIT_ALIENS ) + else if( team == TEAM_ALIENS ) { if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) { @@ -736,7 +696,11 @@ static void CG_BuildableParticleEffects( centity_t *cent ) } } - +/* +================== +CG_BuildableStatusParse +================== +*/ void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) { pc_token_t token; @@ -866,10 +830,12 @@ void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n", token.string, filename ); bs->loaded = qfalse; + trap_Parse_FreeSource( handle ); return; } } bs->loaded = qtrue; + trap_Parse_FreeSource( handle ); } #define STATUS_FADE_TIME 200 @@ -899,23 +865,30 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) qboolean visible = qfalse; vec3_t mins, maxs; entityState_t *hit; + int anim; - if( BG_FindTeamForBuildable( es->modelindex ) == BIT_ALIENS ) + if( BG_Buildable( es->modelindex )->team == TEAM_ALIENS ) bs = &cgs.alienBuildStat; else bs = &cgs.humanBuildStat; if( !bs->loaded ) return; - + d = Distance( cent->lerpOrigin, cg.refdef.vieworg ); if( d > STATUS_MAX_VIEW_DIST ) return; - + Vector4Copy( bs->foreColor, color ); - // trace for center point - BG_FindBBoxForBuildable( es->modelindex, mins, maxs ); + // trace for center point + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); + + // hack for shrunken barricades + anim = es->torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if( es->modelindex == BA_A_BARRICADE && + ( anim == BANIM_DESTROYED || !( es->eFlags & EF_B_SPAWNED ) ) ) + maxs[ 2 ] = (int)( maxs[ 2 ] * BARRICADE_SHRINKPROP ); VectorCopy( cent->lerpOrigin, origin ); @@ -960,8 +933,8 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) hit = &cg_entities[ tr.entityNum ].currentState; if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE && - ( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) || - BG_FindTransparentTestForBuildable( hit->modelindex ) ) ) ) + ( !( es->eFlags & EF_B_SPAWNED ) || + BG_Buildable( hit->modelindex )->transparentTest ) ) ) { entNum = tr.entityNum; VectorCopy( tr.endpos, trOrigin ); @@ -972,7 +945,7 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) } // hack to make the kit obscure view if( cg_drawGun.integer && visible && - cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS && + cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && CG_WorldToScreen( origin, &x, &y ) ) { if( x > 450 && y > 290 ) @@ -1006,8 +979,8 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) return; } - health = es->generic1 & B_HEALTH_MASK; - healthScale = (float)health / B_HEALTH_MASK; + health = es->misc; + healthScale = (float)health / BG_Buildable( es->modelindex )->health; if( health > 0 && healthScale < 0.01f ) healthScale = 0.01f; @@ -1024,13 +997,14 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) float picY = y; float scale; float subH, subY; + float clipX, clipY, clipW, clipH; vec4_t frameColor; // this is fudged to get the width/height in the cfg to be more realistic scale = ( picH / d ) * 3; - powered = es->generic1 & B_POWERED_TOGGLEBIT; - marked = es->generic1 & B_MARKED_TOGGLEBIT; + powered = es->eFlags & EF_B_POWERED; + marked = es->eFlags & EF_B_MARKED; picH *= scale; picW *= scale; @@ -1041,6 +1015,12 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) subH = picH - ( picH * bs->verticalMargin ); subY = picY + ( picH * 0.5f ) - ( subH * 0.5f ); + clipW = ( 640.0f * cg_viewsize.integer ) / 100.0f; + clipH = ( 480.0f * cg_viewsize.integer ) / 100.0f; + clipX = 320.0f - ( clipW * 0.5f ); + clipY = 240.0f - ( clipH * 0.5f ); + CG_SetClipRegion( clipX, clipY, clipW, clipH ); + if( bs->frameShader ) { Vector4Copy( bs->backColor, frameColor ); @@ -1073,7 +1053,7 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) healthColor[ 3 ] = color[ 3 ]; trap_R_SetColor( healthColor ); - + CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader ); trap_R_SetColor( NULL ); } @@ -1089,7 +1069,7 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) oW *= scale; oX -= ( oW * 0.5f ); oY -= ( oH * 0.5f ); - + trap_R_SetColor( frameColor ); CG_DrawPic( oX, oY, oW, oH, bs->overlayShader ); trap_R_SetColor( NULL ); @@ -1117,12 +1097,12 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) int healthMax; int healthPoints; - healthMax = BG_FindHealthForBuildable( es->modelindex ); + healthMax = BG_Buildable( es->modelindex )->health; healthPoints = (int)( healthScale * healthMax ); if( health > 0 && healthPoints < 1 ) healthPoints = 1; - nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f ); - + nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f ); + if( healthPoints > 999 ) nX -= 0.0f; else if( healthPoints > 99 ) @@ -1131,14 +1111,21 @@ static void CG_BuildableStatusDisplay( centity_t *cent ) nX -= subH * 1.0f; else nX -= subH * 1.5f; - + CG_DrawField( nX, subY, 4, subH, subH, healthPoints ); } + trap_R_SetColor( NULL ); + CG_ClearClipRegion( ); } } -static int QDECL CG_SortDistance( const void *a, const void *b ) +/* +================== +CG_SortDistance +================== +*/ +static int CG_SortDistance( const void *a, const void *b ) { centity_t *aent, *bent; float adist, bdist; @@ -1155,6 +1142,48 @@ static int QDECL CG_SortDistance( const void *a, const void *b ) return 0; } +/* +================== +CG_PlayerIsBuilder +================== +*/ +static qboolean CG_PlayerIsBuilder( buildable_t buildable ) +{ + switch( cg.predictedPlayerState.weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return BG_Buildable( buildable )->team == + BG_Weapon( cg.predictedPlayerState.weapon )->team; + + default: + return qfalse; + } +} + +/* +================== +CG_BuildableRemovalPending +================== +*/ +static qboolean CG_BuildableRemovalPending( int entityNum ) +{ + int i; + playerState_t *ps = &cg.snap->ps; + + if( !( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) ) + return qfalse; + + for( i = 0; i < MAX_MISC; i++ ) + { + if( ps->misc[ i ] == entityNum ) + return qtrue; + } + + return qfalse; +} + /* ================== CG_DrawBuildableStatus @@ -1168,30 +1197,18 @@ void CG_DrawBuildableStatus( void ) int buildableList[ MAX_ENTITIES_IN_SNAPSHOT ]; int buildables = 0; - switch( cg.predictedPlayerState.weapon ) + for( i = 0; i < cg.snap->numEntities; i++ ) { - case WP_ABUILD: - case WP_ABUILD2: - case WP_HBUILD: - case WP_HBUILD2: - for( i = 0; i < cg.snap->numEntities; i++ ) - { - cent = &cg_entities[ cg.snap->entities[ i ].number ]; - es = ¢->currentState; - - if( es->eType == ET_BUILDABLE && - BG_FindTeamForBuildable( es->modelindex ) == - BG_FindTeamForWeapon( cg.predictedPlayerState.weapon ) ) - buildableList[ buildables++ ] = cg.snap->entities[ i ].number; - } - qsort( buildableList, buildables, sizeof( int ), CG_SortDistance ); - for( i = 0; i < buildables; i++ ) - CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ] ); - break; + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + es = ¢->currentState; - default: - break; + if( es->eType == ET_BUILDABLE && CG_PlayerIsBuilder( es->modelindex ) ) + buildableList[ buildables++ ] = cg.snap->entities[ i ].number; } + + qsort( buildableList, buildables, sizeof( int ), CG_SortDistance ); + for( i = 0; i < buildables; i++ ) + CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ] ); } #define BUILDABLE_SOUND_PERIOD 500 @@ -1205,17 +1222,15 @@ void CG_Buildable( centity_t *cent ) { refEntity_t ent; entityState_t *es = ¢->currentState; - vec3_t angles; vec3_t surfNormal, xNormal, mins, maxs; vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; float rotAngle; - buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex ); + team_t team = BG_Buildable( es->modelindex )->team; float scale; int health; - float healthScale; //must be before EF_NODRAW check - if( team == BIT_ALIENS ) + if( team == TEAM_ALIENS ) CG_Creep( cent ); // if set to invisible, skip @@ -1229,37 +1244,63 @@ void CG_Buildable( centity_t *cent ) memset ( &ent, 0, sizeof( ent ) ); - VectorCopy( cent->lerpOrigin, ent.origin ); - VectorCopy( cent->lerpOrigin, ent.oldorigin ); - VectorCopy( cent->lerpOrigin, ent.lightingOrigin ); - VectorCopy( es->origin2, surfNormal ); - VectorCopy( es->angles, angles ); - BG_FindBBoxForBuildable( es->modelindex, mins, maxs ); + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); if( es->pos.trType == TR_STATIONARY ) - CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, es->number, - mins, maxs, ent.axis, ent.origin ); + { + // seeing as buildables rarely move, we cache the results and recalculate + // only if the buildable moves or changes orientation + if( VectorCompare( cent->buildableCache.cachedOrigin, cent->lerpOrigin ) && + VectorCompare( cent->buildableCache.cachedAngles, cent->lerpAngles ) && + VectorCompare( cent->buildableCache.cachedNormal, surfNormal ) && + cent->buildableCache.cachedType == es->modelindex ) + { + VectorCopy( cent->buildableCache.axis[ 0 ], ent.axis[ 0 ] ); + VectorCopy( cent->buildableCache.axis[ 1 ], ent.axis[ 1 ] ); + VectorCopy( cent->buildableCache.axis[ 2 ], ent.axis[ 2 ] ); + VectorCopy( cent->buildableCache.origin, ent.origin ); + } + else + { + CG_PositionAndOrientateBuildable( cent->lerpAngles, cent->lerpOrigin, surfNormal, + es->number, mins, maxs, ent.axis, + ent.origin ); + VectorCopy( ent.axis[ 0 ], cent->buildableCache.axis[ 0 ] ); + VectorCopy( ent.axis[ 1 ], cent->buildableCache.axis[ 1 ] ); + VectorCopy( ent.axis[ 2 ], cent->buildableCache.axis[ 2 ] ); + VectorCopy( ent.origin, cent->buildableCache.origin ); + VectorCopy( cent->lerpOrigin, cent->buildableCache.cachedOrigin ); + VectorCopy( cent->lerpAngles, cent->buildableCache.cachedAngles ); + VectorCopy( surfNormal, cent->buildableCache.cachedNormal ); + cent->buildableCache.cachedType = es->modelindex; + } + } + else + { + VectorCopy( cent->lerpOrigin, ent.origin ); + AnglesToAxis( cent->lerpAngles, ent.axis ); + } //offset on the Z axis if required - VectorMA( ent.origin, BG_FindZOffsetForBuildable( es->modelindex ), surfNormal, ent.origin ); + VectorMA( ent.origin, BG_BuildableConfig( es->modelindex )->zOffset, surfNormal, ent.origin ); VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all VectorCopy( ent.origin, ent.lightingOrigin ); ent.hModel = cg_buildables[ es->modelindex ].models[ 0 ]; - if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( !( es->eFlags & EF_B_SPAWNED ) ) { sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild; - if( team == BIT_HUMANS ) + if( team == TEAM_HUMANS ) { ent.customShader = cgs.media.humanSpawningShader; prebuildSound = cgs.media.humanBuildablePrebuild; } - else if( team == BIT_ALIENS ) + else if( team == TEAM_ALIENS ) prebuildSound = cgs.media.alienBuildablePrebuild; trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); @@ -1268,7 +1309,7 @@ void CG_Buildable( centity_t *cent ) CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); //rescale the model - scale = BG_FindModelScaleForBuildable( es->modelindex ); + scale = BG_BuildableConfig( es->modelindex )->modelScale; if( scale != 1.0f ) { @@ -1281,6 +1322,8 @@ void CG_Buildable( centity_t *cent ) else ent.nonNormalizedAxes = qfalse; + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + ent.customShader = cgs.media.redBuildShader; //add to refresh list trap_R_AddRefEntityToScene( &ent ); @@ -1324,6 +1367,9 @@ void CG_Buildable( centity_t *cent ) else turretBarrel.nonNormalizedAxes = qfalse; + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretBarrel.customShader = cgs.media.redBuildShader; + trap_R_AddRefEntityToScene( &turretBarrel ); } @@ -1366,6 +1412,9 @@ void CG_Buildable( centity_t *cent ) else turretTop.nonNormalizedAxes = qfalse; + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretTop.customShader = cgs.media.redBuildShader; + trap_R_AddRefEntityToScene( &turretTop ); } @@ -1375,7 +1424,7 @@ void CG_Buildable( centity_t *cent ) weaponInfo_t *weapon = &cg_weapons[ es->weapon ]; if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME || - BG_FindProjTypeForBuildable( es->modelindex ) == WP_TESLAGEN ) + BG_Buildable( es->modelindex )->turretProjType == WP_TESLAGEN ) { if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] || weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] || @@ -1397,27 +1446,34 @@ void CG_Buildable( centity_t *cent ) trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } - health = es->generic1 & B_HEALTH_MASK; - healthScale = (float)health / B_HEALTH_MASK; + health = es->misc; - if( healthScale < cent->lastBuildableHealthScale && ( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) + if( health < cent->lastBuildableHealth && + ( es->eFlags & EF_B_SPAWNED ) ) { if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) { - if( team == BIT_HUMANS ) + if( team == TEAM_HUMANS ) { int i = rand( ) % 4; trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] ); } - else if( team == BIT_ALIENS ) + else if( team == TEAM_ALIENS ) trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage ); cent->lastBuildableDamageSoundTime = cg.time; } } - cent->lastBuildableHealthScale = healthScale; + cent->lastBuildableHealth = health; //smoke etc for damaged buildables CG_BuildableParticleEffects( cent ); + + if ( cg_rangeMarkerForBlueprint.integer ) + { + // only light up the powered buildables. + if ( es->eFlags & EF_B_POWERED ) + CG_GhostBuildableRangeMarker( es->modelindex, ent.origin, surfNormal ); + } } diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c index 68aab6c..90e2e2d 100644 --- a/src/cgame/cg_consolecmds.c +++ b/src/cgame/cg_consolecmds.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,34 +17,16 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_consolecmds.c -- text commands typed in at the local console, or // executed by a key binding - #include "cg_local.h" - - -void CG_TargetCommand_f( void ) -{ - int targetNum; - char test[ 4 ]; - - targetNum = CG_CrosshairPlayer( ); - if( !targetNum ) - return; - - trap_Argv( 1, test, 4 ); - trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); -} - - - /* ================= CG_SizeUp_f @@ -53,7 +36,7 @@ Keybinding command */ static void CG_SizeUp_f( void ) { - trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer + 10 ) ) ); + trap_Cvar_Set( "cg_viewsize", va( "%i", MIN( cg_viewsize.integer + 10, 100 ) ) ); } @@ -66,7 +49,7 @@ Keybinding command */ static void CG_SizeDown_f( void ) { - trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer - 10 ) ) ); + trap_Cvar_Set( "cg_viewsize", va( "%i", MAX( cg_viewsize.integer - 10, 30 ) ) ); } @@ -91,7 +74,6 @@ qboolean CG_RequestScores( void ) // the scores are more than two seconds out of data, // so request new ones cg.scoresRequestTime = cg.time; - //TA: added \n SendClientCommand doesn't call flush( )? trap_SendClientCommand( "score\n" ); return qtrue; @@ -127,26 +109,12 @@ static void CG_ScoresDown_f( void ) { Menu_SetFeederSelection( menuScoreboard, FEEDER_ALIENTEAM_LIST, 0, NULL ); Menu_SetFeederSelection( menuScoreboard, FEEDER_HUMANTEAM_LIST, 0, NULL ); - } - - if( CG_RequestScores( ) ) - { - // leave the current scores up if they were already - // displayed, but if this is the first hit, clear them out - if( !cg.showScores ) - { - if( cg_debugRandom.integer ) - CG_Printf( "CG_ScoresDown_f: scores out of date\n" ); - - cg.showScores = qtrue; - cg.numScores = 0; - } + cg.showScores = qtrue; } else { - // show the cached contents even if they just pressed if it - // is within two seconds - cg.showScores = qtrue; + cg.showScores = qfalse; + cg.numScores = 0; } } @@ -159,70 +127,111 @@ static void CG_ScoresUp_f( void ) } } -static void CG_TellTarget_f( void ) +void CG_ClientList_f( void ) { - int clientNum; - char command[ 128 ]; - char message[ 128 ]; + clientInfo_t *ci; + int i; + int count = 0; - clientNum = CG_CrosshairPlayer( ); - if( clientNum == -1 ) - return; + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = &cgs.clientinfo[ i ]; + if( !ci->infoValid ) + continue; - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); + switch( ci->team ) + { + case TEAM_ALIENS: + Com_Printf( "%2d " S_COLOR_RED "A " S_COLOR_WHITE "%s\n", i, + ci->name ); + break; + + case TEAM_HUMANS: + Com_Printf( "%2d " S_COLOR_CYAN "H " S_COLOR_WHITE "%s\n", i, + ci->name ); + break; + + default: + case TEAM_NONE: + case NUM_TEAMS: + Com_Printf( "%2d S %s\n", i, ci->name ); + break; + } + + count++; + } + + Com_Printf( "Listed %2d clients\n", count ); } -static void CG_TellAttacker_f( void ) +static void CG_VoiceMenu_f( void ) { - int clientNum; - char command[ 128 ]; - char message[ 128 ]; - - clientNum = CG_LastAttacker( ); - if( clientNum == -1 ) - return; + char cmd[sizeof("voicemenu3")]; + + trap_Argv(0, cmd, sizeof(cmd)); + + switch (cmd[9]) { + default: + case '\0': + trap_Cvar_Set("ui_voicemenu", "1"); + break; + case '2': + trap_Cvar_Set("ui_voicemenu", "2"); + break; + case '3': + trap_Cvar_Set("ui_voicemenu", "3"); + break; + }; + + trap_SendConsoleCommand("menu tremulous_voicecmd\n"); +} - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); +static void CG_UIMenu_f( void ) +{ + trap_SendConsoleCommand( va( "menu \"%s\"\n", CG_Argv( 1 ) ) ); } -typedef struct +static void CG_KillMessage_f( void ) { - char *cmd; - void (*function)( void ); -} consoleCommand_t; + char msg1[ 33 * 3 + 1]; + char msg2[ 33 * 3 + 1 ]; + trap_Argv( 1, msg1, sizeof(msg1) ); + trap_Argv( 2, msg2, sizeof(msg2) ); + CG_AddToKillMsg(msg1, msg2, WP_GRENADE); +} static consoleCommand_t commands[ ] = { - { "testgun", CG_TestGun_f }, - { "testmodel", CG_TestModel_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "cgame_memory", BG_MemoryInfo }, + { "clientlist", CG_ClientList_f }, + { "destroyTestPS", CG_DestroyTestPS_f }, + { "destroyTestTS", CG_DestroyTestTS_f }, { "nextframe", CG_TestModelNextFrame_f }, - { "prevframe", CG_TestModelPrevFrame_f }, { "nextskin", CG_TestModelNextSkin_f }, + { "prevframe", CG_TestModelPrevFrame_f }, { "prevskin", CG_TestModelPrevSkin_f }, - { "viewpos", CG_Viewpos_f }, - { "+scores", CG_ScoresDown_f }, - { "-scores", CG_ScoresUp_f }, - { "scoresUp", CG_scrollScoresUp_f }, { "scoresDown", CG_scrollScoresDown_f }, - { "sizeup", CG_SizeUp_f }, + { "scoresUp", CG_scrollScoresUp_f }, { "sizedown", CG_SizeDown_f }, - { "weapnext", CG_NextWeapon_f }, - { "weapprev", CG_PrevWeapon_f }, - { "weapon", CG_Weapon_f }, - { "tell_target", CG_TellTarget_f }, - { "tell_attacker", CG_TellAttacker_f }, - { "tcmd", CG_TargetCommand_f }, + { "sizeup", CG_SizeUp_f }, + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, { "testPS", CG_TestPS_f }, - { "destroyTestPS", CG_DestroyTestPS_f }, { "testTS", CG_TestTS_f }, - { "destroyTestTS", CG_DestroyTestTS_f }, + { "ui_menu", CG_UIMenu_f }, + { "viewpos", CG_Viewpos_f }, + { "voicemenu", CG_VoiceMenu_f }, + { "voicemenu2", CG_VoiceMenu_f }, + { "voicemenu3", CG_VoiceMenu_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapon", CG_Weapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "zcp", CG_CenterPrint_f }, + { "zkill", CG_KillMessage_f } }; - /* ================= CG_ConsoleCommand @@ -233,33 +242,81 @@ Cmd_Argc() / Cmd_Argv() */ qboolean CG_ConsoleCommand( void ) { - const char *cmd; - const char *arg1; - int i; + consoleCommand_t *cmd; - cmd = CG_Argv( 0 ); + cmd = bsearch( CG_Argv( 0 ), commands, + ARRAY_LEN( commands ), sizeof( commands[ 0 ] ), + cmdcmp ); - //TA: ugly hacky special case - if( !Q_stricmp( cmd, "ui_menu" ) ) - { - arg1 = CG_Argv( 1 ); - trap_SendConsoleCommand( va( "menu %s\n", arg1 ) ); - return qtrue; - } + if( !cmd ) + return qfalse; - for( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); i++ ) - { - if( !Q_stricmp( cmd, commands[ i ].cmd ) ) - { - commands[ i ].function( ); - return qtrue; - } - } + cmd->function( ); + return qtrue; +} - return qfalse; +#ifndef MODULE_INTERFACE_11 +/* +================== +CG_CompleteCallVote_f +================== +*/ +void CG_CompleteCallVote_f( int argNum ) { + switch( argNum ) { + case 2: + trap_Field_CompleteList( + "[" + "\"allowbuild\"," + "\"cancel\"," + "\"denybuild\"," + "\"draw\"," + "\"extend\"," + "\"kick\"," + "\"map\"," + "\"map_restart\"," + "\"mute\"," + "\"nextmap\"," + "\"poll\"," + "\"sudden_death\"," + "\"unmute\" ]" ); + break; + } } +static consoleCommandCompletions_t commandCompletions[ ] = +{ + { "callvote", CG_CompleteCallVote_f } +}; + +/* +================= +CG_Console_CompleteArgument + +Try to complete the client command line argument given in +argNum. Returns true if a completion function is found in CGAME, +otherwise client tries another completion method. +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_Console_CompleteArgument( int argNum ) +{ + consoleCommandCompletions_t *cmd; + + // Skip command prefix character + cmd = bsearch( CG_Argv( 0 ) + 1, commandCompletions, + ARRAY_LEN( commandCompletions ), sizeof( commandCompletions[ 0 ] ), + cmdcmp ); + + if( !cmd ) + return qfalse; + + cmd->function( argNum ); + return qtrue; +} +#endif + /* ================= CG_InitConsoleCommands @@ -272,7 +329,7 @@ void CG_InitConsoleCommands( void ) { int i; - for( i = 0 ; i < sizeof( commands ) / sizeof( commands[ 0 ] ) ; i++ ) + for( i = 0; i < ARRAY_LEN( commands ); i++ ) trap_AddCommand( commands[ i ].cmd ); // @@ -280,31 +337,28 @@ void CG_InitConsoleCommands( void ) // forwarded to the server after they are not recognized locally // trap_AddCommand( "kill" ); + trap_AddCommand( "ui_messagemode" ); + trap_AddCommand( "ui_messagemode2" ); + trap_AddCommand( "ui_messagemode3" ); + trap_AddCommand( "ui_messagemode4" ); trap_AddCommand( "say" ); trap_AddCommand( "say_team" ); - trap_AddCommand( "tell" ); trap_AddCommand( "vsay" ); trap_AddCommand( "vsay_team" ); - trap_AddCommand( "vtell" ); - trap_AddCommand( "vtaunt" ); - trap_AddCommand( "vosay" ); - trap_AddCommand( "vosay_team" ); - trap_AddCommand( "votell" ); + trap_AddCommand( "vsay_local" ); + trap_AddCommand( "m" ); + trap_AddCommand( "mt" ); trap_AddCommand( "give" ); trap_AddCommand( "god" ); trap_AddCommand( "notarget" ); trap_AddCommand( "noclip" ); trap_AddCommand( "team" ); trap_AddCommand( "follow" ); - trap_AddCommand( "levelshot" ); - trap_AddCommand( "addbot" ); trap_AddCommand( "setviewpos" ); trap_AddCommand( "callvote" ); trap_AddCommand( "vote" ); trap_AddCommand( "callteamvote" ); trap_AddCommand( "teamvote" ); - trap_AddCommand( "stats" ); - trap_AddCommand( "teamtask" ); trap_AddCommand( "class" ); trap_AddCommand( "build" ); trap_AddCommand( "buy" ); @@ -315,11 +369,6 @@ void CG_InitConsoleCommands( void ) trap_AddCommand( "itemtoggle" ); trap_AddCommand( "destroy" ); trap_AddCommand( "deconstruct" ); - trap_AddCommand( "menu" ); - trap_AddCommand( "ui_menu" ); - trap_AddCommand( "mapRotation" ); - trap_AddCommand( "stopMapRotation" ); - trap_AddCommand( "advanceMapRotation" ); - trap_AddCommand( "alienWin" ); - trap_AddCommand( "humanWin" ); + trap_AddCommand( "ignore" ); + trap_AddCommand( "unignore" ); } diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c index 1a3ecae..38c9578 100644 --- a/src/cgame/cg_draw.c +++ b/src/cgame/cg_draw.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,284 +17,84 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_draw.c -- draw all of the graphical elements during // active (after loading) gameplay - #include "cg_local.h" -#include "../ui/ui_shared.h" +#include "ui/ui_shared.h" -// used for scoreboard -extern displayContextDef_t cgDC; menuDef_t *menuScoreboard = NULL; -int drawTeamOverlayModificationCount = -1; - -int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; -int numSortedTeamPlayers; - -//TA UI -int CG_Text_Width( const char *text, float scale, int limit ) +static void CG_AlignText( rectDef_t *rect, const char *text, float scale, + float w, float h, + int align, int valign, + float *x, float *y ) { - int count,len; - float out; - glyphInfo_t *glyph; - float useScale; -// FIXME: see ui_main.c, same problem -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; + float tx, ty; - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; - - useScale = scale * font->glyphScale; - out = 0; - - if( text ) + if( scale > 0.0f ) { - len = strlen( text ); - if( limit > 0 && len > limit ) - len = limit; - - count = 0; - while( s && *s && count < len ) - { - if( Q_IsColorString( s ) ) - { - s += 2; - continue; - } - else - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build - out += glyph->xSkip; - s++; - count++; - } - } + w = UI_Text_Width( text, scale ); + h = UI_Text_Height( text, scale ); } - return out * useScale; -} - -int CG_Text_Height( const char *text, float scale, int limit ) -{ - int len, count; - float max; - glyphInfo_t *glyph; - float useScale; -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; - - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; - - useScale = scale * font->glyphScale; - max = 0; - - if( text ) + switch( align ) { - len = strlen( text ); - if( limit > 0 && len > limit ) - len = limit; - - count = 0; - while( s && *s && count < len ) - { - if( Q_IsColorString( s ) ) - { - s += 2; - continue; - } - else - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build - if( max < glyph->height ) - max = glyph->height; - - s++; - count++; - } - } - } - - return max * useScale; -} + default: + case ALIGN_LEFT: + tx = 0.0f; + break; -void CG_Text_PaintChar( float x, float y, float width, float height, float scale, - float s, float t, float s2, float t2, qhandle_t hShader ) -{ - float w, h; - w = width * scale; - h = height * scale; - CG_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); -} + case ALIGN_RIGHT: + tx = rect->w - w; + break; -void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, - float adjust, int limit, int style ) -{ - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; + case ALIGN_CENTER: + tx = ( rect->w - w ) / 2.0f; + break; - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; + case ALIGN_NONE: + tx = 0; + break; + } - useScale = scale * font->glyphScale; - if( text ) + switch( valign ) { -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - - trap_R_SetColor( color ); - memcpy( &newColor[ 0 ], &color[ 0 ], sizeof( vec4_t ) ); - len = strlen( text ); - - if( limit > 0 && len > limit ) - len = limit; - - count = 0; - while( s && *s && count < len ) - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build + default: + case VALIGN_BOTTOM: + ty = rect->h; + break; - if( Q_IsColorString( s ) ) - { - memcpy( newColor, g_color_table[ ColorIndex( *( s + 1 ) ) ], sizeof( newColor ) ); - newColor[ 3 ] = color[ 3 ]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } - else - { - float yadj = useScale * glyph->top; - if( style == ITEM_TEXTSTYLE_SHADOWED || - style == ITEM_TEXTSTYLE_SHADOWEDMORE ) - { - int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; - colorBlack[ 3 ] = newColor[ 3 ]; - trap_R_SetColor( colorBlack ); - CG_Text_PaintChar( x + ofs, y - yadj + ofs, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - colorBlack[ 3 ] = 1.0; - trap_R_SetColor( newColor ); - } - else if( style == ITEM_TEXTSTYLE_NEON ) - { - vec4_t glow, outer, inner, white; - - glow[ 0 ] = newColor[ 0 ] * 0.5; - glow[ 1 ] = newColor[ 1 ] * 0.5; - glow[ 2 ] = newColor[ 2 ] * 0.5; - glow[ 3 ] = newColor[ 3 ] * 0.2; - - outer[ 0 ] = newColor[ 0 ]; - outer[ 1 ] = newColor[ 1 ]; - outer[ 2 ] = newColor[ 2 ]; - outer[ 3 ] = newColor[ 3 ]; - - inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5; - inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5; - inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5; - inner[ 3 ] = newColor[ 3 ]; - - white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f; - - trap_R_SetColor( glow ); - CG_Text_PaintChar( x - 3, y - yadj - 3, - glyph->imageWidth + 6, - glyph->imageHeight + 6, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( outer ); - CG_Text_PaintChar( x - 1, y - yadj - 1, - glyph->imageWidth + 2, - glyph->imageHeight + 2, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( inner ); - CG_Text_PaintChar( x - 0.5, y - yadj - 0.5, - glyph->imageWidth + 1, - glyph->imageHeight + 1, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( white ); - } + case VALIGN_TOP: + ty = h; + break; + case VALIGN_CENTER: + ty = h + ( ( rect->h - h ) / 2.0f ); + break; - CG_Text_PaintChar( x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); + case VALIGN_NONE: + ty = 0; + break; + } - x += ( glyph->xSkip * useScale ) + adjust; - s++; - count++; - } - } + if( x ) + *x = rect->x + tx; - trap_R_SetColor( NULL ); - } + if( y ) + *y = rect->y + ty; } /* ============== CG_DrawFieldPadded -Draws large numbers for status bar and powerups +Draws large numbers for status bar ============== */ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value ) @@ -307,7 +108,7 @@ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int val charWidth = CHAR_WIDTH; if( !( charHeight = ch ) ) - charWidth = CHAR_HEIGHT; + charHeight = CHAR_HEIGHT; if( width < 1 ) return; @@ -344,7 +145,7 @@ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int val orgL = l; - x += 2; + x += ( 2.0f * cgDC.aspectScale ); ptr = num; while( *ptr && l ) @@ -373,7 +174,7 @@ static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int val ============== CG_DrawField -Draws large numbers for status bar and powerups +Draws large numbers for status bar ============== */ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) @@ -387,7 +188,7 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) charWidth = CHAR_WIDTH; if( !( charHeight = ch ) ) - charWidth = CHAR_HEIGHT; + charHeight = CHAR_HEIGHT; if( width < 1 ) return; @@ -422,7 +223,7 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) if( l > width ) l = width; - x += 2 + charWidth * ( width - l ); + x += ( 2.0f * cgDC.aspectScale ) + charWidth * ( width - l ); ptr = num; while( *ptr && l ) @@ -440,18 +241,22 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) } static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special, float progress ) + int align, int textalign, int textStyle, + float borderSize, float progress ) { - float rimWidth = rect->h / 20.0f; + float rimWidth; float doneWidth, leftWidth; - float tx, ty, tw, th; + float tx, ty; char textBuffer[ 8 ]; - if( rimWidth < 0.6f ) - rimWidth = 0.6f; - - if( special >= 0.0f ) - rimWidth = special; + if( borderSize >= 0.0f ) + rimWidth = borderSize; + else + { + rimWidth = rect->h / 20.0f; + if( rimWidth < 0.6f ) + rimWidth = 0.6f; + } if( progress < 0.0f ) progress = 0.0f; @@ -464,7 +269,7 @@ static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, trap_R_SetColor( color ); //draw rim and bar - if( align == ITEM_ALIGN_RIGHT ) + if( align == ALIGN_RIGHT ) { CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader ); CG_DrawPic( rect->x + rimWidth, rect->y, @@ -490,31 +295,9 @@ static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, if( scale > 0.0 ) { Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) ); - tw = CG_Text_Width( textBuffer, scale, 0 ); - th = scale * 40.0f; + CG_AlignText( rect, textBuffer, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty ); - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x + ( rect->w / 10.0f ); - ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f ); - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - ( rect->w / 10.0f ) - tw; - ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f ); - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f ); - ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f ); - break; - - default: - tx = ty = 0.0f; - } - - CG_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle ); + UI_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle ); } } @@ -539,37 +322,19 @@ static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean p value = ps->persistant[ PERS_CREDIT ]; if( value > -1 ) { - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && - !CG_AtHighestClass( ) ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) + if( !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ], + value, cgs.alienStage ) && + cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME && + ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) & 1 ) { - if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 ) - color[ 3 ] = 0.0f; + color[ 3 ] = 0.0f; } - } - - trap_R_SetColor( color ); - - if( padding ) - CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); - else - CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value ); - - trap_R_SetColor( NULL ); - } -} -static void CG_DrawPlayerBankValue( rectDef_t *rect, vec4_t color, qboolean padding ) -{ - int value; - playerState_t *ps; - - ps = &cg.snap->ps; + value /= ALIEN_CREDITS_PER_KILL; + } - value = ps->persistant[ PERS_BANK ]; - if( value > -1 ) - { trap_R_SetColor( color ); if( padding ) @@ -581,114 +346,66 @@ static void CG_DrawPlayerBankValue( rectDef_t *rect, vec4_t color, qboolean padd } } -#define HH_MIN_ALPHA 0.2f -#define HH_MAX_ALPHA 0.8f -#define HH_ALPHA_DIFF (HH_MAX_ALPHA-HH_MIN_ALPHA) - -#define AH_MIN_ALPHA 0.2f -#define AH_MAX_ALPHA 0.8f -#define AH_ALPHA_DIFF (AH_MAX_ALPHA-AH_MIN_ALPHA) - -/* -============== -CG_DrawPlayerStamina1 -============== -*/ -static void CG_DrawPlayerStamina1( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerCreditsFraction( rectDef_t *rect, vec4_t color, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; - float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; - float progress; + float fraction; + float height; - stamina -= ( 2 * (int)maxStaminaBy3 ); - progress = stamina / maxStaminaBy3; + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; - if( progress > 1.0f ) - progress = 1.0f; - else if( progress < 0.0f ) - progress = 0.0f; + fraction = ((float)(cg.predictedPlayerState.persistant[ PERS_CREDIT ] % + ALIEN_CREDITS_PER_KILL)) / ALIEN_CREDITS_PER_KILL; - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + CG_AdjustFrom640( &rect->x, &rect->y, &rect->w, &rect->h ); + height = rect->h * fraction; trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_DrawStretchPic( rect->x, rect->y - height + rect->h, rect->w, + height, 0.0f, 1.0f - fraction, 1.0f, 1.0f, shader ); trap_R_SetColor( NULL ); } -/* -============== -CG_DrawPlayerStamina2 -============== -*/ -static void CG_DrawPlayerStamina2( rectDef_t *rect, vec4_t color, qhandle_t shader ) -{ - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; - float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; - float progress; - - stamina -= (int)maxStaminaBy3; - progress = stamina / maxStaminaBy3; - - if( progress > 1.0f ) - progress = 1.0f; - else if( progress < 0.0f ) - progress = 0.0f; - - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); - - trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - trap_R_SetColor( NULL ); -} /* ============== -CG_DrawPlayerStamina3 +CG_DrawPlayerStamina ============== */ -static void CG_DrawPlayerStamina3( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerStamina( int ownerDraw, rectDef_t *rect, + vec4_t backColor, vec4_t foreColor, + qhandle_t shader ) { playerState_t *ps = &cg.snap->ps; float stamina = ps->stats[ STAT_STAMINA ]; - float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f; + float maxStaminaBy3 = (float)STAMINA_MAX / 3.0f; float progress; + vec4_t color; + switch( ownerDraw ) + { + case CG_PLAYER_STAMINA_1: + progress = ( stamina - 2 * (int)maxStaminaBy3 ) / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_2: + progress = ( stamina - (int)maxStaminaBy3 ) / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_3: progress = stamina / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_4: + progress = ( stamina + STAMINA_MAX ) / STAMINA_MAX; + break; + default: + return; + } if( progress > 1.0f ) progress = 1.0f; else if( progress < 0.0f ) progress = 0.0f; - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); - - trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - trap_R_SetColor( NULL ); -} - -/* -============== -CG_DrawPlayerStamina4 -============== -*/ -static void CG_DrawPlayerStamina4( rectDef_t *rect, vec4_t color, qhandle_t shader ) -{ - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; - float progress; - - stamina += (float)MAX_STAMINA; - progress = stamina / (float)MAX_STAMINA; - - if( progress > 1.0f ) - progress = 1.0f; - else if( progress < 0.0f ) - progress = 0.0f; - - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); @@ -700,15 +417,28 @@ static void CG_DrawPlayerStamina4( rectDef_t *rect, vec4_t color, qhandle_t shad CG_DrawPlayerStaminaBolt ============== */ -static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - float stamina = ps->stats[ STAT_STAMINA ]; + float stamina = cg.snap->ps.stats[ STAT_STAMINA ]; + vec4_t color; - if( stamina < 0 ) - color[ 3 ] = HH_MIN_ALPHA; + if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_SPEEDBOOST ) + { + if( stamina >= 0 ) + Vector4Lerp( ( sin( cg.time / 150.0f ) + 1 ) / 2, + backColor, foreColor, color ); + else + Vector4Lerp( ( sin( cg.time / 2000.0f ) + 1 ) / 2, + backColor, foreColor, color ); + } else - color[ 3 ] = HH_MAX_ALPHA; + { + if( stamina < 0 ) + Vector4Copy( backColor, color ); + else + Vector4Copy( foreColor, color ); + } trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); @@ -720,40 +450,42 @@ static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t s CG_DrawPlayerClipsRing ============== */ -static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { playerState_t *ps = &cg.snap->ps; centity_t *cent; float buildTime = ps->stats[ STAT_MISC ]; float progress; float maxDelay; + weapon_t weapon; + vec4_t color; cent = &cg_entities[ cg.snap->ps.clientNum ]; + weapon = BG_GetPlayerWeapon( ps ); - switch( cent->currentState.weapon ) + switch( weapon ) { case WP_ABUILD: case WP_ABUILD2: case WP_HBUILD: - case WP_HBUILD2: - maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon ); - - if( buildTime > maxDelay ) - buildTime = maxDelay; + if( buildTime > MAXIMUM_BUILD_TIME ) + buildTime = MAXIMUM_BUILD_TIME; + progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME; - progress = ( maxDelay - buildTime ) / maxDelay; - - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); break; default: if( ps->weaponstate == WEAPON_RELOADING ) { - maxDelay = (float)BG_FindReloadTimeForWeapon( cent->currentState.weapon ); + maxDelay = (float)BG_Weapon( cent->currentState.weapon )->reloadTime; progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay; - color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); } + else + Com_Memcpy( color, foreColor, sizeof( color ) ); break; } @@ -767,24 +499,20 @@ static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t sha CG_DrawPlayerBuildTimerRing ============== */ -static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { playerState_t *ps = &cg.snap->ps; - centity_t *cent; float buildTime = ps->stats[ STAT_MISC ]; float progress; - float maxDelay; + vec4_t color; - cent = &cg_entities[ cg.snap->ps.clientNum ]; - - maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon ); + if( buildTime > MAXIMUM_BUILD_TIME ) + buildTime = MAXIMUM_BUILD_TIME; - if( buildTime > maxDelay ) - buildTime = maxDelay; + progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME; - progress = ( maxDelay - buildTime ) / maxDelay; - - color[ 3 ] = AH_MIN_ALPHA + ( progress * AH_ALPHA_DIFF ); + Vector4Lerp( progress, backColor, foreColor, color ); trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); @@ -796,17 +524,14 @@ static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_ CG_DrawPlayerBoosted ============== */ -static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED; - - if( boosted ) - color[ 3 ] = AH_MAX_ALPHA; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + trap_R_SetColor( foreColor ); else - color[ 3 ] = AH_MIN_ALPHA; + trap_R_SetColor( backColor ); - trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } @@ -816,26 +541,20 @@ static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shade CG_DrawPlayerBoosterBolt ============== */ -static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED; - vec4_t localColor; + vec4_t color; - Vector4Copy( color, localColor ); - - if( boosted ) - { - if( ps->stats[ STAT_BOOSTTIME ] > BOOST_TIME - 3000 ) - { - qboolean flash = ( ps->stats[ STAT_BOOSTTIME ] / 500 ) % 2; - - if( flash ) - localColor[ 3 ] = 1.0f; - } - } + // Flash bolts when the boost is almost out + if( ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) && + ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTEDWARNING ) ) + Vector4Lerp( ( sin( cg.time / 100.0f ) + 1 ) / 2, + backColor, foreColor, color ); + else + Vector4Copy( foreColor, color ); - trap_R_SetColor( localColor ); + trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } @@ -847,38 +566,49 @@ CG_DrawPlayerPoisonBarbs */ static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - int x = rect->x; - int y = rect->y; - int width = rect->w; - int height = rect->h; - qboolean vertical; - int iconsize, numBarbs, i; + qboolean vertical; + float x = rect->x, y = rect->y; + float width = rect->w, height = rect->h; + float diff; + int iconsize, numBarbs, maxBarbs; + + maxBarbs = BG_Weapon( cg.snap->ps.weapon )->maxAmmo; + numBarbs = cg.snap->ps.ammo; + if( maxBarbs <= 0 || numBarbs <= 0 ) + return; - numBarbs = ps->ammo; + // adjust these first to ensure the aspect ratio of the barb image is + // preserved + CG_AdjustFrom640( &x, &y, &width, &height ); if( height > width ) { vertical = qtrue; iconsize = width; + if( maxBarbs != 1 ) // avoid division by zero + diff = ( height - iconsize ) / (float)( maxBarbs - 1 ); + else + diff = 0; // doesn't matter, won't be used } - else if( height <= width ) + else { vertical = qfalse; iconsize = height; + if( maxBarbs != 1 ) + diff = ( width - iconsize ) / (float)( maxBarbs - 1 ); + else + diff = 0; } - if( color[ 3 ] != 0.0 ) - trap_R_SetColor( color ); + trap_R_SetColor( color ); - for( i = 0; i < numBarbs; i ++ ) + for( ; numBarbs > 0; numBarbs-- ) { + trap_R_DrawStretchPic( x, y, iconsize, iconsize, 0, 0, 1, 1, shader ); if( vertical ) - y += iconsize; + y += diff; else - x += iconsize; - - CG_DrawPic( x, y, iconsize, iconsize, shader ); + x += diff; } trap_R_SetColor( NULL ); @@ -889,70 +619,83 @@ static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t s CG_DrawPlayerWallclimbing ============== */ -static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t backColor, vec4_t foreColor, qhandle_t shader ) { - playerState_t *ps = &cg.snap->ps; - qboolean ww = ps->stats[ STAT_STATE ] & SS_WALLCLIMBING; - - if( ww ) - color[ 3 ] = AH_MAX_ALPHA; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) + trap_R_SetColor( foreColor ); else - color[ 3 ] = AH_MIN_ALPHA; + trap_R_SetColor( backColor ); - trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } -static void CG_DrawPlayerStamina( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) -{ - playerState_t *ps = &cg.snap->ps; - int stamina = ps->stats[ STAT_STAMINA ]; - float progress = ( (float)stamina + (float)MAX_STAMINA ) / ( (float)MAX_STAMINA * 2.0f ); - - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, progress ); -} - static void CG_DrawPlayerAmmoValue( rectDef_t *rect, vec4_t color ) { - int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[ cg.snap->ps.clientNum ]; - ps = &cg.snap->ps; + int value; + int valueMarked = -1; + qboolean bp = qfalse; - if( cent->currentState.weapon ) + switch( cg.snap->ps.stats[ STAT_WEAPON ] ) { - switch( cent->currentState.weapon ) - { - case WP_ABUILD: - case WP_ABUILD2: - //percentage of BP remaining - value = cgs.alienBuildPoints; - break; + case WP_NONE: + case WP_BLASTER: + return; - case WP_HBUILD: - case WP_HBUILD2: - //percentage of BP remaining - value = cgs.humanBuildPoints; - break; + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + value = cg.snap->ps.persistant[ PERS_BP ]; + valueMarked = cg.snap->ps.persistant[ PERS_MARKEDBP ]; + bp = qtrue; + break; - default: - value = ps->ammo; - break; - } + default: + value = cg.snap->ps.ammo; + break; + } - if( value > 999 ) - value = 999; + if( value > 999 ) + value = 999; + if( valueMarked > 999 ) + valueMarked = 999; - if( value > -1 ) + if( value > -1 ) + { + float tx, ty; + const char *text; + float scale; + int len; + + trap_R_SetColor( color ); + if( !bp ) { - trap_R_SetColor( color ); - CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + CG_DrawField( rect->x - 5, rect->y, 4, rect->w / 4, rect->h, value ); trap_R_SetColor( NULL ); + return; } + + if( valueMarked > 0 ) + text = va( "%d+(%d)", value, valueMarked ); + else + text = va( "%d", value ); + + len = strlen( text ); + + if( len <= 4 ) + scale = 0.50; + else if( len <= 6 ) + scale = 0.43; + else if( len == 7 ) + scale = 0.36; + else if( len == 8 ) + scale = 0.33; + else + scale = 0.31; + + CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_RIGHT, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint( tx + 1, ty, scale, color, text, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + trap_R_SetColor( NULL ); } } @@ -964,7 +707,7 @@ CG_DrawAlienSense */ static void CG_DrawAlienSense( rectDef_t *rect ) { - if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_PCLASS ], SCA_ALIENSENSE ) ) + if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_CLASS ], SCA_ALIENSENSE ) ) CG_AlienSense( rect ); } @@ -999,19 +742,25 @@ static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t co es = &cg_entities[ trace.entityNum ].currentState; - if( es->eType == ET_BUILDABLE && BG_FindUsableForBuildable( es->modelindex ) && - cg.predictedPlayerState.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) + if( es->eType == ET_BUILDABLE && BG_Buildable( es->modelindex )->usable && + cg.predictedPlayerState.stats[ STAT_TEAM ] == BG_Buildable( es->modelindex )->team ) { //hack to prevent showing the usable buildable when you aren't carrying an energy weapon if( ( es->modelindex == BA_H_REACTOR || es->modelindex == BA_H_REPEATER ) && - ( !BG_FindUsesEnergyForWeapon( cg.snap->ps.weapon ) || - BG_FindInfinteAmmoForWeapon( cg.snap->ps.weapon ) ) ) + ( !BG_Weapon( cg.snap->ps.weapon )->usesEnergy || + BG_Weapon( cg.snap->ps.weapon )->infiniteAmmo ) ) + { + cg.nearUsableBuildable = BA_NONE; return; + } trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); + cg.nearUsableBuildable = es->modelindex; } + else + cg.nearUsableBuildable = BA_NONE; } @@ -1019,221 +768,381 @@ static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t co static void CG_DrawPlayerBuildTimer( rectDef_t *rect, vec4_t color ) { - float progress; int index; - centity_t *cent; playerState_t *ps; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; - if( cent->currentState.weapon ) - { - switch( cent->currentState.weapon ) - { - case WP_ABUILD: - progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_BASE_DELAY; - break; - - case WP_ABUILD2: - progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_ADV_DELAY; - break; - - case WP_HBUILD: - progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD_DELAY; - break; - - case WP_HBUILD2: - progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD2_DELAY; - break; + if( ps->stats[ STAT_MISC ] <= 0 ) + return; - default: - return; - break; - } + switch( ps->stats[ STAT_WEAPON ] ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + break; - if( !ps->stats[ STAT_MISC ] ) + default: return; + } - index = (int)( progress * 8.0f ); - - if( index > 7 ) - index = 7; - else if( index < 0 ) - index = 0; + index = 8 * ( ps->stats[ STAT_MISC ] - 1 ) / MAXIMUM_BUILD_TIME; + if( index > 7 ) + index = 7; + else if( index < 0 ) + index = 0; - if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME ) - { - if( ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 ) - { - color[ 0 ] = 1.0f; - color[ 1 ] = color[ 2 ] = 0.0f; - color[ 3 ] = 1.0f; - } - } - - trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, - cgs.media.buildWeaponTimerPie[ index ] ); - trap_R_SetColor( NULL ); + if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME && + ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = color[ 2 ] = 0.0f; + color[ 3 ] = 1.0f; } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, + cgs.media.buildWeaponTimerPie[ index ] ); + trap_R_SetColor( NULL ); } static void CG_DrawPlayerClipsValue( rectDef_t *rect, vec4_t color ) { int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[ cg.snap->ps.clientNum ]; - ps = &cg.snap->ps; + playerState_t *ps = &cg.snap->ps; - if( cent->currentState.weapon ) + switch( ps->stats[ STAT_WEAPON ] ) { - switch( cent->currentState.weapon ) - { - case WP_ABUILD: - case WP_ABUILD2: - case WP_HBUILD: - case WP_HBUILD2: - break; + case WP_NONE: + case WP_BLASTER: + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return; - default: - value = ps->clips; + default: + value = ps->clips; - if( value > -1 ) - { - trap_R_SetColor( color ); - CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); - trap_R_SetColor( NULL ); - } - break; - } + if( value > -1 ) + { + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + trap_R_SetColor( NULL ); + } + break; } } static void CG_DrawPlayerHealthValue( rectDef_t *rect, vec4_t color ) { - playerState_t *ps; - int value; - - ps = &cg.snap->ps; - - value = ps->stats[ STAT_HEALTH ]; - trap_R_SetColor( color ); - CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, + cg.snap->ps.stats[ STAT_HEALTH ] ); trap_R_SetColor( NULL ); } -static void CG_DrawPlayerHealthBar( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) -{ - playerState_t *ps; - float total; - - ps = &cg.snap->ps; - - total = ( (float)ps->stats[ STAT_HEALTH ] / (float)ps->stats[ STAT_MAX_HEALTH ] ); - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total ); -} - /* ============== CG_DrawPlayerHealthCross ============== */ -static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t color, qhandle_t shader ) +static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t ref_color ) { - playerState_t *ps = &cg.snap->ps; - int health = ps->stats[ STAT_HEALTH ]; + qhandle_t shader; + vec4_t color; + float ref_alpha; + + // Pick the current icon + shader = cgs.media.healthCross; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_3X ) + shader = cgs.media.healthCross3X; + else if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) + { + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + shader = cgs.media.healthCross2X; + else + shader = cgs.media.healthCrossMedkit; + } + else if( cg.snap->ps.stats[ STAT_STATE ] & SS_POISONED ) + shader = cgs.media.healthCrossPoisoned; - if( health < 10 ) + // Pick the alpha value + Vector4Copy( ref_color, color ); + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + cg.snap->ps.stats[ STAT_HEALTH ] < 10 ) { color[ 0 ] = 1.0f; color[ 1 ] = color[ 2 ] = 0.0f; } + ref_alpha = ref_color[ 3 ]; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_ACTIVE ) + ref_alpha = 1.0f; + + // Don't fade from nothing + if( !cg.lastHealthCross ) + cg.lastHealthCross = shader; + // Fade the icon during transition + if( cg.lastHealthCross != shader ) + { + cg.healthCrossFade += cg.frametime / 500.0f; + if( cg.healthCrossFade > 1.0f ) + { + cg.healthCrossFade = 0.0f; + cg.lastHealthCross = shader; + } + else + { + // Fading between two icons + color[ 3 ] = ref_alpha * cg.healthCrossFade; + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + color[ 3 ] = ref_alpha * ( 1.0f - cg.healthCrossFade ); + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg.lastHealthCross ); + trap_R_SetColor( NULL ); + return; + } + } + + // Not fading, draw a single icon + color[ 3 ] = ref_alpha; trap_R_SetColor( color ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); trap_R_SetColor( NULL ); } -static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, const char *s, float fraction ) +static float CG_ChargeProgress( void ) { - vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; - float tx, tw = CG_Text_Width( s, scale, 0 ); + float progress; + int min = 0, max = 0; - switch( align ) + if( cg.snap->ps.weapon == WP_ALEVEL3 ) { - case ITEM_ALIGN_LEFT: - tx = 0.0f; - break; + min = LEVEL3_POUNCE_TIME_MIN; + max = LEVEL3_POUNCE_TIME; + } + else if( cg.snap->ps.weapon == WP_ALEVEL3_UPG ) + { + min = LEVEL3_POUNCE_TIME_MIN; + max = LEVEL3_POUNCE_TIME_UPG; + } + else if( cg.snap->ps.weapon == WP_ALEVEL4 ) + { + if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_CHARGING ) + { + min = 0; + max = LEVEL4_TRAMPLE_DURATION; + } + else + { + min = LEVEL4_TRAMPLE_CHARGE_MIN; + max = LEVEL4_TRAMPLE_CHARGE_MAX; + } + } + else if( cg.snap->ps.weapon == WP_LUCIFER_CANNON ) + { + min = LCANNON_CHARGE_TIME_MIN; + max = LCANNON_CHARGE_TIME_MAX; + } - case ITEM_ALIGN_RIGHT: - tx = rect->w - tw; - break; + if( max - min <= 0.0f ) + return 0.0f; - case ITEM_ALIGN_CENTER: - tx = ( rect->w / 2.0f ) - ( tw / 2.0f ); - break; + progress = ( (float)cg.predictedPlayerState.stats[ STAT_MISC ] - min ) / + ( max - min ); - default: - tx = 0.0f; + if( progress > 1.0f ) + return 1.0f; + + if( progress < 0.0f ) + return 0.0f; + + return progress; +} + +#define CHARGE_BAR_FADE_RATE 0.002f + +static void CG_DrawPlayerChargeBarBG( rectDef_t *rect, vec4_t ref_color, + qhandle_t shader ) +{ + vec4_t color; + + if( !cg_drawChargeBar.integer || cg.chargeMeterAlpha <= 0.0f ) + return; + + color[ 0 ] = ref_color[ 0 ]; + color[ 1 ] = ref_color[ 1 ]; + color[ 2 ] = ref_color[ 2 ]; + color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha; + + // Draw meter background + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +// FIXME: This should come from the element info +#define CHARGE_BAR_CAP_SIZE 3 + +static void CG_DrawPlayerChargeBar( rectDef_t *rect, vec4_t ref_color, + qhandle_t shader ) +{ + vec4_t color; + float x, y, width, height, cap_size, progress; + + if( !cg_drawChargeBar.integer ) + return; + + // Get progress proportion and pump fade + progress = CG_ChargeProgress(); + if( progress <= 0.0f ) + { + cg.chargeMeterAlpha -= CHARGE_BAR_FADE_RATE * cg.frametime; + if( cg.chargeMeterAlpha <= 0.0f ) + { + cg.chargeMeterAlpha = 0.0f; + return; + } + } + else + { + cg.chargeMeterValue = progress; + cg.chargeMeterAlpha += CHARGE_BAR_FADE_RATE * cg.frametime; + if( cg.chargeMeterAlpha > 1.0f ) + cg.chargeMeterAlpha = 1.0f; + } + + color[ 0 ] = ref_color[ 0 ]; + color[ 1 ] = ref_color[ 1 ]; + color[ 2 ] = ref_color[ 2 ]; + color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha; + + // Flash red for Lucifer Cannon warning + if( cg.snap->ps.weapon == WP_LUCIFER_CANNON && + cg.snap->ps.stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_WARN && + ( cg.time & 128 ) ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = 0.0f; + color[ 2 ] = 0.0f; + } + + x = rect->x; + y = rect->y; + + // Horizontal charge bar + if( rect->w >= rect->h ) + { + width = ( rect->w - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue; + height = rect->h; + CG_AdjustFrom640( &x, &y, &width, &height ); + cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenXScale; + + // Draw the meter + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y, cap_size, height, 0, 0, 1, 1, shader ); + trap_R_DrawStretchPic( x + width + cap_size, y, cap_size, height, + 1, 0, 0, 1, shader ); + trap_R_DrawStretchPic( x + cap_size, y, width, height, 1, 0, 1, 1, shader ); + trap_R_SetColor( NULL ); } + // Vertical charge bar + else + { + y += rect->h; + width = rect->w; + height = ( rect->h - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue; + CG_AdjustFrom640( &x, &y, &width, &height ); + cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenYScale; + + // Draw the meter + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y - cap_size, width, cap_size, + 0, 1, 1, 0, shader ); + trap_R_DrawStretchPic( x, y - height - cap_size * 2, width, + cap_size, 0, 0, 1, 1, shader ); + trap_R_DrawStretchPic( x, y - height - cap_size, width, height, + 0, 1, 1, 1, shader ); + trap_R_SetColor( NULL ); + } +} + +static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, + const char *s, float fraction ) +{ + vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + float tx, ty; + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + if( fraction < 1.0f ) - CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, white, + UI_Text_Paint( text_x + tx, text_y + ty, scale, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); else - CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, color, + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, ITEM_TEXTSTYLE_NEON ); } static void CG_DrawMediaProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) + int align, int textalign, int textStyle, + float borderSize ) { - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.mediaFraction ); + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.mediaFraction ); } static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align ) + vec4_t color, float scale, int textalign, int textvalign ) { - CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Map and Textures", cg.mediaFraction ); + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Map and Textures", cg.mediaFraction ); } -static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) +static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, + float scale, int align, int textalign, + int textStyle, float borderSize ) { - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.buildablesFraction ); + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.buildablesFraction ); } static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align ) + vec4_t color, float scale, int textalign, int textvalign ) { - CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Buildable Models", cg.buildablesFraction ); + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Buildable Models", cg.buildablesFraction ); } -static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) +static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, + float scale, int align, int textalign, + int textStyle, float borderSize ) { - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.charModelFraction ); + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.charModelFraction ); } static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align ) + vec4_t color, float scale, int textalign, int textvalign ) { - CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Character Models", cg.charModelFraction ); + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Character Models", cg.charModelFraction ); } static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale, - int align, int textStyle, int special ) + int align, int textalign, int textStyle, + float borderSize ) { float total; - total = ( cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction ) / 3.0f; - CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total ); + total = cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction; + total /= 3.0f; + + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, total ); } static void CG_DrawLevelShot( rectDef_t *rect ) @@ -1258,97 +1167,44 @@ static void CG_DrawLevelShot( rectDef_t *rect ) CG_DrawPic( rect->x, rect->y, rect->w, rect->h, detail ); } -static void CG_DrawLoadingString( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle, const char *s ) -{ - float tw, th, tx; - int pos, i; - char buffer[ 1024 ]; - char *end; - - if( !s[ 0 ] ) - return; - - strcpy( buffer, s ); - tw = CG_Text_Width( s, scale, 0 ); - th = scale * 40.0f; - - pos = i = 0; - - while( pos < strlen( s ) ) - { - strcpy( buffer, &s[ pos ] ); - tw = CG_Text_Width( buffer, scale, 0 ); - - while( tw > rect->w ) - { - end = strrchr( buffer, ' ' ); - - if( end == NULL ) - break; - - *end = '\0'; - tw = CG_Text_Width( buffer, scale, 0 ); - } - - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - tw; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f ); - break; - - default: - tx = 0.0f; - } - - CG_Text_Paint( tx + text_x, rect->y + text_y + i * ( th + 3 ), scale, color, - buffer, 0, 0, textStyle ); - - pos += strlen( buffer ) + 1; - i++; - } -} - static void CG_DrawLevelName( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) { const char *s; s = CG_ConfigString( CS_MESSAGE ); - CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, s ); } static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) { const char *s; + char parsed[ MAX_STRING_CHARS ]; s = CG_ConfigString( CS_MOTD ); - CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s ); + Q_ParseNewlines( parsed, s, sizeof( parsed ) ); + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, parsed ); } static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) { char buffer[ 1024 ]; const char *info; info = CG_ConfigString( CS_SERVERINFO ); - Q_strncpyz( buffer, Info_ValueForKey( info, "sv_hostname" ), 1024 ); + UI_EscapeEmoticons( buffer, Info_ValueForKey( info, "sv_hostname" ), sizeof( buffer ) ); Q_CleanStr( buffer ); - CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, buffer ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, buffer ); } /* @@ -1409,30 +1265,29 @@ Draw all the status / pacifier stuff during level loading */ void CG_DrawLoadingScreen( void ) { - Menu_Paint( Menus_FindByName( "Loading" ), qtrue ); + menuDef_t *menu = Menus_FindByName( "Loading" ); + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); } float CG_GetValue( int ownerDraw ) { - centity_t *cent; playerState_t *ps; + weapon_t weapon; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; + weapon = BG_GetPlayerWeapon( ps ); switch( ownerDraw ) { case CG_PLAYER_AMMO_VALUE: - if( cent->currentState.weapon ) - { + if( weapon ) return ps->ammo; - } break; case CG_PLAYER_CLIPS_VALUE: - if( cent->currentState.weapon ) - { + if( weapon ) return ps->clips; - } break; case CG_PLAYER_HEALTH: return ps->stats[ STAT_HEALTH ]; @@ -1461,165 +1316,131 @@ static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color, if( cg.killerName[ 0 ] ) { int x = rect->x + rect->w / 2; - CG_Text_Paint( x - CG_Text_Width( CG_GetKillerText( ), scale, 0 ) / 2, + UI_Text_Paint( x - UI_Text_Width( CG_GetKillerText( ), scale ) / 2, rect->y + rect->h, scale, color, CG_GetKillerText( ), 0, 0, textStyle ); } } -static void CG_Text_Paint_Limit( float *maxX, float x, float y, float scale, - vec4_t color, const char* text, float adjust, int limit ) -{ - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; +#define SPECTATORS_PIXELS_PER_SECOND 30.0f - if( text ) - { -// TTimo: FIXME -// const unsigned char *s = text; // bk001206 - unsigned - const char *s = text; - float max = *maxX; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; - - if( scale <= cg_smallFont.value ) - font = &cgDC.Assets.smallFont; - else if( scale > cg_bigFont.value ) - font = &cgDC.Assets.bigFont; +/* +================== +CG_DrawTeamSpectators +================== +*/ +static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, int textvalign, vec4_t color, qhandle_t shader ) +{ + float y; + char *text = cg.spectatorList; + float textWidth = UI_Text_Width( text, scale ); - useScale = scale * font->glyphScale; - trap_R_SetColor( color ); - len = strlen( text ); + CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_LEFT, textvalign, NULL, &y ); - if( limit > 0 && len > limit ) - len = limit; + if( textWidth > rect->w ) + { + // The text is too wide to fit, so scroll it + int now = trap_Milliseconds( ); + int delta = now - cg.spectatorTime; - count = 0; + CG_SetClipRegion( rect->x, rect->y, rect->w, rect->h ); - while( s && *s && count < len ) - { - glyph = &font->glyphs[ (int)*s ]; - //TTimo: FIXME: getting nasty warnings without the cast, - //hopefully this doesn't break the VM build + UI_Text_Paint( rect->x - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 ); + UI_Text_Paint( rect->x + textWidth - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 ); - if( Q_IsColorString( s ) ) - { - memcpy( newColor, g_color_table[ ColorIndex( *(s+1) ) ], sizeof( newColor ) ); - newColor[ 3 ] = color[ 3 ]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } - else - { - float yadj = useScale * glyph->top; + CG_ClearClipRegion( ); - if( CG_Text_Width( s, useScale, 1 ) + x > max ) - { - *maxX = 0; - break; - } + cg.spectatorOffset += ( delta / 1000.0f ) * SPECTATORS_PIXELS_PER_SECOND; - CG_Text_PaintChar( x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - x += ( glyph->xSkip * useScale ) + adjust; - *maxX = x; - count++; - s++; - } - } + while( cg.spectatorOffset > textWidth ) + cg.spectatorOffset -= textWidth; - trap_R_SetColor( NULL ); + cg.spectatorTime = now; + } + else + { + UI_Text_Paint( rect->x, y, scale, color, text, 0, 0, 0 ); } } -static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) +#define FOLLOWING_STRING "following " +#define CHASING_STRING "chasing " + +/* +================== +CG_DrawFollow +================== +*/ +static void CG_DrawFollow( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) { - if( cg.spectatorLen ) - { - float maxX; + float tx, ty; - if( cg.spectatorWidth == -1 ) - { - cg.spectatorWidth = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } + if( cg.snap && cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + char buffer[ MAX_STRING_CHARS ]; - if( cg.spectatorOffset > cg.spectatorLen ) - { - cg.spectatorOffset = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } + if( !cg.chaseFollow ) + strcpy( buffer, FOLLOWING_STRING ); + else + strcpy( buffer, CHASING_STRING ); - if( cg.time > cg.spectatorTime ) - { - cg.spectatorTime = cg.time + 10; + strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name ); - if( cg.spectatorPaintX <= rect->x + 2 ) - { - if( cg.spectatorOffset < cg.spectatorLen ) - { - //TA: skip colour directives - if( Q_IsColorString( &cg.spectatorList[ cg.spectatorOffset ] ) ) - cg.spectatorOffset += 2; - else - { - cg.spectatorPaintX += CG_Text_Width( &cg.spectatorList[ cg.spectatorOffset ], scale, 1 ) - 1; - cg.spectatorOffset++; - } - } - else - { - cg.spectatorOffset = 0; + CG_AlignText( rect, buffer, scale, 0, 0, textalign, textvalign, &tx, &ty ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, buffer, 0, 0, + textStyle ); + } +} - if( cg.spectatorPaintX2 >= 0 ) - cg.spectatorPaintX = cg.spectatorPaintX2; - else - cg.spectatorPaintX = rect->x + rect->w - 2; +/* +================== +CG_DrawTeamLabel +================== +*/ +static void CG_DrawTeamLabel( rectDef_t *rect, team_t team, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) +{ + char *t; + char stage[ MAX_TOKEN_CHARS ]; + const char *s; + float tx, ty; - cg.spectatorPaintX2 = -1; - } - } - else - { - cg.spectatorPaintX--; + stage[ 0 ] = '\0'; - if( cg.spectatorPaintX2 >= 0 ) - cg.spectatorPaintX2--; - } - } + switch( team ) + { + case TEAM_ALIENS: + t = "Aliens"; + if( cg.intermissionStarted ) + Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.alienStage + 1 ); + break; - maxX = rect->x + rect->w - 2; + case TEAM_HUMANS: + t = "Humans"; + if( cg.intermissionStarted ) + Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.humanStage + 1 ); + break; - CG_Text_Paint_Limit( &maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, - &cg.spectatorList[ cg.spectatorOffset ], 0, 0 ); + default: + t = ""; + break; + } - if( cg.spectatorPaintX2 >= 0 ) - { - float maxX2 = rect->x + rect->w - 2; - CG_Text_Paint_Limit( &maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, - color, cg.spectatorList, 0, cg.spectatorOffset ); - } + switch( textalign ) + { + default: + case ALIGN_LEFT: + s = va( "%s %s", t, stage ); + break; - if( cg.spectatorOffset && maxX > 0 ) - { - // if we have an offset ( we are skipping the first part of the string ) and we fit the string - if( cg.spectatorPaintX2 == -1 ) - cg.spectatorPaintX2 = rect->x + rect->w - 2; - } - else - cg.spectatorPaintX2 = -1; + case ALIGN_RIGHT: + s = va( "%s %s", stage, t ); + break; } + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); } /* @@ -1628,70 +1449,52 @@ CG_DrawStageReport ================== */ static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y, - vec4_t color, float scale, int align, int textStyle ) + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) { char s[ MAX_TOKEN_CHARS ]; - int tx, w, kills; + float tx, ty; - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_NONE && !cg.intermissionStarted ) + if( cg.intermissionStarted ) return; - if( cg.intermissionStarted ) - { - Com_sprintf( s, MAX_TOKEN_CHARS, - "Stage %d" //PH34R MY MAD-LEET CODING SKILLZ - " " - "Stage %d", - cgs.alienStage + 1, cgs.humanStage + 1 ); - } - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + return; + + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - kills = cgs.alienNextStageThreshold - cgs.alienKills; + int kills = ceil( (float)(cgs.alienNextStageThreshold - cgs.alienCredits) / ALIEN_CREDITS_PER_KILL ); + if( kills < 0 ) + kills = 0; if( cgs.alienNextStageThreshold < 0 ) Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.alienStage + 1 ); else if( kills == 1 ) - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage", - cgs.alienStage + 1, kills ); + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 frag for next stage", + cgs.alienStage + 1 ); else - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage", + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d frags for next stage", cgs.alienStage + 1, kills ); } - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - kills = cgs.humanNextStageThreshold - cgs.humanKills; + int credits = cgs.humanNextStageThreshold - cgs.humanCredits; + + if( credits < 0 ) + credits = 0; if( cgs.humanNextStageThreshold < 0 ) Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.humanStage + 1 ); - else if( kills == 1 ) - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage", - cgs.humanStage + 1, kills ); + else if( credits == 1 ) + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 credit for next stage", + cgs.humanStage + 1 ); else - Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage", - cgs.humanStage + 1, kills ); + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d credits for next stage", + cgs.humanStage + 1, credits ); } - w = CG_Text_Width( s, scale, 0 ); - - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - w; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f ); - break; - - default: - tx = 0.0f; - } + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); - CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); } /* @@ -1699,15 +1502,17 @@ static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y, CG_DrawFPS ================== */ -//TA: personally i think this should be longer - it should really be a cvar #define FPS_FRAMES 20 #define FPS_STRING "fps" static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle, qboolean scalableText ) { - char *s; - int tx, w, totalWidth, strLength; + const char *s; + float tx, ty; + float w, h, totalWidth; + int strLength; static int previousTimes[ FPS_FRAMES ]; static int index; int i, total; @@ -1741,27 +1546,12 @@ static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, fps = 1000 * FPS_FRAMES / total; s = va( "%d", fps ); - w = CG_Text_Width( "0", scale, 0 ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); strLength = CG_DrawStrlen( s ); - totalWidth = CG_Text_Width( FPS_STRING, scale, 0 ) + w * strLength; - - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - totalWidth; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f ); - break; + totalWidth = UI_Text_Width( FPS_STRING, scale ) + w * strLength; - default: - tx = 0.0f; - } + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); if( scalableText ) { @@ -1772,8 +1562,10 @@ static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, c[ 0 ] = s[ i ]; c[ 1 ] = '\0'; - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle ); + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); } + + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, FPS_STRING, 0, 0, textStyle ); } else { @@ -1781,9 +1573,6 @@ static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, fps ); trap_R_SetColor( NULL ); } - - if( scalableText ) - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, FPS_STRING, 0, 0, textStyle ); } } @@ -1844,10 +1633,13 @@ CG_DrawTimer ================= */ static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle ) + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) { - char *s; - int i, tx, w, totalWidth, strLength; + const char *s; + float tx, ty; + int i, strLength; + float w, h, totalWidth; int mins, seconds, tens; int msec; @@ -1863,36 +1655,255 @@ static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, seconds -= tens * 10; s = va( "%d:%d%d", mins, tens, seconds ); - w = CG_Text_Width( "0", scale, 0 ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); strLength = CG_DrawStrlen( s ); totalWidth = w * strLength; - switch( align ) + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); + + for( i = 0; i < strLength; i++ ) { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; + char c[ 2 ]; - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - totalWidth; - break; + c[ 0 ] = s[ i ]; + c[ 1 ] = '\0'; - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f ); - break; + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); + } +} - default: - tx = 0.0f; +/* +================= +CG_DrawTeamOverlay +================= +*/ + +typedef enum +{ + TEAMOVERLAY_OFF, + TEAMOVERLAY_ALL, + TEAMOVERLAY_SUPPORT, + TEAMOVERLAY_NEARBY, +} teamOverlayMode_t; + +typedef enum +{ + TEAMOVERLAY_SORT_NONE, + TEAMOVERLAY_SORT_SCORE, + TEAMOVERLAY_SORT_WEAPONCLASS, +} teamOverlaySort_t; + +static int QDECL SortScore( const void *a, const void *b ) +{ + int na = *(int *)a; + int nb = *(int *)b; + + return( cgs.clientinfo[ nb ].score - cgs.clientinfo[ na ].score ); +} + +static int QDECL SortWeaponClass( const void *a, const void *b ) +{ + int out; + clientInfo_t *ca = cgs.clientinfo + *(int *)a; + clientInfo_t *cb = cgs.clientinfo + *(int *)b; + + out = cb->curWeaponClass - ca->curWeaponClass; + + // We want grangers on top. ckits are already on top without the special case. + if( ca->team == TEAM_ALIENS ) + { + if( ca->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + cb->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + ca->curWeaponClass == PCL_ALIEN_BUILDER0 || + cb->curWeaponClass == PCL_ALIEN_BUILDER0 ) + { + out = -out; + } } - for( i = 0; i < strLength; i++ ) + return( out ); +} + +static void CG_DrawTeamOverlay( rectDef_t *rect, float scale, vec4_t color ) +{ + const char *s; + int i; + float x = rect->x; + float y; + clientInfo_t *ci, *pci; + vec4_t tcolor; + float iconSize = rect->h / 8.0f; + float leftMargin = 4.0f; + float iconTopMargin = 2.0f; + float midSep = 2.0f; + float backgroundWidth = rect->w; + float fontScale = 0.30f; + float vPad = 0.0f; + float nameWidth = 0.5f * rect->w; + char name[ MAX_NAME_LENGTH + 2 ]; + int maxDisplayCount = 0; + int displayCount = 0; + float nameMaxX, nameMaxXCp; + float maxX = rect->x + rect->w; + float maxXCp = maxX; + weapon_t curWeapon = WP_NONE; + teamOverlayMode_t mode = cg_drawTeamOverlay.integer; + teamOverlaySort_t sort = cg_teamOverlaySortMode.integer; + int displayClients[ MAX_CLIENTS ]; + + if( cg.predictedPlayerState.pm_type == PM_SPECTATOR ) + return; + + if( mode == TEAMOVERLAY_OFF || !cg_teamOverlayMaxPlayers.integer ) + return; + + if( !cgs.teaminfoReceievedTime ) + return; + + if( cg.showScores || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) + return; + + pci = cgs.clientinfo + cg.snap->ps.clientNum; + + if( mode == TEAMOVERLAY_ALL || mode == TEAMOVERLAY_SUPPORT ) { - char c[ 2 ]; + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = cgs.clientinfo + i; + if( ci->infoValid && pci != ci && ci->team == pci->team ) + { + if( mode == TEAMOVERLAY_ALL ) + displayClients[ maxDisplayCount++ ] = i; + else + { + if( ci->curWeaponClass == PCL_ALIEN_BUILDER0 || + ci->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + ci->curWeaponClass == PCL_ALIEN_LEVEL1 || + ci->curWeaponClass == PCL_ALIEN_LEVEL1_UPG || + ci->curWeaponClass == WP_HBUILD ) + { + displayClients[ maxDisplayCount++ ] = i; + } + } + } + } + } + else // find nearby + { + for( i = 0; i < cg.snap->numEntities; i++ ) + { + centity_t *cent = &cg_entities[ cg.snap->entities[ i ].number ]; + vec3_t relOrigin = { 0.0f, 0.0f, 0.0f }; + int team = cent->currentState.misc & 0x00FF; - c[ 0 ] = s[ i ]; - c[ 1 ] = '\0'; + if( cent->currentState.eType != ET_PLAYER || + team != pci->team || + cent->currentState.eFlags & EF_DEAD ) + { + continue; + } + + VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE ) + displayClients[ maxDisplayCount++ ] = cg.snap->entities[ i ].number; + } + } + + // Sort + if( sort == TEAMOVERLAY_SORT_SCORE ) + { + qsort( displayClients, maxDisplayCount, + sizeof( displayClients[ 0 ] ), SortScore ); + } + else if( sort == TEAMOVERLAY_SORT_WEAPONCLASS ) + { + qsort( displayClients, maxDisplayCount, + sizeof( displayClients[ 0 ] ), SortWeaponClass ); + } + + if( maxDisplayCount > cg_teamOverlayMaxPlayers.integer ) + maxDisplayCount = cg_teamOverlayMaxPlayers.integer; + + iconSize *= scale; + leftMargin *= scale; + iconTopMargin *= scale; + midSep *= scale; + backgroundWidth *= scale; + fontScale *= scale; + nameWidth *= scale; + + vPad = ( rect->h - ( (float) maxDisplayCount * iconSize ) ) / 2.0f; + y = rect->y + vPad; + + tcolor[ 0 ] = 1.0f; + tcolor[ 1 ] = 1.0f; + tcolor[ 2 ] = 1.0f; + tcolor[ 3 ] = color[ 3 ]; - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle ); + for( i = 0; i < MAX_CLIENTS && displayCount < maxDisplayCount; i++ ) + { + ci = cgs.clientinfo + displayClients[ i ]; + + if( !ci->infoValid || pci == ci || ci->team != pci->team ) + continue; + + Com_sprintf( name, sizeof( name ), "%s^7", ci->name ); + + trap_R_SetColor( color ); + CG_DrawPic( x, y, backgroundWidth, + iconSize, cgs.media.teamOverlayShader ); + trap_R_SetColor( tcolor ); + if( ci->health <= 0 || !ci->curWeaponClass ) + s = ""; + else + { + if( ci->team == TEAM_HUMANS ) + curWeapon = ci->curWeaponClass; + else if( ci->team == TEAM_ALIENS ) + curWeapon = BG_Class( ci->curWeaponClass )->startWeapon; + + CG_DrawPic( x + leftMargin, y, iconSize, iconSize, + cg_weapons[ curWeapon ].weaponIcon ); + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( ci->upgrade != UP_NONE ) + { + CG_DrawPic( x + iconSize + leftMargin, y, iconSize, + iconSize, cg_upgrades[ ci->upgrade ].upgradeIcon ); + } + } + else + { + if( curWeapon == WP_ABUILD2 || curWeapon == WP_ALEVEL1_UPG || + curWeapon == WP_ALEVEL2_UPG || curWeapon == WP_ALEVEL3_UPG ) + { + CG_DrawPic( x + iconSize + leftMargin, y, iconSize, + iconSize, cgs.media.upgradeClassIconShader ); + } + } + + s = va( " [^%c%3d^7] ^7%s", + CG_GetColorCharForHealth( displayClients[ i ] ), + ci->health, + CG_ConfigString( CS_LOCATIONS + ci->location ) ); + } + + trap_R_SetColor( NULL ); + nameMaxX = nameMaxXCp = x + 2.0f * iconSize + + leftMargin + midSep + nameWidth; + UI_Text_Paint_Limit( &nameMaxXCp, x + 2.0f * iconSize + leftMargin + midSep, + y + iconSize - iconTopMargin, fontScale, tcolor, name, + 0, 0 ); + + maxXCp = maxX; + + UI_Text_Paint_Limit( &maxXCp, nameMaxX, y + iconSize - iconTopMargin, + fontScale, tcolor, s, 0, 0 ); + y += iconSize; + displayCount++; } } @@ -1902,17 +1913,19 @@ CG_DrawClock ================= */ static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle ) + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) { - char *s; - int i, tx, w, totalWidth, strLength; + const char *s; + float tx, ty; + int i, strLength; + float w, h, totalWidth; qtime_t qt; - int t; if( !cg_drawClock.integer ) return; - t = trap_RealTime( &qt ); + trap_RealTime( &qt ); if( cg_drawClock.integer == 2 ) { @@ -1936,27 +1949,12 @@ static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm ); } - w = CG_Text_Width( "0", scale, 0 ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); strLength = CG_DrawStrlen( s ); totalWidth = w * strLength; - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; - - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - totalWidth; - break; - - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f ); - break; - - default: - tx = 0.0f; - } + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); for( i = 0; i < strLength; i++ ) { @@ -1965,7 +1963,7 @@ static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, c[ 0 ] = s[ i ]; c[ 1 ] = '\0'; - CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle ); + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); } } @@ -1975,39 +1973,90 @@ CG_DrawSnapshot ================== */ static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int align, int textStyle ) + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) { - char *s; - int w, tx; + const char *s; + float tx, ty; if( !cg_drawSnapshot.integer ) return; s = va( "time:%d snap:%d cmd:%d", cg.snap->serverTime, cg.latestSnapshotNum, cgs.serverCommandSequence ); - w = CG_Text_Width( s, scale, 0 ); - switch( align ) - { - case ITEM_ALIGN_LEFT: - tx = rect->x; - break; + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); - case ITEM_ALIGN_RIGHT: - tx = rect->x + rect->w - w; - break; + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); +} - case ITEM_ALIGN_CENTER: - tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f ); - break; +/* +=============================================================================== - default: - tx = 0.0f; - } +KILLL MESSAGE + +=============================================================================== +*/ - CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle ); +/* +================== +CG_DrawKillMsg +================== +*/ +static void CG_DrawKillMsg( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) +{ + int i; + vec4_t hcolor; + int chatHeight; + + if (cg_killMsgHeight.integer < TEAMCHAT_HEIGHT) + chatHeight = cg_killMsgHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + + if (chatHeight <= 0) + return; // disabled + + if (cgs.killMsgLastPos != cgs.killMsgPos) + { + if (cg.time - cgs.killMsgMsgTimes[cgs.killMsgLastPos % chatHeight] > cg_killMsgTime.integer) + cgs.killMsgLastPos++; + + hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0f; + + for ( i = cgs.killMsgPos - 1; i >= cgs.killMsgLastPos; i-- ) + { + int x = 0, w; + int j = i % chatHeight; + + w = UI_Text_Width( cgs.killMsgKillers[j], scale ); + UI_Text_Paint( rect->x + TINYCHAR_WIDTH, + rect->y - (cgs.killMsgPos - i)*20, + scale, color, cgs.killMsgKillers[j], + 0, 0, textStyle ); + x += w + 3; + + if ( cg_weapons[cgs.killMsgWeapons[j]].weaponIcon != WP_NONE ) + { + CG_DrawPic( rect->x + TINYCHAR_WIDTH + x, + rect->y - (cgs.killMsgPos - i)*20-15, + 16, 16, + cg_weapons[cgs.killMsgWeapons[j]].weaponIcon ); + x += 16 + 2; + + w = UI_Text_Width( cgs.killMsgVictims[j], scale ); + UI_Text_Paint( rect->x + TINYCHAR_WIDTH + x, + rect->y - (cgs.killMsgPos - i)*20, + scale, color, cgs.killMsgVictims[j], + 0, 0, textStyle ); + } + } + } } + /* =============================================================================== @@ -2080,7 +2129,7 @@ void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { previousPings[ index++ ] = cg.snap->ping; index = index % PING_FRAMES; - + for( i = 0; i < PING_FRAMES; i++ ) { cg.ping += previousPings[ i ]; @@ -2116,8 +2165,8 @@ static void CG_DrawDisconnect( void ) // also add text in center of screen s = "Connection Interrupted"; - w = CG_Text_Width( s, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + w = UI_Text_Width( s, 0.7f ); + UI_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); // blink the icon if( ( cg.time >> 9 ) & 1 ) @@ -2147,7 +2196,7 @@ static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y, int color; vec4_t adjustedColor; float vscale; - vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + const char *ping; if( cg.snap->ps.pm_type == PM_INTERMISSION ) return; @@ -2248,103 +2297,216 @@ static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y, } } - v = v * vscale; + v = v * vscale; + + if( v > range ) + v = range; + + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + else if( v < 0 ) + { + if( color != 4 ) + { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] ); + } + + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if( cg_nopredict.integer || cg_synchronousClients.integer ) + ping = "snc"; + else + ping = va( "%d", cg.ping ); + ax = rect->x + ( rect->w / 2.0f ) - + ( UI_Text_Width( ping, scale ) / 2.0f ) + text_x; + ay = rect->y + ( rect->h / 2.0f ) + + ( UI_Text_Height( ping, scale ) / 2.0f ) + text_y; + + Vector4Copy( textColor, adjustedColor ); + adjustedColor[ 3 ] = 0.5f; + UI_Text_Paint( ax, ay, scale, adjustedColor, ping, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); + + CG_DrawDisconnect( ); +} + +#define SPEEDOMETER_NUM_SAMPLES 4096 +#define SPEEDOMETER_NUM_DISPLAYED_SAMPLES 160 +#define SPEEDOMETER_DRAW_TEXT 0x1 +#define SPEEDOMETER_DRAW_GRAPH 0x2 +#define SPEEDOMETER_IGNORE_Z 0x4 +float speedSamples[ SPEEDOMETER_NUM_SAMPLES ]; +int speedSampleTimes[ SPEEDOMETER_NUM_SAMPLES ]; +// array indices +int oldestSpeedSample = 0; +int maxSpeedSample = 0; +int maxSpeedSampleInWindow = 0; + +/* +=================== +CG_AddSpeed + +append a speed to the sample history +=================== +*/ +void CG_AddSpeed( void ) +{ + float speed; + vec3_t vel; + int windowTime; + qboolean newSpeedGteMaxSpeed, newSpeedGteMaxSpeedInWindow; + + VectorCopy( cg.snap->ps.velocity, vel ); + + if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z ) + vel[ 2 ] = 0; + + speed = VectorLength( vel ); + + windowTime = cg_maxSpeedTimeWindow.integer; + if( windowTime < 0 ) + windowTime = 0; + else if( windowTime > SPEEDOMETER_NUM_SAMPLES * 1000 ) + windowTime = SPEEDOMETER_NUM_SAMPLES * 1000; + + if( ( newSpeedGteMaxSpeed = ( speed >= speedSamples[ maxSpeedSample ] ) ) ) + maxSpeedSample = oldestSpeedSample; - if( v > range ) - v = range; + if( ( newSpeedGteMaxSpeedInWindow = ( speed >= speedSamples[ maxSpeedSampleInWindow ] ) ) ) + maxSpeedSampleInWindow = oldestSpeedSample; - trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } - else if( v < 0 ) - { - if( color != 4 ) - { - color = 4; // RED for dropped snapshots - trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] ); - } + speedSamples[ oldestSpeedSample ] = speed; + speedSampleTimes[ oldestSpeedSample ] = cg.time; - trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + if( !newSpeedGteMaxSpeed && maxSpeedSample == oldestSpeedSample ) + { + // if old max was overwritten find a new one + int i; + for( maxSpeedSample = 0, i = 1; i < SPEEDOMETER_NUM_SAMPLES; i++ ) + { + if( speedSamples[ i ] > speedSamples[ maxSpeedSample ] ) + maxSpeedSample = i; } } - trap_R_SetColor( NULL ); - - if( cg_nopredict.integer || cg_synchronousClients.integer ) - CG_Text_Paint( ax, ay, 0.5, white, "snc", 0, 0, ITEM_TEXTSTYLE_NORMAL ); - else + if( !newSpeedGteMaxSpeedInWindow && ( maxSpeedSampleInWindow == oldestSpeedSample || + cg.time - speedSampleTimes[ maxSpeedSampleInWindow ] > windowTime ) ) { - char *s; - - s = va( "%d", cg.ping ); - ax = rect->x + ( rect->w / 2.0f ) - ( CG_Text_Width( s, scale, 0 ) / 2.0f ) + text_x; - ay = rect->y + ( rect->h / 2.0f ) + ( CG_Text_Height( s, scale, 0 ) / 2.0f ) + text_y; - - Vector4Copy( textColor, adjustedColor ); - adjustedColor[ 3 ] = 0.5f; - CG_Text_Paint( ax, ay, scale, adjustedColor, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + int i; + do { + maxSpeedSampleInWindow = ( maxSpeedSampleInWindow + 1 ) % SPEEDOMETER_NUM_SAMPLES; + } while( cg.time - speedSampleTimes[ maxSpeedSampleInWindow ] > windowTime ); + for( i = maxSpeedSampleInWindow; ; i = ( i + 1 ) % SPEEDOMETER_NUM_SAMPLES ) + { + if( speedSamples[ i ] > speedSamples[ maxSpeedSampleInWindow ] ) + maxSpeedSampleInWindow = i; + if( i == oldestSpeedSample ) + break; + } } - CG_DrawDisconnect( ); + oldestSpeedSample = ( oldestSpeedSample + 1 ) % SPEEDOMETER_NUM_SAMPLES; } +#define SPEEDOMETER_MIN_RANGE 900 +#define SPEED_MED 1000.f +#define SPEED_FAST 1600.f + /* -============== -CG_DrawTextBlock -============== +=================== +CG_DrawSpeedGraph +=================== */ -static void CG_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle, const char *text, - menuDef_t *parent, itemDef_t *textItem ) +static void CG_DrawSpeedGraph( rectDef_t *rect, vec4_t foreColor, + vec4_t backColor ) { - float x, y, w, h; - - //offset the text - x = rect->x; - y = rect->y; - w = rect->w - ( 16 + ( 2 * text_x ) ); //16 to ensure text within frame - h = rect->h; - - textItem->text = text; + int i; + float val, max, top; + // colour of graph is interpolated between these values + const vec3_t slow = { 0.0, 0.0, 1.0 }; + const vec3_t medium = { 0.0, 1.0, 0.0 }; + const vec3_t fast = { 1.0, 0.0, 0.0 }; + vec4_t color; + + max = speedSamples[ maxSpeedSample ]; + if( max < SPEEDOMETER_MIN_RANGE ) + max = SPEEDOMETER_MIN_RANGE; + + trap_R_SetColor( backColor ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader ); - textItem->parent = parent; - memcpy( textItem->window.foreColor, color, sizeof( vec4_t ) ); - textItem->window.flags = 0; + Vector4Copy( foreColor, color ); - switch( align ) + for( i = 1; i < SPEEDOMETER_NUM_DISPLAYED_SAMPLES; i++ ) { - case ITEM_ALIGN_LEFT: - textItem->window.rect.x = x; - break; - - case ITEM_ALIGN_RIGHT: - textItem->window.rect.x = x + w; - break; + val = speedSamples[ ( oldestSpeedSample + i + SPEEDOMETER_NUM_SAMPLES - + SPEEDOMETER_NUM_DISPLAYED_SAMPLES ) % SPEEDOMETER_NUM_SAMPLES ]; + if( val < SPEED_MED ) + VectorLerp2( val / SPEED_MED, slow, medium, color ); + else if( val < SPEED_FAST ) + VectorLerp2( ( val - SPEED_MED ) / ( SPEED_FAST - SPEED_MED ), + medium, fast, color ); + else + VectorCopy( fast, color ); + trap_R_SetColor( color ); + top = rect->y + ( 1 - val / max ) * rect->h; + CG_DrawPic( rect->x + ( i / (float)SPEEDOMETER_NUM_DISPLAYED_SAMPLES ) * rect->w, top, + rect->w / (float)SPEEDOMETER_NUM_DISPLAYED_SAMPLES, val * rect->h / max, + cgs.media.whiteShader ); + } + trap_R_SetColor( NULL ); +} - case ITEM_ALIGN_CENTER: - textItem->window.rect.x = x + ( w / 2 ); - break; +/* +=================== +CG_DrawSpeedText +=================== +*/ +static void CG_DrawSpeedText( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t foreColor ) +{ + char speedstr[ 16 ]; + float val; + vec4_t color; - default: - textItem->window.rect.x = x; - break; + VectorCopy( foreColor, color ); + color[ 3 ] = 1; + if( cg.predictedPlayerState.clientNum == cg.clientNum ) + { + vec3_t vel; + VectorCopy( cg.predictedPlayerState.velocity, vel ); + if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z ) + vel[ 2 ] = 0; + val = VectorLength( vel ); } + else + val = speedSamples[ ( oldestSpeedSample - 1 + SPEEDOMETER_NUM_SAMPLES ) % SPEEDOMETER_NUM_SAMPLES ]; - textItem->window.rect.y = y; - textItem->window.rect.w = w; - textItem->window.rect.h = h; - textItem->window.borderSize = 0; - textItem->textRect.x = 0; - textItem->textRect.y = 0; - textItem->textRect.w = 0; - textItem->textRect.h = 0; - textItem->textalignment = align; - textItem->textalignx = text_x; - textItem->textaligny = text_y; - textItem->textscale = scale; - textItem->textStyle = textStyle; + Com_sprintf( speedstr, sizeof( speedstr ), "%d / %d", (int)val, (int)speedSamples[ maxSpeedSampleInWindow ] ); - //hack to utilise existing autowrap code - Item_Text_AutoWrapped_Paint( textItem ); + UI_Text_Paint( + rect->x + ( rect->w - UI_Text_Width( speedstr, scale ) ) / 2.0f, + rect->y + ( rect->h + UI_Text_Height( speedstr, scale ) ) / 2.0f, + scale, color, speedstr, 0, 0, ITEM_TEXTSTYLE_NORMAL ); +} + +/* +=================== +CG_DrawSpeed +=================== +*/ +static void CG_DrawSpeed( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t foreColor, vec4_t backColor ) +{ + if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_GRAPH ) + CG_DrawSpeedGraph( rect, foreColor, backColor ); + if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_TEXT ) + CG_DrawSpeedText( rect, text_x, text_y, scale, foreColor ); } /* @@ -2353,13 +2515,9 @@ CG_DrawConsole =================== */ static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle ) + float scale, int textalign, int textvalign, int textStyle ) { - static menuDef_t dummyParent; - static itemDef_t textItem; - - CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle, - cg.consoleText, &dummyParent, &textItem ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, cg.consoleText ); } /* @@ -2368,16 +2526,12 @@ CG_DrawTutorial =================== */ static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color, - float scale, int align, int textStyle ) + float scale, int textalign, int textvalign, int textStyle ) { - static menuDef_t dummyParent; - static itemDef_t textItem; - if( !cg_tutorial.integer ) return; - CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle, - CG_TutorialText( ), &dummyParent, &textItem ); + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, CG_TutorialText( ) ); } /* @@ -2387,29 +2541,34 @@ CG_DrawWeaponIcon */ void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) { - int ammo, clips, maxAmmo; - centity_t *cent; + int maxAmmo; playerState_t *ps; + weapon_t weapon; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; + weapon = BG_GetPlayerWeapon( ps ); - ammo = ps->ammo; - clips = ps->clips; - BG_FindAmmoForWeapon( cent->currentState.weapon, &maxAmmo, NULL ); + maxAmmo = BG_Weapon( weapon )->maxAmmo; // don't display if dead if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) return; - if( cent->currentState.weapon == 0 ) + if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) + { return; + } - CG_RegisterWeapon( cent->currentState.weapon ); + if( !cg_weapons[ weapon ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawWeaponIcon: weapon %d (%s) " + "is not registered\n", weapon, BG_Weapon( weapon )->name ); + return; + } - if( clips == 0 && !BG_FindInfinteAmmoForWeapon( cent->currentState.weapon ) ) + if( ps->clips == 0 && !BG_Weapon( weapon )->infiniteAmmo ) { - float ammoPercent = (float)ammo / (float)maxAmmo; + float ammoPercent = (float)ps->ammo / (float)maxAmmo; if( ammoPercent < 0.33f ) { @@ -2418,7 +2577,9 @@ void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) } } - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && CG_AtHighestClass( ) ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS && + !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ], + ps->persistant[ PERS_CREDIT ], cgs.alienStage ) ) { if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) { @@ -2428,7 +2589,8 @@ void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) } trap_R_SetColor( color ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].weaponIcon ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, + cg_weapons[ weapon ].weaponIcon ); trap_R_SetColor( NULL ); } @@ -2443,50 +2605,65 @@ CROSSHAIR */ + /* ================= CG_DrawCrosshair ================= */ -static void CG_DrawCrosshair( void ) +static void CG_DrawCrosshair( rectDef_t *rect, vec4_t color ) { float w, h; qhandle_t hShader; float x, y; weaponInfo_t *wi; + weapon_t weapon; + + weapon = BG_GetPlayerWeapon( &cg.snap->ps ); if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF ) return; if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY && - !BG_FindLongRangedForWeapon( cg.snap->ps.weapon ) ) - { + !BG_Weapon( weapon )->longRanged ) return; - } - if( ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) || - ( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) || - ( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) return; if( cg.renderingThirdPerson ) return; - wi = &cg_weapons[ cg.snap->ps.weapon ]; + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + return; + + wi = &cg_weapons[ weapon ]; - w = h = wi->crossHairSize; + w = h = wi->crossHairSize * cg_crosshairSize.value; + w *= cgDC.aspectScale; - x = cg_crosshairX.integer; - y = cg_crosshairY.integer; - CG_AdjustFrom640( &x, &y, &w, &h ); + //FIXME: this still ignores the width/height of the rect, but at least it's + //neater than cg_crosshairX/cg_crosshairY + x = rect->x + ( rect->w / 2 ) - ( w / 2 ); + y = rect->y + ( rect->h / 2 ) - ( h / 2 ); hShader = wi->crossHair; + //aiming at a friendly player/buildable, dim the crosshair + if( cg.time == cg.crosshairClientTime || cg.crosshairBuildable >= 0 ) + { + int i; + for( i = 0; i < 3; i++ ) + color[i] *= .5f; + + } + if( hShader != 0 ) { - trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * ( cg.refdef.width - w ), - y + cg.refdef.y + 0.5 * ( cg.refdef.height - h ), - w, h, 0, 0, 1, 1, hShader ); + + trap_R_SetColor( color ); + CG_DrawPic( x, y, w, h, hShader ); + trap_R_SetColor( NULL ); } } @@ -2502,7 +2679,7 @@ static void CG_ScanForCrosshairEntity( void ) trace_t trace; vec3_t start, end; int content; - pTeam_t team; + team_t team; VectorCopy( cg.refdef.vieworg, start ); VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end ); @@ -2510,20 +2687,29 @@ static void CG_ScanForCrosshairEntity( void ) CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); - if( trace.entityNum >= MAX_CLIENTS ) - return; - // if the player is in fog, don't show it content = trap_CM_PointContents( trace.endpos, 0 ); if( content & CONTENTS_FOG ) return; + if( trace.entityNum >= MAX_CLIENTS ) + { + entityState_t *s = &cg_entities[ trace.entityNum ].currentState; + if( s->eType == ET_BUILDABLE && BG_Buildable( s->modelindex )->team == + cg.snap->ps.stats[ STAT_TEAM ] ) + cg.crosshairBuildable = trace.entityNum; + else + cg.crosshairBuildable = -1; + + return; + } + team = cgs.clientinfo[ trace.entityNum ].team; - if( cg.snap->ps.persistant[ PERS_TEAM ] != TEAM_SPECTATOR ) + if( cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE ) { //only display team names of those on the same team as this player - if( team != cg.snap->ps.stats[ STAT_PTEAM ] ) + if( team != cg.snap->ps.stats[ STAT_TEAM ] ) return; } @@ -2533,6 +2719,44 @@ static void CG_ScanForCrosshairEntity( void ) } +/* +===================== +CG_DrawLocation +===================== +*/ +static void CG_DrawLocation( rectDef_t *rect, float scale, int textalign, vec4_t color ) +{ + const char *location; + centity_t *locent; + float maxX; + float tx = rect->x, ty = rect->y; + + if( cg.intermissionStarted ) + return; + + maxX = rect->x + rect->w; + + locent = CG_GetPlayerLocation( ); + if( locent ) + location = CG_ConfigString( CS_LOCATIONS + locent->currentState.generic1 ); + else + location = CG_ConfigString( CS_LOCATIONS ); + + // need to skip horiz. align if it's too long, but valign must be run either way + if( UI_Text_Width( location, scale ) < rect->w ) + { + CG_AlignText( rect, location, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint( tx, ty, scale, color, location, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + } + else + { + CG_AlignText( rect, location, scale, 0.0f, 0.0f, ALIGN_NONE, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint_Limit( &maxX, tx, ty, scale, color, location, 0, 0 ); + } + + trap_R_SetColor( NULL ); +} + /* ===================== CG_DrawCrosshairNames @@ -2541,7 +2765,7 @@ CG_DrawCrosshairNames static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) { float *color; - char *name; + const char *name; float w, x; if( !cg_drawCrosshairNames.integer ) @@ -2554,21 +2778,31 @@ static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) CG_ScanForCrosshairEntity( ); // draw the name of the player being looked at - color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + color = CG_FadeColor( cg.crosshairClientTime, CROSSHAIR_CLIENT_TIMEOUT ); if( !color ) { trap_R_SetColor( NULL ); return; } + // add health from overlay info to the crosshair client name name = cgs.clientinfo[ cg.crosshairClientNum ].name; - w = CG_Text_Width( name, scale, 0 ); - x = rect->x + rect->w / 2; - CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + if( cg_teamOverlayUserinfo.integer && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE && + cgs.teaminfoReceievedTime && + cgs.clientinfo[ cg.crosshairClientNum ].health > 0 ) + { + name = va( "%s ^7[^%c%d^7]", name, + CG_GetColorCharForHealth( cg.crosshairClientNum ), + cgs.clientinfo[ cg.crosshairClientNum ].health ); + } + + w = UI_Text_Width( name, scale ); + x = rect->x + rect->w / 2.0f; + UI_Text_Paint( x - w / 2.0f, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); trap_R_SetColor( NULL ); } - /* =============== CG_OwnerDraw @@ -2578,14 +2812,12 @@ Draw an owner drawn item */ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, - int align, float special, float scale, vec4_t color, + int align, int textalign, int textvalign, float borderSize, + float scale, vec4_t foreColor, vec4_t backColor, qhandle_t shader, int textStyle ) { rectDef_t rect; - if( cg_drawStatus.integer == 0 ) - return; - rect.x = x; rect.y = y; rect.w = w; @@ -2594,100 +2826,107 @@ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, switch( ownerDraw ) { case CG_PLAYER_CREDITS_VALUE: - CG_DrawPlayerCreditsValue( &rect, color, qtrue ); + CG_DrawPlayerCreditsValue( &rect, foreColor, qtrue ); break; - case CG_PLAYER_BANK_VALUE: - CG_DrawPlayerBankValue( &rect, color, qtrue ); + case CG_PLAYER_CREDITS_FRACTION: + CG_DrawPlayerCreditsFraction( &rect, foreColor, shader ); break; case CG_PLAYER_CREDITS_VALUE_NOPAD: - CG_DrawPlayerCreditsValue( &rect, color, qfalse ); - break; - case CG_PLAYER_BANK_VALUE_NOPAD: - CG_DrawPlayerBankValue( &rect, color, qfalse ); - break; - case CG_PLAYER_STAMINA: - CG_DrawPlayerStamina( &rect, color, scale, align, textStyle, special ); + CG_DrawPlayerCreditsValue( &rect, foreColor, qfalse ); break; case CG_PLAYER_STAMINA_1: - CG_DrawPlayerStamina1( &rect, color, shader ); - break; case CG_PLAYER_STAMINA_2: - CG_DrawPlayerStamina2( &rect, color, shader ); - break; case CG_PLAYER_STAMINA_3: - CG_DrawPlayerStamina3( &rect, color, shader ); - break; case CG_PLAYER_STAMINA_4: - CG_DrawPlayerStamina4( &rect, color, shader ); + CG_DrawPlayerStamina( ownerDraw, &rect, backColor, foreColor, shader ); break; case CG_PLAYER_STAMINA_BOLT: - CG_DrawPlayerStaminaBolt( &rect, color, shader ); + CG_DrawPlayerStaminaBolt( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_AMMO_VALUE: - CG_DrawPlayerAmmoValue( &rect, color ); + CG_DrawPlayerAmmoValue( &rect, foreColor ); break; case CG_PLAYER_CLIPS_VALUE: - CG_DrawPlayerClipsValue( &rect, color ); + CG_DrawPlayerClipsValue( &rect, foreColor ); break; case CG_PLAYER_BUILD_TIMER: - CG_DrawPlayerBuildTimer( &rect, color ); + CG_DrawPlayerBuildTimer( &rect, foreColor ); break; case CG_PLAYER_HEALTH: - CG_DrawPlayerHealthValue( &rect, color ); - break; - case CG_PLAYER_HEALTH_BAR: - CG_DrawPlayerHealthBar( &rect, color, scale, align, textStyle, special ); + CG_DrawPlayerHealthValue( &rect, foreColor ); break; case CG_PLAYER_HEALTH_CROSS: - CG_DrawPlayerHealthCross( &rect, color, shader ); + CG_DrawPlayerHealthCross( &rect, foreColor ); + break; + case CG_PLAYER_CHARGE_BAR_BG: + CG_DrawPlayerChargeBarBG( &rect, foreColor, shader ); + break; + case CG_PLAYER_CHARGE_BAR: + CG_DrawPlayerChargeBar( &rect, foreColor, shader ); break; case CG_PLAYER_CLIPS_RING: - CG_DrawPlayerClipsRing( &rect, color, shader ); + CG_DrawPlayerClipsRing( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_BUILD_TIMER_RING: - CG_DrawPlayerBuildTimerRing( &rect, color, shader ); + CG_DrawPlayerBuildTimerRing( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_WALLCLIMBING: - CG_DrawPlayerWallclimbing( &rect, color, shader ); + CG_DrawPlayerWallclimbing( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_BOOSTED: - CG_DrawPlayerBoosted( &rect, color, shader ); + CG_DrawPlayerBoosted( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_BOOST_BOLT: - CG_DrawPlayerBoosterBolt( &rect, color, shader ); + CG_DrawPlayerBoosterBolt( &rect, backColor, foreColor, shader ); break; case CG_PLAYER_POISON_BARBS: - CG_DrawPlayerPoisonBarbs( &rect, color, shader ); + CG_DrawPlayerPoisonBarbs( &rect, foreColor, shader ); break; case CG_PLAYER_ALIEN_SENSE: CG_DrawAlienSense( &rect ); break; case CG_PLAYER_HUMAN_SCANNER: - CG_DrawHumanScanner( &rect, shader, color ); + CG_DrawHumanScanner( &rect, shader, foreColor ); break; case CG_PLAYER_USABLE_BUILDABLE: - CG_DrawUsableBuildable( &rect, shader, color ); + CG_DrawUsableBuildable( &rect, shader, foreColor ); break; case CG_KILLER: - CG_DrawKiller( &rect, scale, color, shader, textStyle ); + CG_DrawKiller( &rect, scale, foreColor, shader, textStyle ); break; case CG_PLAYER_SELECT: - CG_DrawItemSelect( &rect, color ); + CG_DrawItemSelect( &rect, foreColor ); break; case CG_PLAYER_WEAPONICON: - CG_DrawWeaponIcon( &rect, color ); + CG_DrawWeaponIcon( &rect, foreColor ); break; case CG_PLAYER_SELECTTEXT: CG_DrawItemSelectText( &rect, scale, textStyle ); break; case CG_SPECTATORS: - CG_DrawTeamSpectators( &rect, scale, color, shader ); + CG_DrawTeamSpectators( &rect, scale, textvalign, foreColor, shader ); + break; + case CG_PLAYER_LOCATION: + CG_DrawLocation( &rect, scale, textalign, foreColor ); + break; + case CG_FOLLOW: + CG_DrawFollow( &rect, text_x, text_y, foreColor, scale, + textalign, textvalign, textStyle ); break; case CG_PLAYER_CROSSHAIRNAMES: CG_DrawCrosshairNames( &rect, scale, textStyle ); break; + case CG_PLAYER_CROSSHAIR: + CG_DrawCrosshair( &rect, foreColor ); + break; case CG_STAGE_REPORT_TEXT: - CG_DrawStageReport( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawStageReport( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_ALIENS_SCORE_LABEL: + CG_DrawTeamLabel( &rect, TEAM_ALIENS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_HUMANS_SCORE_LABEL: + CG_DrawTeamLabel( &rect, TEAM_HUMANS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; //loading screen @@ -2695,74 +2934,92 @@ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, CG_DrawLevelShot( &rect ); break; case CG_LOAD_MEDIA: - CG_DrawMediaProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawMediaProgress( &rect, foreColor, scale, align, textalign, textStyle, + borderSize ); break; case CG_LOAD_MEDIA_LABEL: - CG_DrawMediaProgressLabel( &rect, text_x, text_y, color, scale, align ); + CG_DrawMediaProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); break; case CG_LOAD_BUILDABLES: - CG_DrawBuildablesProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawBuildablesProgress( &rect, foreColor, scale, align, textalign, + textStyle, borderSize ); break; case CG_LOAD_BUILDABLES_LABEL: - CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, color, scale, align ); + CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); break; case CG_LOAD_CHARMODEL: - CG_DrawCharModelProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawCharModelProgress( &rect, foreColor, scale, align, textalign, + textStyle, borderSize ); break; case CG_LOAD_CHARMODEL_LABEL: - CG_DrawCharModelProgressLabel( &rect, text_x, text_y, color, scale, align ); + CG_DrawCharModelProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); break; case CG_LOAD_OVERALL: - CG_DrawOverallProgress( &rect, color, scale, align, textStyle, special ); + CG_DrawOverallProgress( &rect, foreColor, scale, align, textalign, textStyle, + borderSize ); break; case CG_LOAD_LEVELNAME: - CG_DrawLevelName( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawLevelName( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_LOAD_MOTD: - CG_DrawMOTD( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawMOTD( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_LOAD_HOSTNAME: - CG_DrawHostname( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawHostname( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_FPS: - CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qtrue ); + CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qtrue ); break; case CG_FPS_FIXED: - CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qfalse ); + CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qfalse ); break; case CG_TIMER: - CG_DrawTimer( &rect, text_x, text_y, scale, color, align, textStyle ); + CG_DrawTimer( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); break; case CG_CLOCK: - CG_DrawClock( &rect, text_x, text_y, scale, color, align, textStyle ); + CG_DrawClock( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); break; case CG_TIMER_MINS: - CG_DrawTimerMins( &rect, color ); + CG_DrawTimerMins( &rect, foreColor ); break; case CG_TIMER_SECS: - CG_DrawTimerSecs( &rect, color ); + CG_DrawTimerSecs( &rect, foreColor ); break; case CG_SNAPSHOT: - CG_DrawSnapshot( &rect, text_x, text_y, scale, color, align, textStyle ); + CG_DrawSnapshot( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); break; case CG_LAGOMETER: - CG_DrawLagometer( &rect, text_x, text_y, scale, color ); + CG_DrawLagometer( &rect, text_x, text_y, scale, foreColor ); + break; + case CG_TEAMOVERLAY: + CG_DrawTeamOverlay( &rect, scale, foreColor ); + break; + case CG_SPEEDOMETER: + CG_DrawSpeed( &rect, text_x, text_y, scale, foreColor, backColor ); break; case CG_DEMO_PLAYBACK: - CG_DrawDemoPlayback( &rect, color, shader ); + CG_DrawDemoPlayback( &rect, foreColor, shader ); break; case CG_DEMO_RECORDING: - CG_DrawDemoRecording( &rect, color, shader ); + CG_DrawDemoRecording( &rect, foreColor, shader ); break; case CG_CONSOLE: - CG_DrawConsole( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawConsole( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); break; case CG_TUTORIAL: - CG_DrawTutorial( &rect, text_x, text_y, color, scale, align, textStyle ); + CG_DrawTutorial( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + + case CG_KILLFEED: + CG_DrawKillMsg( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); + break; + + case CG_PLAYER_THZ_SCANNER: + THZ_DrawScanner( &rect ); break; default: @@ -2827,17 +3084,17 @@ CG_ShowTeamMenus */ void CG_ShowTeamMenu( void ) { - Menus_OpenByName( "teamMenu" ); + Menus_ActivateByName( "teamMenu" ); } /* ================== CG_EventHandling -================== - type 0 - no event handling - 1 - team menu - 2 - hud editor +type 0 - no event handling + 1 - team menu + 2 - hud editor +================== */ void CG_EventHandling( int type ) { @@ -2891,14 +3148,6 @@ int CG_ClientNumFromName( const char *p ) void CG_RunMenuScript( char **args ) { } - - -void CG_GetTeamColor( vec4_t *color ) -{ - (*color)[ 0 ] = (*color)[ 2 ] = 0.0f; - (*color)[ 1 ] = 0.17f; - (*color)[ 3 ] = 0.25f; -} //END TA UI @@ -2910,13 +3159,9 @@ CG_DrawLighting */ static void CG_DrawLighting( void ) { - centity_t *cent; - - cent = &cg_entities[ cg.snap->ps.clientNum ]; - //fade to black if stamina is low - if( ( cg.snap->ps.stats[ STAT_STAMINA ] < -800 ) && - ( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) + if( ( cg.snap->ps.stats[ STAT_STAMINA ] < STAMINA_BLACKOUT_LEVEL ) && + ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) ) { vec4_t black = { 0, 0, 0, 0 }; black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f ); @@ -2946,8 +3191,15 @@ for a few moments void CG_CenterPrint( const char *str, int y, int charWidth ) { char *s; + char newlineParsed[ MAX_STRING_CHARS ]; + const char *wrapped; + static int maxWidth = (int)( ( 2.0f / 3.0f ) * (float)SCREEN_WIDTH ); + + Q_ParseNewlines( newlineParsed, str, sizeof( newlineParsed ) ); - Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) ); + wrapped = Item_Text_Wrap( newlineParsed, 0.5f, maxWidth ); + + Q_strncpyz( cg.centerPrint, wrapped, sizeof( cg.centerPrint ) ); cg.centerPrintTime = cg.time; cg.centerPrintY = y; @@ -2994,9 +3246,9 @@ static void CG_DrawCenterString( void ) while( 1 ) { - char linebuffer[ 1024 ]; + char linebuffer[ MAX_STRING_CHARS ]; - for( l = 0; l < 50; l++ ) + for( l = 0; l < sizeof(linebuffer) - 1; l++ ) { if( !start[ l ] || start[ l ] == '\n' ) break; @@ -3006,10 +3258,10 @@ static void CG_DrawCenterString( void ) linebuffer[ l ] = 0; - w = CG_Text_Width( linebuffer, 0.5, 0 ); - h = CG_Text_Height( linebuffer, 0.5, 0 ); + w = UI_Text_Width( linebuffer, 0.5 ); + h = UI_Text_Height( linebuffer, 0.5 ); x = ( SCREEN_WIDTH - w ) / 2; - CG_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + UI_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); y += h + 6; while( *start && ( *start != '\n' ) ) @@ -3037,83 +3289,62 @@ static void CG_DrawCenterString( void ) CG_DrawVote ================= */ -static void CG_DrawVote( void ) +static void CG_DrawVote( team_t team ) { - char *s; + const char *s; int sec; + int offset = 0; vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; - char yeskey[ 32 ], nokey[ 32 ]; + char yeskey[ 32 ] = "", nokey[ 32 ] = ""; - if( !cgs.voteTime ) + if( !cgs.voteTime[ team ] ) return; // play a talk beep whenever it is modified - if( cgs.voteModified ) + if( cgs.voteModified[ team ] ) { - cgs.voteModified = qfalse; + cgs.voteModified[ team ] = qfalse; trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } - sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime[ team ] ) ) / 1000; if( sec < 0 ) sec = 0; - Q_strncpyz( yeskey, CG_KeyBinding( "vote yes" ), sizeof( yeskey ) ); - Q_strncpyz( nokey, CG_KeyBinding( "vote no" ), sizeof( nokey ) ); - s = va( "VOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec, cgs.voteString, - yeskey, cgs.voteYes, nokey, cgs.voteNo ); - CG_Text_Paint( 8, 340, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); -} -/* -================= -CG_DrawTeamVote -================= -*/ -static void CG_DrawTeamVote( void ) -{ - char *s; - int sec, cs_offset; - vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; - char yeskey[ 32 ], nokey[ 32 ]; + if( cg_tutorial.integer ) + { + Com_sprintf( yeskey, sizeof( yeskey ), "[%s]", + CG_KeyBinding( va( "%svote yes", team == TEAM_NONE ? "" : "team" ) ) ); + Com_sprintf( nokey, sizeof( nokey ), "[%s]", + CG_KeyBinding( va( "%svote no", team == TEAM_NONE ? "" : "team" ) ) ); + } - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) - cs_offset = 0; - else if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) - cs_offset = 1; - else - return; + if( team != TEAM_NONE ) + offset = 80; - if( !cgs.teamVoteTime[ cs_offset ] ) - return; + s = va( "%sVOTE(%i): %s", + team == TEAM_NONE ? "" : "TEAM", sec, cgs.voteString[ team ] ); - // play a talk beep whenever it is modified - if ( cgs.teamVoteModified[ cs_offset ] ) - { - cgs.teamVoteModified[ cs_offset ] = qfalse; - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } + UI_Text_Paint( 8, 300 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); - sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[ cs_offset ] ) ) / 1000; + s = va( " Called by: \"%s\"", cgs.voteCaller[ team ] ); - if( sec < 0 ) - sec = 0; + UI_Text_Paint( 8, 320 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); - Q_strncpyz( yeskey, CG_KeyBinding( "teamvote yes" ), sizeof( yeskey ) ); - Q_strncpyz( nokey, CG_KeyBinding( "teamvote no" ), sizeof( nokey ) ); - s = va( "TEAMVOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec, - cgs.teamVoteString[ cs_offset ], - yeskey, cgs.teamVoteYes[cs_offset], - nokey, cgs.teamVoteNo[ cs_offset ] ); + s = va( " %sYes:%i %sNo:%i", + yeskey, cgs.voteYes[ team ], nokey, cgs.voteNo[ team ] ); - CG_Text_Paint( 8, 360, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + UI_Text_Paint( 8, 340 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); } static qboolean CG_DrawScoreboard( void ) { static qboolean firstTime = qtrue; - float fade, *fadeColor; if( menuScoreboard ) menuScoreboard->window.flags &= ~WINDOW_FORCED; @@ -3125,13 +3356,8 @@ static qboolean CG_DrawScoreboard( void ) return qfalse; } - if( cg.showScores || - cg.predictedPlayerState.pm_type == PM_INTERMISSION ) - { - fade = 1.0; - fadeColor = colorWhite; - } - else + if( !cg.showScores && + cg.predictedPlayerState.pm_type != PM_INTERMISSION ) { cg.deferredPlayerLoading = 0; cg.killerName[ 0 ] = 0; @@ -3139,6 +3365,7 @@ static qboolean CG_DrawScoreboard( void ) return qfalse; } + CG_RequestScores( ); if( menuScoreboard == NULL ) menuScoreboard = Menus_FindByName( "teamscore_menu" ); @@ -3147,10 +3374,12 @@ static qboolean CG_DrawScoreboard( void ) { if( firstTime ) { + cg.spectatorTime = trap_Milliseconds(); CG_SetScoreSelection( menuScoreboard ); firstTime = qfalse; } + Menu_Update( menuScoreboard ); Menu_Paint( menuScoreboard, qtrue ); } @@ -3164,27 +3393,28 @@ CG_DrawIntermission */ static void CG_DrawIntermission( void ) { - if( cg_drawStatus.integer ) - Menu_Paint( Menus_FindByName( "default_hud" ), qtrue ); + menuDef_t *menu = Menus_FindByName( "default_hud" ); + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); cg.scoreFadeTime = cg.time; cg.scoreBoardShowing = CG_DrawScoreboard( ); } -#define FOLLOWING_STRING "following " - /* ================= -CG_DrawFollow +CG_DrawQueue ================= */ -static qboolean CG_DrawFollow( void ) +static qboolean CG_DrawQueue( void ) { float w; vec4_t color; - char buffer[ MAX_STRING_CHARS ]; + int position; + char *ordinal, buffer[ MAX_STRING_CHARS ]; - if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) ) return qfalse; color[ 0 ] = 1; @@ -3192,66 +3422,82 @@ static qboolean CG_DrawFollow( void ) color[ 2 ] = 1; color[ 3 ] = 1; - strcpy( buffer, FOLLOWING_STRING ); - strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name ); + position = cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1; + if( position < 1 ) + return qfalse; + + switch( position % 100 ) + { + case 11: + case 12: + case 13: + ordinal = "th"; + break; + default: + switch( position % 10 ) + { + case 1: ordinal = "st"; break; + case 2: ordinal = "nd"; break; + case 3: ordinal = "rd"; break; + default: ordinal = "th"; break; + } + break; + } + + Com_sprintf( buffer, MAX_STRING_CHARS, "You are %d%s in the spawn queue", + position, ordinal ); + + w = UI_Text_Width( buffer, 0.7f ); + UI_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + + if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 0 ) + Com_sprintf( buffer, MAX_STRING_CHARS, "There are no spawns remaining" ); + else if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 1 ) + Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining" ); + else + Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining", + cg.snap->ps.persistant[ PERS_SPAWNS ] ); - w = CG_Text_Width( buffer, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + w = UI_Text_Width( buffer, 0.7f ); + UI_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); return qtrue; } + /* ================= -CG_DrawQueue +CG_DrawWarmup ================= */ -static qboolean CG_DrawQueue( void ) +static void CG_DrawWarmup( void ) { - float w; - vec4_t color; - char buffer[ MAX_STRING_CHARS ]; - - if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) ) - return qfalse; + int sec = 0; + int w; + int h; + float size = 0.5f; + char text[ MAX_STRING_CHARS ] = "Warmup Time:"; - color[ 0 ] = 1; - color[ 1 ] = 1; - color[ 2 ] = 1; - color[ 3 ] = 1; + if( !cg.warmupTime ) + return; - Com_sprintf( buffer, MAX_STRING_CHARS, "You are in position %d of the spawn queue.", - cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1 ); + sec = ( cg.warmupTime - cg.time ) / 1000; - w = CG_Text_Width( buffer, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + if( sec < 0 ) + return; - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - if( cgs.numAlienSpawns == 1 ) - Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." ); - else - Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.", - cgs.numAlienSpawns ); - } - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - if( cgs.numHumanSpawns == 1 ) - Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." ); - else - Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.", - cgs.numHumanSpawns ); - } + w = UI_Text_Width( text, size ); + h = UI_Text_Height( text, size ); + UI_Text_Paint( 320 - w / 2, 200, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); - w = CG_Text_Width( buffer, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + Com_sprintf( text, sizeof( text ), "%s", sec ? va( "%d", sec ) : "FIGHT!" ); - return qtrue; + w = UI_Text_Width( text, size ); + UI_Text_Paint( 320 - w / 2, 200 + 1.5f * h, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); } //================================================================================== -#define SPECTATOR_STRING "SPECTATOR" /* ================= CG_Draw2D @@ -3259,15 +3505,11 @@ CG_Draw2D */ static void CG_Draw2D( void ) { - vec4_t color; - float w; - menuDef_t *menu = NULL, *defaultMenu; + menuDef_t *menu = NULL; - color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 1.0f; - - // if we are taking a levelshot for the menu, don't draw anything - if( cg.levelShot ) - return; + // fading to black if stamina runs out + // (only 2D that can't be disabled) + CG_DrawLighting( ); if( cg_draw2D.integer == 0 ) return; @@ -3278,36 +3520,30 @@ static void CG_Draw2D( void ) return; } - //TA: draw the lighting effects e.g. nvg - CG_DrawLighting( ); - - - defaultMenu = Menus_FindByName( "default_hud" ); - - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + if ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_NONE + || (cg.snap->ps.persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT + && cg.snap->ps.stats[ STAT_HEALTH ] > 0 )) { - w = CG_Text_Width( SPECTATOR_STRING, 0.7f, 0 ); - CG_Text_Paint( 320 - w / 2, 440, 0.7f, color, SPECTATOR_STRING, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + menu = Menus_FindByName( BG_ClassConfig( + cg.predictedPlayerState.stats[ STAT_CLASS ] )->hudName ); + + CG_DrawBuildableStatus( ); } - else - menu = Menus_FindByName( BG_FindHudNameForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ) ); - if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) && - !( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) && menu && - ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) ) + if( !menu ) { - CG_DrawBuildableStatus( ); - if( cg_drawStatus.integer ) - Menu_Paint( menu, qtrue ); + menu = Menus_FindByName( "default_hud" ); - CG_DrawCrosshair( ); + if( !menu ) // still couldn't find it + CG_Error( "Default HUD could not be found" ); } - else if( cg_drawStatus.integer ) - Menu_Paint( defaultMenu, qtrue ); - CG_DrawVote( ); - CG_DrawTeamVote( ); - CG_DrawFollow( ); + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); + + CG_DrawVote( TEAM_NONE ); + CG_DrawVote( cg.predictedPlayerState.stats[ STAT_TEAM ] ); + CG_DrawWarmup( ); CG_DrawQueue( ); // don't draw center string if scoreboard is up @@ -3356,7 +3592,7 @@ static void CG_PainBlend( void ) float x, y, w, h; float s1, t1, s2, t2; - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR || cg.intermissionStarted ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || cg.intermissionStarted ) return; damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ]; @@ -3383,9 +3619,9 @@ static void CG_PainBlend( void ) return; } - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) VectorSet( color, 0.43f, 0.8f, 0.37f ); - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) VectorSet( color, 0.8f, 0.0f, 0.0f ); if( cg.painBlendValue > cg.painBlendTarget ) @@ -3454,6 +3690,66 @@ void CG_ResetPainBlend( void ) cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ]; } +/* +================ +CG_DrawBinaryShadersFinalPhases +================ +*/ +static void CG_DrawBinaryShadersFinalPhases( void ) +{ + float ss, f, l, u; + polyVert_t verts[ 4 ] = { + { { 0, 0, 0 }, { 0, 0 }, { 255, 255, 255, 255 } }, + { { 0, 0, 0 }, { 1, 0 }, { 255, 255, 255, 255 } }, + { { 0, 0, 0 }, { 1, 1 }, { 255, 255, 255, 255 } }, + { { 0, 0, 0 }, { 0, 1 }, { 255, 255, 255, 255 } } + }; + int i, j, k; + + if( !cg.numBinaryShadersUsed ) + return; + + ss = cg_binaryShaderScreenScale.value; + if( ss <= 0.0f ) + { + cg.numBinaryShadersUsed = 0; + return; + } + else if( ss > 1.0f ) + ss = 1.0f; + + ss = sqrt( ss ); + + f = 1.01f; // FIXME: is this a good choice to avoid near-clipping? + l = f * tan( DEG2RAD( cg.refdef.fov_x / 2 ) ) * ss; + u = f * tan( DEG2RAD( cg.refdef.fov_y / 2 ) ) * ss; + + VectorMA( cg.refdef.vieworg, f, cg.refdef.viewaxis[ 0 ], verts[ 0 ].xyz ); + VectorMA( verts[ 0 ].xyz, l, cg.refdef.viewaxis[ 1 ], verts[ 0 ].xyz ); + VectorMA( verts[ 0 ].xyz, u, cg.refdef.viewaxis[ 2 ], verts[ 0 ].xyz ); + VectorMA( verts[ 0 ].xyz, -2*l, cg.refdef.viewaxis[ 1 ], verts[ 1 ].xyz ); + VectorMA( verts[ 1 ].xyz, -2*u, cg.refdef.viewaxis[ 2 ], verts[ 2 ].xyz ); + VectorMA( verts[ 0 ].xyz, -2*u, cg.refdef.viewaxis[ 2 ], verts[ 3 ].xyz ); + + trap_R_AddPolyToScene( cgs.media.binaryAlpha1Shader, 4, verts ); + + for( i = 0; i < cg.numBinaryShadersUsed; ++i ) + { + for( j = 0; j < 4; ++j ) + { + for( k = 0; k < 3; ++k ) + verts[ j ].modulate[ k ] = cg.binaryShaderSettings[ i ].color[ k ]; + } + + if( cg.binaryShaderSettings[ i ].drawFrontline ) + trap_R_AddPolyToScene( cgs.media.binaryShaders[ i ].f3, 4, verts ); + if( cg.binaryShaderSettings[ i ].drawIntersection ) + trap_R_AddPolyToScene( cgs.media.binaryShaders[ i ].b3, 4, verts ); + } + + cg.numBinaryShadersUsed = 0; +} + /* ===================== CG_DrawActive @@ -3496,6 +3792,8 @@ void CG_DrawActive( stereoFrame_t stereoView ) VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ], cg.refdef.vieworg ); + CG_DrawBinaryShadersFinalPhases( ); + // draw 3D view trap_R_RenderScene( &cg.refdef ); @@ -3510,6 +3808,3 @@ void CG_DrawActive( stereoFrame_t stereoView ) // draw status bar and other floating elements CG_Draw2D( ); } - - - diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c index 06ae071..cc8533a 100644 --- a/src/cgame/cg_drawtools.c +++ b/src/cgame/cg_drawtools.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc - #include "cg_local.h" /* @@ -126,10 +126,14 @@ Coords are virtual 640x480 */ void CG_DrawSides( float x, float y, float w, float h, float size ) { + float sizeY; + CG_AdjustFrom640( &x, &y, &w, &h ); + sizeY = size * cgs.screenYScale; size *= cgs.screenXScale; - trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); - trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_DrawStretchPic( x, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, cgs.media.whiteShader ); } void CG_DrawTopBottom( float x, float y, float w, float h, float size ) @@ -172,7 +176,34 @@ void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); } +/* +================ +CG_SetClipRegion +================= +*/ +void CG_SetClipRegion( float x, float y, float w, float h ) +{ + vec4_t clip; + + CG_AdjustFrom640( &x, &y, &w, &h ); + clip[ 0 ] = x; + clip[ 1 ] = y; + clip[ 2 ] = x + w; + clip[ 3 ] = y + h; + + trap_R_SetClipRegion( clip ); +} + +/* +================ +CG_ClearClipRegion +================= +*/ +void CG_ClearClipRegion( void ) +{ + trap_R_SetClipRegion( NULL ); +} /* ================ @@ -321,30 +352,30 @@ CG_WorldToScreen */ qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ) { - vec3_t trans; - float xc, yc; - float px, py; - float z; + vec3_t trans; + float xc, yc; + float px, py; + float z; - px = tan( cg.refdef.fov_x * M_PI / 360.0 ); - py = tan( cg.refdef.fov_y * M_PI / 360.0 ); + px = tan( cg.refdef.fov_x * M_PI / 360.0f ); + py = tan( cg.refdef.fov_y * M_PI / 360.0f ); - VectorSubtract( point, cg.refdef.vieworg, trans ); + VectorSubtract( point, cg.refdef.vieworg, trans ); - xc = 640.0f / 2.0f; - yc = 480.0f / 2.0f; + xc = ( 640.0f * cg_viewsize.integer ) / 200.0f; + yc = ( 480.0f * cg_viewsize.integer ) / 200.0f; - z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] ); - if( z <= 0.001f ) - return qfalse; + z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] ); + if( z <= 0.001f ) + return qfalse; if( x ) - *x = xc - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px ); + *x = 320.0f - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px ); if( y ) - *y = yc - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); + *y = 240.0f - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); - return qtrue; + return qtrue; } /* @@ -359,6 +390,7 @@ char *CG_KeyBinding( const char *bind ) int i; key[ 0 ] = '\0'; + // NOTE: change K_LAST_KEY to MAX_KEYS for full key support (eventually) for( i = 0; i < K_LAST_KEY; i++ ) { @@ -367,12 +399,41 @@ char *CG_KeyBinding( const char *bind ) { trap_Key_KeynumToStringBuf( i, key, sizeof( key ) ); break; - } + } } + if( !key[ 0 ] ) { Q_strncpyz( key, "\\", sizeof( key ) ); Q_strcat( key, sizeof( key ), bind ); } + return key; } + +/* +================= +CG_GetColorCharForHealth +================= +*/ +char CG_GetColorCharForHealth( int clientnum ) +{ + char health_char = '2'; + int healthPercent; + int maxHealth; + int curWeaponClass = cgs.clientinfo[ clientnum ].curWeaponClass; + + if( cgs.clientinfo[ clientnum ].team == TEAM_ALIENS ) + maxHealth = BG_Class( curWeaponClass )->health; + else + maxHealth = BG_Class( PCL_HUMAN )->health; + + healthPercent = (int) ( 100.0f * (float) cgs.clientinfo[ clientnum ].health / + (float) maxHealth ); + + if( healthPercent < 33 ) + health_char = '1'; + else if( healthPercent < 67 ) + health_char = '3'; + return health_char; +} diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c index 17f1a7d..4008e56 100644 --- a/src/cgame/cg_ents.c +++ b/src/cgame/cg_ents.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_ents.c -- present snapshot entities, happens every single frame - #include "cg_local.h" /* @@ -251,7 +251,7 @@ static void CG_EntityEffects( centity_t *cent ) if( CG_IsTrailSystemValid( ¢->muzzleTS ) ) { - //FIXME hack to prevent tesla trails reaching too far + //FIXME hack to prevent tesla trails reaching too far if( cent->currentState.eType == ET_BUILDABLE ) { vec3_t front, back; @@ -309,6 +309,136 @@ static void CG_General( centity_t *cent ) trap_R_AddRefEntityToScene( &ent ); } +/* +================== +CG_WeaponDrop +================== +*/ +static void CG_WeaponDrop( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es; + int msec; + float frac; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + + if (BG_Weapon(es->modelindex) == WP_NONE) + { + CG_Printf("Bad weapon index %i on entity", es->modelindex); + return; + } + + // if set to invisible, skip + if (es->eFlags & EF_NODRAW) + return; + + + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + + memset (&ent, 0, sizeof(ent)); + + // autorotate at one of two speeds + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); +// VectorCopy( cg.autoAngles, cent->lerpAngles ); +// AxisCopy( cg.autoAxis, ent.axis ); + + wi = &cg_weapons[ es->modelindex ]; + + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + + cent->lerpOrigin[0] -= wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + cent->lerpOrigin[2] += 8; // an extra height boost + +#if 0 + if( item->giType == IT_WEAPON && item->giTag == WP_RAILGUN ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + Byte4Copy( ci->c1RGBA, ent.shaderRGBA ); + } +#endif + + ent.hModel = wi->weaponModel; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + msec = cg.time - cent->miscTime; + if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) + { + frac = (float)msec / ITEM_SCALEUP_TIME; + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + else + { + frac = 1.0; + } + + // items without glow textures need to keep a minimum light value + // so they are always visible + ent.renderfx |= RF_MINLIGHT; + + // increase the size of the weapons when they are presented as items + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; +#ifdef MISSIONPACK + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); +#endif + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + +#if 0 + if ( item->giType == IT_WEAPON && wi && wi->barrelModel ) + { + refEntity_t barrel; + vec3_t angles; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } +#endif +} + + /* ================== CG_Speaker @@ -367,6 +497,7 @@ static void CG_LaunchMissile( centity_t *cent ) { CG_SetAttachmentCent( &ps->attachment, cent ); CG_AttachToCent( &ps->attachment ); + ps->charge = es->torsoAnim; } } @@ -407,9 +538,6 @@ static void CG_Missile( centity_t *cent ) wim = &wi->wim[ weaponMode ]; - // calculate the axis - VectorCopy( es->angles, cent->lerpAngles ); - // add dynamic light if( wim->missileDlight ) { @@ -437,7 +565,8 @@ static void CG_Missile( centity_t *cent ) if( wim->usesSpriteMissle ) { ent.reType = RT_SPRITE; - ent.radius = wim->missileSpriteSize; + ent.radius = wim->missileSpriteSize + + wim->missileSpriteCharge * es->torsoAnim; ent.rotation = 0; ent.customShader = wim->missileSprite; ent.shaderRGBA[ 0 ] = 0xFF; @@ -498,6 +627,9 @@ static void CG_Mover( centity_t *cent ) s1 = ¢->currentState; + if( !s1->modelindex ) + return; + // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); @@ -653,7 +785,7 @@ static void CG_LightFlare( centity_t *cent ) flare.renderfx |= RF_DEPTHHACK; //bunch of geometry - AngleVectors( es->angles, forward, NULL, NULL ); + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy( cent->lerpOrigin, flare.origin ); VectorSubtract( flare.origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); @@ -788,38 +920,28 @@ static void CG_Lev2ZapChain( centity_t *cent ) int i; entityState_t *es; centity_t *source = NULL, *target = NULL; + int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; + int count; es = ¢->currentState; - for( i = 0; i <= 2; i++ ) + count = BG_UnpackEntityNumbers( es, entityNums, LEVEL2_AREAZAP_MAX_TARGETS + 1 ); + + for( i = 1; i < count; i++ ) { - switch( i ) + if( i == 1 ) { - case 0: - if( es->time <= 0 ) - continue; - - source = &cg_entities[ es->misc ]; - target = &cg_entities[ es->time ]; - break; - - case 1: - if( es->time2 <= 0 ) - continue; - - source = &cg_entities[ es->time ]; - target = &cg_entities[ es->time2 ]; - break; - - case 2: - if( es->constantLight <= 0 ) - continue; - - source = &cg_entities[ es->time2 ]; - target = &cg_entities[ es->constantLight ]; - break; + // First entity is the attacker + source = &cg_entities[ entityNums[ 0 ] ]; + } + else + { + // Subsequent zaps come from the first target + source = &cg_entities[ entityNums[ 1 ] ]; } + target = &cg_entities[ entityNums[ i ] ]; + if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); @@ -844,7 +966,7 @@ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; - vec3_t oldAngles, angles, deltaAngles; + vec3_t oldAngles, angles; if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { @@ -867,7 +989,6 @@ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); - VectorSubtract( angles, oldAngles, deltaAngles ); VectorAdd( in, deltaOrigin, out ); @@ -947,9 +1068,10 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) return; } - if( cg_projectileNudge.integer > 0 && - cent->currentState.eType == ET_MISSILE && - !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + if( cg_projectileNudge.integer && + !cg.demoPlayback && + cent->currentState.eType == ET_MISSILE && + !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { timeshift = cg.ping; } @@ -972,7 +1094,7 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) // don't let the projectile go through the floor if( tr.fraction < 1.0f ) - VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); + VectorLerp2( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); } // adjust for riding a mover if it wasn't rolled into the predicted @@ -984,7 +1106,6 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) } } - /* =============== CG_CEntityPVSEnter @@ -1003,6 +1124,10 @@ static void CG_CEntityPVSEnter( centity_t *cent ) case ET_MISSILE: CG_LaunchMissile( cent ); break; + + case ET_BUILDABLE: + cent->lastBuildableHealth = es->misc; + break; } //clear any particle systems from previous uses of this centity_t @@ -1034,11 +1159,10 @@ static void CG_CEntityPVSLeave( centity_t *cent ) if( cg_debugPVS.integer ) CG_Printf( "Entity %d left PVS\n", cent->currentState.number ); - switch( es->eType ) { case ET_LEV2_ZAP_CHAIN: - for( i = 0; i <= 2; i++ ) + for( i = 0; i <= LEVEL2_AREAZAP_MAX_TARGETS; i++ ) { if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] ); @@ -1069,18 +1193,23 @@ static void CG_AddCEntity( centity_t *cent ) switch( cent->currentState.eType ) { default: - CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + CG_Error( "Bad entity type: %i", cent->currentState.eType ); break; case ET_INVISIBLE: case ET_PUSH_TRIGGER: case ET_TELEPORT_TRIGGER: + case ET_LOCATION: break; case ET_GENERAL: CG_General( cent ); break; + case ET_WEAPON_DROP: + CG_WeaponDrop( cent ); + break; + case ET_CORPSE: CG_Corpse( cent ); break; @@ -1093,6 +1222,10 @@ static void CG_AddCEntity( centity_t *cent ) CG_Buildable( cent ); break; + case ET_RANGE_MARKER: + CG_RangeMarker( cent ); + break; + case ET_MISSILE: CG_Missile( cent ); break; @@ -1253,4 +1386,3 @@ void CG_AddPacketEntities( void ) } } } - diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c index 07bcea9..3ed0ac9 100644 --- a/src/cgame/cg_event.c +++ b/src/cgame/cg_event.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,16 +17,129 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_event.c -- handle entity events at snapshot or playerstate transitions - #include "cg_local.h" +/* +======================= +CG_AddToKillMsg + +======================= +*/ +void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ) +{ + int klen, vlen, index; + char *kls, *vls; + char *k, *v; + int lastcolor; + int chatHeight; + + if( cg_killMsgHeight.integer < TEAMCHAT_HEIGHT ) + chatHeight = cg_killMsgHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + + if( chatHeight <= 0 || cg_killMsgTime.integer <= 0 ) { + cgs.killMsgPos = cgs.killMsgLastPos = 0; + return; + } + + index = cgs.killMsgPos % chatHeight; + klen = vlen = 0; + + k = cgs.killMsgKillers[ index ]; *k=0; + v = cgs.killMsgVictims[ index ]; *v=0; + cgs.killMsgWeapons[ index ] = icon; + + memset( k, '\0', sizeof(cgs.killMsgKillers[index])); + memset( v, '\0', sizeof(cgs.killMsgVictims[index])); + kls = vls = NULL; + + lastcolor = '7'; + + // Killers name + while( *killername ) + { + if( klen > TEAMCHAT_WIDTH-1 ) { + if( kls ) { + killername -= ( k - kls ); + killername ++; + k -= ( k - kls ); + } + *k = 0; + +// cgs.killMsgMsgTimes[index] = cg.time; + k = cgs.killMsgKillers[index]; + *k = 0; + *k++ = Q_COLOR_ESCAPE; + *k++ = lastcolor; + klen = 0; + kls = NULL; + } + + if( Q_IsColorString( killername ) ) + { + *k++ = *killername++; + lastcolor = *killername; + *k++ = *killername++; + continue; + } + + if( *killername == ' ' ) + kls = k; + + *k++ = *killername++; + klen++; + } + + // Victims name + if (victimname) + while( *victimname ) + { + if( vlen > TEAMCHAT_WIDTH-1 ) { + if( vls ) { + victimname -= ( v - vls ); + victimname ++; + v -= ( v - vls ); + } + *v = 0; + + v = cgs.killMsgVictims[index]; + *v = 0; + *v++ = Q_COLOR_ESCAPE; + *v++ = lastcolor; + vlen = 0; + vls = NULL; + } + + if( Q_IsColorString( victimname ) ) + { + *v++ = *victimname++; + lastcolor = *victimname; + *v++ = *victimname++; + continue; + } + + if( *victimname == ' ' ) + vls = v; + + *v++ = *victimname++; + vlen++; + } + + cgs.killMsgMsgTimes[ index ] = cg.time; + cgs.killMsgPos++; + + if( cgs.killMsgPos - cgs.killMsgLastPos > chatHeight ) + cgs.killMsgLastPos = cgs.killMsgPos - chatHeight; +} + /* ============= CG_Obituary @@ -39,12 +153,13 @@ static void CG_Obituary( entityState_t *ent ) char *message2; const char *targetInfo; const char *attackerInfo; - char targetName[ 32 ]; - char attackerName[ 32 ]; + char targetName[ MAX_NAME_LENGTH ]; + char attackerName[ MAX_NAME_LENGTH ]; char className[ 64 ]; gender_t gender; clientInfo_t *ci; qboolean teamKill = qfalse; + int icon = WP_NONE; target = ent->otherEntityNum; attacker = ent->otherEntityNum2; @@ -54,6 +169,7 @@ static void CG_Obituary( entityState_t *ent ) CG_Error( "CG_Obituary: target out of range" ); ci = &cgs.clientinfo[ target ]; + gender = ci->gender; if( attacker < 0 || attacker >= MAX_CLIENTS ) { @@ -72,8 +188,7 @@ static void CG_Obituary( entityState_t *ent ) if( !targetInfo ) return; - Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName ) - 2 ); - strcat( targetName, S_COLOR_WHITE ); + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName )); message2 = ""; @@ -81,9 +196,6 @@ static void CG_Obituary( entityState_t *ent ) switch( mod ) { - case MOD_SUICIDE: - message = "suicides"; - break; case MOD_FALLING: message = "fell fowl to gravity"; break; @@ -97,7 +209,7 @@ static void CG_Obituary( entityState_t *ent ) message = "melted"; break; case MOD_LAVA: - message = "does a back flip into the lava"; + message = "did a back flip into the lava"; break; case MOD_TARGET_LASER: message = "saw the light"; @@ -137,9 +249,8 @@ static void CG_Obituary( entityState_t *ent ) break; } - if( attacker == target ) + if( !message && attacker == target ) { - gender = ci->gender; switch( mod ) { case MOD_FLAMER_SPLASH: @@ -169,6 +280,24 @@ static void CG_Obituary( entityState_t *ent ) message = "blew himself up"; break; + case MOD_LEVEL3_BOUNCEBALL: + if( gender == GENDER_FEMALE ) + message = "sniped herself"; + else if( gender == GENDER_NEUTER ) + message = "sniped itself"; + else + message = "sniped himself"; + break; + + case MOD_PRIFLE: + if( gender == GENDER_FEMALE ) + message = "pulse rifled herself"; + else if( gender == GENDER_NEUTER ) + message = "pulse rifled itself"; + else + message = "pulse rifled himself"; + break; + default: if( gender == GENDER_FEMALE ) message = "killed herself"; @@ -178,12 +307,12 @@ static void CG_Obituary( entityState_t *ent ) message = "killed himself"; break; } - } - if( message ) - { - CG_Printf( "%s %s.\n", targetName, message ); - return; + if ( cg_killMsg.integer == 2) + { + CG_AddToKillMsg(va("%s ^7%s", targetName, message), NULL, WP_NONE); + return; + } } // check for double client messages @@ -194,8 +323,7 @@ static void CG_Obituary( entityState_t *ent ) } else { - Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName ) - 2); - strcat( attackerName, S_COLOR_WHITE ); + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName )); // check for kill messages about the current clientNum if( target == cg.snap->ps.clientNum ) Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); @@ -205,119 +333,141 @@ static void CG_Obituary( entityState_t *ent ) { switch( mod ) { + // + // HUMANS + // case MOD_PAINSAW: + icon = WP_PAIN_SAW; message = "was sawn by"; break; case MOD_BLASTER: + icon = WP_BLASTER; message = "was blasted by"; break; case MOD_MACHINEGUN: + icon = WP_MACHINEGUN; message = "was machinegunned by"; break; case MOD_CHAINGUN: + icon = WP_CHAINGUN; message = "was chaingunned by"; break; case MOD_SHOTGUN: + icon = WP_SHOTGUN; message = "was gunned down by"; break; case MOD_PRIFLE: + icon = WP_PULSE_RIFLE; message = "was pulse rifled by"; break; case MOD_MDRIVER: + icon = WP_MASS_DRIVER; message = "was mass driven by"; break; case MOD_LASGUN: + icon = WP_LAS_GUN; message = "was lasgunned by"; break; case MOD_FLAMER: - message = "was grilled by"; - message2 = "'s flamer"; - break; case MOD_FLAMER_SPLASH: - message = "was toasted by"; + icon = WP_FLAMER; + message = "was grilled by"; message2 = "'s flamer"; break; case MOD_LCANNON: + icon = WP_LUCIFER_CANNON; message = "felt the full force of"; message2 = "'s lucifer cannon"; break; case MOD_LCANNON_SPLASH: + icon = WP_LUCIFER_CANNON; message = "was caught in the fallout of"; message2 = "'s lucifer cannon"; break; case MOD_GRENADE: + icon = WP_GRENADE; message = "couldn't escape"; message2 = "'s grenade"; break; + // + // ALIENS + // case MOD_ABUILDER_CLAW: + icon = WP_ABUILD; message = "should leave"; message2 = "'s buildings alone"; break; case MOD_LEVEL0_BITE: + icon = WP_ALEVEL0; message = "was bitten by"; break; case MOD_LEVEL1_CLAW: + icon = WP_ALEVEL1; message = "was swiped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); + message2 = className; + break; + case MOD_LEVEL1_PCLOUD: + icon = WP_ALEVEL1; + message = "was gassed by"; + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); message2 = className; break; case MOD_LEVEL2_CLAW: + icon = WP_ALEVEL2; message = "was clawed by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); message2 = className; break; case MOD_LEVEL2_ZAP: + icon = WP_ALEVEL2; message = "was zapped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); message2 = className; break; case MOD_LEVEL3_CLAW: + icon = WP_ALEVEL3; message = "was chomped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); message2 = className; break; case MOD_LEVEL3_POUNCE: + icon = WP_ALEVEL3; message = "was pounced upon by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); message2 = className; break; case MOD_LEVEL3_BOUNCEBALL: + icon = WP_ALEVEL3; message = "was sniped by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); message2 = className; break; case MOD_LEVEL4_CLAW: + icon = WP_ALEVEL4; message = "was mauled by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); message2 = className; break; - case MOD_LEVEL4_CHARGE: + case MOD_LEVEL4_TRAMPLE: + icon = WP_ALEVEL4; message = "should have gotten out of the way of"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) ); + Com_sprintf( className, 64, "'s %s", BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); message2 = className; break; - + case MOD_LEVEL4_CRUSH: + message = "was crushed under"; + message2 = "'s weight"; + break; case MOD_POISON: message = "should have used a medkit against"; message2 = "'s poison"; break; - case MOD_LEVEL1_PCLOUD: - message = "was gassed by"; - Com_sprintf( className, 64, "'s %s", - BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) ); - message2 = className; - break; - + // + // MISC.. + // case MOD_TELEFRAG: message = "tried to invade"; message2 = "'s personal space"; @@ -327,24 +477,55 @@ static void CG_Obituary( entityState_t *ent ) break; } - if( message ) + if ( cg_killMsg.integer == 1) { - CG_Printf( "%s %s %s%s%s\n", - targetName, message, - ( teamKill ) ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "", - attackerName, message2 ); - if( teamKill && attacker == cg.clientNum ) + char killMessage[80]; + if( icon > WP_NONE ) { - CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE " - S_COLOR_WHITE "%s", targetName ), - SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + Com_sprintf(killMessage, sizeof(killMessage), "%s%s", + teamKill ? S_COLOR_RED"TEAMMATE "S_COLOR_WHITE:"", + targetName); + CG_AddToKillMsg(attackerName, killMessage, icon); } - return; } + else if( message ) + { + CG_Printf( "%s" S_COLOR_WHITE " %s %s%s" S_COLOR_WHITE "%s\n", + targetName, + message, + teamKill ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "", + attackerName, + message2 ); + } + + if( attacker == cg.clientNum ) + { + CG_CenterPrint(va("You killed %s%s", teamKill ? S_COLOR_RED"TEAMMATE "S_COLOR_WHITE:"", + targetName), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + if ( cg_killMsg.integer != 1) + return; + } + +#if 0 + switch (mod) { + case MOD_SLAP: + message = "was slapped to death"; + break; + default: + break; } +#endif // we don't know what it was - CG_Printf( "%s died.\n", targetName ); + if ( cg_killMsg.integer ) + { + CG_AddToKillMsg(va("%s ^7%s", targetName, message), NULL, WP_NONE); + } + else + { + CG_Printf( "%s" S_COLOR_WHITE " %s\n", targetName, message ); + } } //========================================================================== @@ -381,6 +562,60 @@ void CG_PainEvent( centity_t *cent, int health ) cent->pe.painDirection ^= 1; } +/* +========================= +CG_Level2Zap +========================= +*/ +static void CG_Level2Zap( entityState_t *es ) +{ + int i; + centity_t *source = NULL, *target = NULL; + + if( es->misc < 0 || es->misc >= MAX_CLIENTS ) + return; + + source = &cg_entities[ es->misc ]; + for( i = 0; i <= 2; i++ ) + { + switch( i ) + { + case 0: + if( es->time <= 0 ) + continue; + + target = &cg_entities[ es->time ]; + break; + + case 1: + if( es->time2 <= 0 ) + continue; + + target = &cg_entities[ es->time2 ]; + break; + + case 2: + if( es->constantLight <= 0 ) + continue; + + target = &cg_entities[ es->constantLight ]; + break; + } + + if( !CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + source->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); + + if( CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + { + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->frontAttachment, source ); + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->backAttachment, target ); + CG_AttachToCent( &source->level2ZapTS[ i ]->frontAttachment ); + CG_AttachToCent( &source->level2ZapTS[ i ]->backAttachment ); + } + } + source->level2ZapTime = cg.time; +} + /* ============== CG_EntityEvent @@ -389,7 +624,6 @@ An entity has an event value also called by CG_CheckPlayerstateEvents ============== */ -#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} void CG_EntityEvent( centity_t *cent, vec3_t position ) { entityState_t *es; @@ -400,22 +634,20 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) clientInfo_t *ci; int steptime; - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) steptime = 200; else - steptime = BG_FindSteptimeForClass( cg.snap->ps.stats[ STAT_PCLASS ] ); + steptime = BG_Class( cg.snap->ps.stats[ STAT_CLASS ] )->steptime; es = ¢->currentState; event = es->event & ~EV_EVENT_BITS; if( cg_debugEvents.integer ) - CG_Printf( "ent:%3i event:%3i ", es->number, event ); + CG_Printf( "ent:%3i event:%3i %s\n", es->number, event, + BG_EventName( event ) ); if( !event ) - { - DEBUGNAME("ZEROEVENT"); return; - } clientNum = es->clientNum; if( clientNum < 0 || clientNum >= MAX_CLIENTS ) @@ -429,7 +661,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // movement generated events // case EV_FOOTSTEP: - DEBUGNAME( "EV_FOOTSTEP" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { if( ci->footsteps == FOOTSTEP_CUSTOM ) @@ -442,7 +673,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTSTEP_METAL: - DEBUGNAME( "EV_FOOTSTEP_METAL" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { if( ci->footsteps == FOOTSTEP_CUSTOM ) @@ -455,7 +685,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTSTEP_SQUELCH: - DEBUGNAME( "EV_FOOTSTEP_SQUELCH" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -464,7 +693,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTSPLASH: - DEBUGNAME( "EV_FOOTSPLASH" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -473,7 +701,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_FOOTWADE: - DEBUGNAME( "EV_FOOTWADE" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -482,7 +709,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_SWIM: - DEBUGNAME( "EV_SWIM" ); if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, @@ -492,45 +718,41 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_FALL_SHORT: - DEBUGNAME( "EV_FALL_SHORT" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound ); if( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes - cg.landChange = -8; + cg.landChange = -1 * BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->landBob; cg.landTime = cg.time; } break; case EV_FALL_MEDIUM: - DEBUGNAME( "EV_FALL_MEDIUM" ); // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); if( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes - cg.landChange = -16; + cg.landChange = -2 * BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->landBob; cg.landTime = cg.time; } break; case EV_FALL_FAR: - DEBUGNAME( "EV_FALL_FAR" ); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); cent->pe.painTime = cg.time; // don't play a pain sound right after this if( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes - cg.landChange = -24; + cg.landChange = -3 * BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->landBob; cg.landTime = cg.time; } break; case EV_FALLING: - DEBUGNAME( "EV_FALLING" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) ); break; @@ -542,7 +764,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_STEPDN_8: case EV_STEPDN_12: case EV_STEPDN_16: // smooth out step down transitions - DEBUGNAME( "EV_STEP" ); { float oldStep; int delta; @@ -586,10 +807,9 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) } case EV_JUMP: - DEBUGNAME( "EV_JUMP" ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); - if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) { vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f }; vec3_t rotAxis; @@ -617,44 +837,36 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_LEV1_GRAB: - DEBUGNAME( "EV_LEV1_GRAB" ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab ); break; - case EV_LEV4_CHARGE_PREPARE: - DEBUGNAME( "EV_LEV4_CHARGE_PREPARE" ); + case EV_LEV4_TRAMPLE_PREPARE: trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare ); break; - case EV_LEV4_CHARGE_START: - DEBUGNAME( "EV_LEV4_CHARGE_START" ); + case EV_LEV4_TRAMPLE_START: //FIXME: stop cgs.media.alienL4ChargePrepare playing here trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart ); break; case EV_TAUNT: - DEBUGNAME( "EV_TAUNT" ); if( !cg_noTaunt.integer ) trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); break; case EV_WATER_TOUCH: - DEBUGNAME( "EV_WATER_TOUCH" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); break; case EV_WATER_LEAVE: - DEBUGNAME( "EV_WATER_LEAVE" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); break; case EV_WATER_UNDER: - DEBUGNAME( "EV_WATER_UNDER" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); break; case EV_WATER_CLEAR: - DEBUGNAME( "EV_WATER_CLEAR" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); break; @@ -662,28 +874,23 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // weapon events // case EV_NOAMMO: - DEBUGNAME( "EV_NOAMMO" ); - { - } + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, + cgs.media.weaponEmptyClick ); break; case EV_CHANGE_WEAPON: - DEBUGNAME( "EV_CHANGE_WEAPON" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); break; case EV_FIRE_WEAPON: - DEBUGNAME( "EV_FIRE_WEAPON" ); CG_FireWeapon( cent, WPM_PRIMARY ); break; case EV_FIRE_WEAPON2: - DEBUGNAME( "EV_FIRE_WEAPON2" ); CG_FireWeapon( cent, WPM_SECONDARY ); break; case EV_FIRE_WEAPON3: - DEBUGNAME( "EV_FIRE_WEAPON3" ); CG_FireWeapon( cent, WPM_TERTIARY ); break; @@ -693,32 +900,26 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // other events // case EV_PLAYER_TELEPORT_IN: - DEBUGNAME( "EV_PLAYER_TELEPORT_IN" ); //deprecated break; case EV_PLAYER_TELEPORT_OUT: - DEBUGNAME( "EV_PLAYER_TELEPORT_OUT" ); CG_PlayerDisconnect( position ); break; case EV_BUILD_CONSTRUCT: - DEBUGNAME( "EV_BUILD_CONSTRUCT" ); //do something useful here break; case EV_BUILD_DESTROY: - DEBUGNAME( "EV_BUILD_DESTROY" ); //do something useful here break; case EV_RPTUSE_SOUND: - DEBUGNAME( "EV_RPTUSE_SOUND" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound ); break; case EV_GRENADE_BOUNCE: - DEBUGNAME( "EV_GRENADE_BOUNCE" ); if( rand( ) & 1 ) trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 ); else @@ -729,40 +930,34 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) // missile impacts // case EV_MISSILE_HIT: - DEBUGNAME( "EV_MISSILE_HIT" ); ByteToDir( es->eventParm, dir ); - CG_MissileHitPlayer( es->weapon, es->generic1, position, dir, es->otherEntityNum ); + CG_MissileHitEntity( es->weapon, es->generic1, position, dir, es->otherEntityNum, es->torsoAnim ); break; case EV_MISSILE_MISS: - DEBUGNAME( "EV_MISSILE_MISS" ); ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT, es->torsoAnim ); break; case EV_MISSILE_MISS_METAL: - DEBUGNAME( "EV_MISSILE_MISS_METAL" ); ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL, es->torsoAnim ); break; case EV_HUMAN_BUILDABLE_EXPLOSION: - DEBUGNAME( "EV_HUMAN_BUILDABLE_EXPLOSION" ); ByteToDir( es->eventParm, dir ); CG_HumanBuildableExplosion( position, dir ); break; case EV_ALIEN_BUILDABLE_EXPLOSION: - DEBUGNAME( "EV_ALIEN_BUILDABLE_EXPLOSION" ); ByteToDir( es->eventParm, dir ); CG_AlienBuildableExplosion( position, dir ); break; case EV_TESLATRAIL: - DEBUGNAME( "EV_TESLATRAIL" ); cent->currentState.weapon = WP_TESLAGEN; { - centity_t *source = &cg_entities[ es->generic1 ]; + centity_t *source = &cg_entities[ es->misc ]; centity_t *target = &cg_entities[ es->clientNum ]; vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f }; @@ -785,23 +980,19 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_BULLET_HIT_WALL: - DEBUGNAME( "EV_BULLET_HIT_WALL" ); ByteToDir( es->eventParm, dir ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); break; case EV_BULLET_HIT_FLESH: - DEBUGNAME( "EV_BULLET_HIT_FLESH" ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); break; case EV_SHOTGUN: - DEBUGNAME( "EV_SHOTGUN" ); CG_ShotgunFire( es ); break; case EV_GENERAL_SOUND: - DEBUGNAME( "EV_GENERAL_SOUND" ); if( cgs.gameSounds[ es->eventParm ] ) trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); else @@ -812,7 +1003,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes - DEBUGNAME( "EV_GLOBAL_SOUND" ); if( cgs.gameSounds[ es->eventParm ] ) trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); else @@ -825,7 +1015,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_PAIN: // local player sounds are triggered in CG_CheckLocalSounds, // so ignore events on the player - DEBUGNAME( "EV_PAIN" ); if( cent->currentState.number != cg.snap->ps.clientNum ) CG_PainEvent( cent, es->eventParm ); break; @@ -833,34 +1022,28 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_DEATH1: case EV_DEATH2: case EV_DEATH3: - DEBUGNAME( "EV_DEATHx" ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) ); break; case EV_OBITUARY: - DEBUGNAME( "EV_OBITUARY" ); CG_Obituary( es ); break; case EV_GIB_PLAYER: - DEBUGNAME( "EV_GIB_PLAYER" ); // no gibbing break; case EV_STOPLOOPINGSOUND: - DEBUGNAME( "EV_STOPLOOPINGSOUND" ); trap_S_StopLoopingSound( es->number ); es->loopSound = 0; break; case EV_DEBUG_LINE: - DEBUGNAME( "EV_DEBUG_LINE" ); CG_Beam( cent ); break; case EV_BUILD_DELAY: - DEBUGNAME( "EV_BUILD_DELAY" ); if( clientNum == cg.predictedPlayerState.clientNum ) { trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); @@ -869,18 +1052,15 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_BUILD_REPAIR: - DEBUGNAME( "EV_BUILD_REPAIR" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound ); break; case EV_BUILD_REPAIRED: - DEBUGNAME( "EV_BUILD_REPAIRED" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound ); break; case EV_OVERMIND_ATTACK: - DEBUGNAME( "EV_OVERMIND_ATTACK" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 ); @@ -888,8 +1068,7 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_OVERMIND_DYING: - DEBUGNAME( "EV_OVERMIND_DYING" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 ); @@ -897,17 +1076,19 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_DCC_ATTACK: - DEBUGNAME( "EV_DCC_ATTACK" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) { //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER ); CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 ); } break; + case EV_MGTURRET_SPINUP: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.turretSpinupSound ); + break; + case EV_OVERMIND_SPAWNS: - DEBUGNAME( "EV_OVERMIND_SPAWNS" ); - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 ); @@ -915,7 +1096,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_ALIEN_EVOLVE: - DEBUGNAME( "EV_ALIEN_EVOLVE" ); trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS ); @@ -935,7 +1115,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_ALIEN_EVOLVE_FAILED: - DEBUGNAME( "EV_ALIEN_EVOLVE_FAILED" ); if( clientNum == cg.predictedPlayerState.clientNum ) { //FIXME: change to "negative" sound @@ -945,7 +1124,6 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_ALIEN_ACIDTUBE: - DEBUGNAME( "EV_ALIEN_ACIDTUBE" ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); @@ -960,18 +1138,19 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) break; case EV_MEDKIT_USED: - DEBUGNAME( "EV_MEDKIT_USED" ); trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound ); break; case EV_PLAYER_RESPAWN: - DEBUGNAME( "EV_PLAYER_RESPAWN" ); if( es->number == cg.clientNum ) cg.spawnTime = cg.time; break; + case EV_LEV2_ZAP: + CG_Level2Zap( es ); + break; + default: - DEBUGNAME( "UNKNOWN" ); CG_Error( "Unknown event: %i", event ); break; } @@ -1031,4 +1210,3 @@ void CG_CheckEvents( centity_t *cent ) if( oldEvent != EV_NONE ) cent->currentState.event = oldEvent; } - diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index ea1694b..ecdf02d 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,17 +17,18 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ -#include "../qcommon/q_shared.h" -#include "../renderer/tr_types.h" -#include "../game/bg_public.h" +#include "qcommon/q_shared.h" +#include "renderercommon/tr_types.h" +#include "game/bg_public.h" #include "cg_public.h" -#include "../ui/ui_shared.h" +#include "binaryshader.h" +#include "ui/ui_shared.h" // The entire cgame module is unloaded and reloaded on each level change, // so there is NO persistant data between levels on the client side. @@ -70,6 +72,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define CHAR_HEIGHT 48 #define TEXT_ICON_SPACE 4 +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + // very large characters #define GIANT_WIDTH 32 #define GIANT_HEIGHT 48 @@ -79,10 +84,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TEAM_OVERLAY_MAXNAME_WIDTH 12 #define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 -#define DEFAULT_MODEL "sarge" -#define DEFAULT_TEAM_MODEL "sarge" -#define DEFAULT_TEAM_HEAD "sarge" - typedef enum { FOOTSTEP_NORMAL, @@ -207,7 +208,9 @@ typedef enum PMT_STATIC_TRANSFORM, PMT_TAG, PMT_CENT_ANGLES, - PMT_NORMAL + PMT_NORMAL, + PMT_LAST_NORMAL, + PMT_OPPORTUNISTIC_NORMAL } pMoveType_t; typedef enum @@ -253,7 +256,7 @@ typedef struct pLerpValues_s typedef struct baseParticle_s { vec3_t displacement; - float randDisplacement; + vec3_t randDisplacement; float normalDisplacement; pMoveType_t velMoveType; @@ -282,6 +285,7 @@ typedef struct baseParticle_s float bounceSoundCountRandFrac; pLerpValues_t radius; + int physicsRadius; pLerpValues_t alpha; pLerpValues_t rotation; @@ -317,6 +321,8 @@ typedef struct baseParticle_s qboolean overdrawProtection; qboolean realLight; qboolean cullOnStartSolid; + + float scaleWithCharge; } baseParticle_t; @@ -358,6 +364,11 @@ typedef struct particleSystem_s //for PMT_NORMAL qboolean normalValid; vec3_t normal; + //for PMT_LAST_NORMAL and PMT_OPPORTUNISTIC_NORMAL + qboolean lastNormalIsCurrent; + vec3_t lastNormal; + + int charge; } particleSystem_t; @@ -383,6 +394,8 @@ typedef struct particle_s baseParticle_t *class; particleEjector_t *parent; + particleSystem_t *childParticleSystem; + int birthTime; int lifeTime; @@ -463,7 +476,7 @@ typedef struct baseTrailBeam_s // the time it takes for a beam to fade out (double attached only) int fadeOutTime; - + char shaderName[ MAX_QPATH ]; qhandle_t shader; @@ -492,6 +505,7 @@ typedef struct baseTrailSystem_s baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ]; int numBeams; + int lifeTime; qboolean thirdPersonOnly; qboolean registered; //whether or not the assets for this trail have been loaded } baseTrailSystem_t; @@ -503,6 +517,7 @@ typedef struct trailSystem_s attachment_t frontAttachment; attachment_t backAttachment; + int birthTime; int destroyTime; qboolean valid; } trailSystem_t; @@ -551,7 +566,7 @@ typedef struct trailBeam_s // because corpses after respawn are outside the normal // client numbering range -//TA: smoothing of view and model for WW transitions +// smoothing of view and model for WW transitions #define MAXSMOOTHS 32 typedef struct @@ -566,7 +581,7 @@ typedef struct typedef struct { - lerpFrame_t legs, torso, flag, nonseg; + lerpFrame_t legs, torso, nonseg, weapon; int painTime; int painDirection; // flip from 0 to 1 @@ -594,6 +609,16 @@ typedef struct buildableStatus_s qboolean visible; // Status is visble? } buildableStatus_t; +typedef struct buildableCache_s +{ + vec3_t cachedOrigin; // If any of the values differ from their + vec3_t cachedAngles; // cached versions, then the cache is invalid + vec3_t cachedNormal; + buildable_t cachedType; + vec3_t axis[ 3 ]; + vec3_t origin; +} buildableCache_t; + //================================================= // centity_t have a direct corespondence with gentity_t in the game, but @@ -634,12 +659,13 @@ typedef struct centity_s lerpFrame_t lerpFrame; - //TA: buildableAnimNumber_t buildableAnim; //persistant anim number buildableAnimNumber_t oldBuildableAnim; //to detect when new anims are set + qboolean buildableIdleAnim; //to check if new idle anim particleSystem_t *buildablePS; buildableStatus_t buildableStatus; - float lastBuildableHealthScale; + buildableCache_t buildableCache; // so we don't recalculate things + float lastBuildableHealth; int lastBuildableDamageSoundTime; lightFlareStatus_t lfs; @@ -656,16 +682,20 @@ typedef struct centity_s particleSystem_t *jetPackPS; jetPackState_t jetPackState; + particleSystem_t *poisonCloudedPS; + particleSystem_t *entityPS; qboolean entityPSMissing; - trailSystem_t *level2ZapTS[ 3 ]; + trailSystem_t *level2ZapTS[ LEVEL2_AREAZAP_MAX_TARGETS ]; + int level2ZapTime; trailSystem_t *muzzleTS; //used for the tesla and reactor int muzzleTSDeathTime; qboolean valid; - qboolean oldValid; + qboolean oldValid; + struct centity_s *nextLocation; } centity_t; @@ -706,41 +736,20 @@ typedef struct { qboolean infoValid; - char name[ MAX_QPATH ]; - pTeam_t team; - - int botSkill; // 0 = not bot, 1-5 = bot - - vec3_t color1; - vec3_t color2; + char name[ MAX_NAME_LENGTH ]; + team_t team; int score; // updated by score servercmds int location; // location index for team mode int health; // you only get this info about your teammates - int armor; - int curWeapon; - - int handicap; - int wins, losses; // in tourney mode - - int teamTask; // task in teamplay (offence/defence) - qboolean teamLeader; // true when this is a team leader - - int powerups; // so can display quad/flag status - - int medkitUsageTime; - int invulnerabilityStartTime; - int invulnerabilityStopTime; - - int breathPuffTime; + int upgrade; + int curWeaponClass; // sends current weapon for H, current class for A // when clientinfo is changed, the loading of models/skins/sounds // can be deferred until you are dead, to prevent hitches in // gameplay char modelName[ MAX_QPATH ]; char skinName[ MAX_QPATH ]; - char headModelName[ MAX_QPATH ]; - char headSkinName[ MAX_QPATH ]; qboolean newAnims; // true if using the new mission pack animations qboolean fixedlegs; // true if legs yaw is always the same as torso yaw @@ -771,6 +780,9 @@ typedef struct sfxHandle_t customFootsteps[ 4 ]; sfxHandle_t customMetalFootsteps[ 4 ]; + + char voice[ MAX_VOICE_NAME_LEN ]; + int voiceTime; } clientInfo_t; @@ -789,6 +801,7 @@ typedef struct weaponInfoMode_s qboolean usesSpriteMissle; qhandle_t missileSprite; int missileSpriteSize; + float missileSpriteCharge; qhandle_t missileParticleSystem; qhandle_t missileTrailSystem; qboolean missileRotates; @@ -799,7 +812,6 @@ typedef struct weaponInfoMode_s int missileAnimLooping; sfxHandle_t firingSound; - qboolean loopFireSound; qhandle_t muzzleParticleSystem; @@ -824,6 +836,13 @@ typedef struct weaponInfo_s qhandle_t barrelModel; qhandle_t flashModel; + qhandle_t weaponModel3rdPerson; + qhandle_t barrelModel3rdPerson; + qhandle_t flashModel3rdPerson; + + animation_t animations[ MAX_WEAPON_ANIMATIONS ]; + qboolean noDrift; + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag qhandle_t weaponIcon; @@ -869,7 +888,6 @@ typedef struct //====================================================================== -//TA: typedef struct { vec3_t alienBuildablePos[ MAX_GENTITIES ]; @@ -906,6 +924,9 @@ typedef struct #define NUM_SAVED_STATES ( CMD_BACKUP + 2 ) +// After this many msec the crosshair name fades out completely +#define CROSSHAIR_CLIENT_TIMEOUT 1000 + typedef struct { int clientFrame; // incremented each frame @@ -1000,19 +1021,14 @@ typedef struct int scoreFadeTime; char killerName[ MAX_NAME_LENGTH ]; char spectatorList[ MAX_STRING_CHARS ]; // list of names - int spectatorLen; // length of list - float spectatorWidth; // width in device units int spectatorTime; // next time to offset - int spectatorPaintX; // current paint x - int spectatorPaintX2; // current paint x - int spectatorOffset; // current offset from start - int spectatorPaintLen; // current offset from start + float spectatorOffset; // current offset from start // centerprinting int centerPrintTime; int centerPrintCharWidth; int centerPrintY; - char centerPrint[ 1024 ]; + char centerPrint[ MAX_STRING_CHARS ]; int centerPrintLines; // low ammo warning state @@ -1022,6 +1038,7 @@ typedef struct int lastKillTime; // crosshair client ID + int crosshairBuildable; int crosshairClientNum; int crosshairClientTime; @@ -1031,7 +1048,6 @@ typedef struct // attacking player int attackerTime; - int voiceTime; // reward medals int rewardStack; @@ -1052,8 +1068,7 @@ typedef struct int voiceChatBufferOut; // warmup countdown - int warmup; - int warmupCount; + int warmupTime; //========================== @@ -1083,8 +1098,7 @@ typedef struct float v_dmg_pitch; float v_dmg_roll; - vec3_t kick_angles; // weapon kicks - vec3_t kick_origin; + qboolean chaseFollow; // temp working variables for player view float bobfracsin; @@ -1099,25 +1113,25 @@ typedef struct char testModelBarrelName[MAX_QPATH]; qboolean testGun; - int spawnTime; //TA: fovwarp - int weapon1Time; //TA: time when BUTTON_ATTACK went t->f f->t - int weapon2Time; //TA: time when BUTTON_ATTACK2 went t->f f->t - int weapon3Time; //TA: time when BUTTON_USE_HOLDABLE went t->f f->t + int spawnTime; // fovwarp + int weapon1Time; // time when BUTTON_ATTACK went t->f f->t + int weapon2Time; // time when BUTTON_ATTACK2 went t->f f->t + int weapon3Time; // time when BUTTON_USE_HOLDABLE went t->f f->t qboolean weapon1Firing; qboolean weapon2Firing; qboolean weapon3Firing; int poisonedTime; - vec3_t lastNormal; //TA: view smoothage - vec3_t lastVangles; //TA: view smoothage - smooth_t sList[ MAXSMOOTHS ]; //TA: WW smoothing + vec3_t lastNormal; // view smoothage + vec3_t lastVangles; // view smoothage + smooth_t sList[ MAXSMOOTHS ]; // WW smoothing - int forwardMoveTime; //TA: for struggling + int forwardMoveTime; // for struggling int rightMoveTime; int upMoveTime; - float charModelFraction; //TA: loading percentages + float charModelFraction; // loading percentages float mediaFraction; float buildablesFraction; @@ -1129,23 +1143,32 @@ typedef struct int numConsoleLines; particleSystem_t *poisonCloudPS; + particleSystem_t *poisonCloudedPS; float painBlendValue; float painBlendTarget; + float healBlendValue; int lastHealth; + qboolean wasDeadLastFrame; int lastPredictedCommand; int lastServerTime; playerState_t savedPmoveStates[ NUM_SAVED_STATES ]; int stateHead, stateTail; int ping; + + float chargeMeterAlpha; + float chargeMeterValue; + qhandle_t lastHealthCross; + float healthCrossFade; + int nearUsableBuildable; + + int nextWeaponClickTime; + // binary shaders - by /dev/humancontroller + int numBinaryShadersUsed; + cgBinaryShaderSetting_t binaryShaderSettings[ NUM_BINARY_SHADERS ]; } cg_t; - -// all of the model, shader, and sound references that are -// loaded at gamestate time are stored in cgMedia_t -// Other media that can be tied to clients, weapons, or items are -// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t typedef struct { qhandle_t charsetShader; @@ -1168,6 +1191,7 @@ typedef struct qhandle_t scannerBlipShader; qhandle_t scannerLineShader; + qhandle_t teamOverlayShader; qhandle_t numberShaders[ 11 ]; @@ -1179,12 +1203,22 @@ typedef struct qhandle_t redBuildShader; qhandle_t humanSpawningShader; + // binary shaders + range markers + qhandle_t sphereModel; + qhandle_t sphericalCone64Model; + qhandle_t sphericalCone240Model; + + qhandle_t plainColorShader; + qhandle_t binaryAlpha1Shader; + cgMediaBinaryShader_t binaryShaders[ NUM_BINARY_SHADERS ]; + // disconnect qhandle_t disconnectPS; qhandle_t disconnectSound; // sounds sfxHandle_t tracerSound; + sfxHandle_t weaponEmptyClick; sfxHandle_t selectSound; sfxHandle_t footsteps[ FOOTSTEP_TOTAL ][ 4 ]; sfxHandle_t talkSound; @@ -1192,6 +1226,7 @@ typedef struct sfxHandle_t humanTalkSound; sfxHandle_t landSound; sfxHandle_t fallSound; + sfxHandle_t turretSpinupSound; sfxHandle_t hardBounceSound1; sfxHandle_t hardBounceSound2; @@ -1251,6 +1286,7 @@ typedef struct sfxHandle_t buildableRepairedSound; qhandle_t poisonCloudPS; + qhandle_t poisonCloudedPS; qhandle_t alienEvolvePS; qhandle_t alienAcidTubePS; @@ -1263,13 +1299,22 @@ typedef struct qhandle_t alienBleedPS; qhandle_t humanBleedPS; + qhandle_t alienBuildableBleedPS; + qhandle_t humanBuildableBleedPS; + qhandle_t teslaZapTS; sfxHandle_t lCannonWarningSound; + sfxHandle_t lCannonWarningSound2; qhandle_t buildWeaponTimerPie[ 8 ]; qhandle_t upgradeClassIconShader; + qhandle_t healthCross; + qhandle_t healthCross2X; + qhandle_t healthCross3X; + qhandle_t healthCrossMedkit; + qhandle_t healthCrossPoisoned; } cgMedia_t; typedef struct @@ -1319,17 +1364,12 @@ typedef struct char mapname[ MAX_QPATH ]; qboolean markDeconstruct; // Whether or not buildables are marked - int voteTime; - int voteYes; - int voteNo; - qboolean voteModified; // beep whenever changed - char voteString[ MAX_STRING_TOKENS ]; - - int teamVoteTime[ 2 ]; - int teamVoteYes[ 2 ]; - int teamVoteNo[ 2 ]; - qboolean teamVoteModified[ 2 ]; // beep whenever changed - char teamVoteString[ 2 ][ MAX_STRING_TOKENS ]; + int voteTime[ NUM_TEAMS ]; + int voteYes[ NUM_TEAMS ]; + int voteNo[ NUM_TEAMS ]; + char voteCaller[ NUM_TEAMS ][ MAX_NAME_LENGTH ]; + qboolean voteModified[ NUM_TEAMS ];// beep whenever changed + char voteString[ NUM_TEAMS ][ MAX_STRING_TOKENS ]; int levelStartTime; @@ -1337,22 +1377,13 @@ typedef struct qboolean newHud; - int alienBuildPoints; - int alienBuildPointsTotal; - int humanBuildPoints; - int humanBuildPointsTotal; - int humanBuildPointsPowered; - int alienStage; int humanStage; - int alienKills; - int humanKills; + int alienCredits; + int humanCredits; int alienNextStageThreshold; int humanNextStageThreshold; - int numAlienSpawns; - int numHumanSpawns; - // // locally derived information from gamestate // @@ -1367,7 +1398,9 @@ typedef struct clientInfo_t clientinfo[ MAX_CLIENTS ]; - //TA: corpse info + int teaminfoReceievedTime; + + // corpse info clientInfo_t corpseinfo[ MAX_CLIENTS ]; int cursorX; @@ -1383,57 +1416,69 @@ typedef struct // media cgMedia_t media; + + voice_t *voices; + clientList_t ignoreList; + + // Kill Message + char killMsgKillers[ TEAMCHAT_HEIGHT ][ 33*3+1 ]; + char killMsgVictims[ TEAMCHAT_HEIGHT ][ 33*3+1 ]; + int killMsgWeapons[ TEAMCHAT_HEIGHT ]; + int killMsgMsgTimes[ TEAMCHAT_HEIGHT ]; + int killMsgPos; + int killMsgLastPos; } cgs_t; +typedef struct +{ + char *cmd; + void ( *function )( void ); +} consoleCommand_t; + +typedef struct +{ + char *cmd; + void (*function)( int argNum ); +} consoleCommandCompletions_t; + //============================================================================== extern cgs_t cgs; extern cg_t cg; extern centity_t cg_entities[ MAX_GENTITIES ]; +extern displayContextDef_t cgDC; -//TA: weapon limit expanded: -//extern weaponInfo_t cg_weapons[MAX_WEAPONS]; extern weaponInfo_t cg_weapons[ 32 ]; -//TA: upgrade infos: extern upgradeInfo_t cg_upgrades[ 32 ]; -//TA: buildable infos: extern buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ]; extern markPoly_t cg_markPolys[ MAX_MARK_POLYS ]; +extern vmCvar_t cg_teslaTrailTime; extern vmCvar_t cg_centertime; extern vmCvar_t cg_runpitch; extern vmCvar_t cg_runroll; -extern vmCvar_t cg_bobup; -extern vmCvar_t cg_bobpitch; -extern vmCvar_t cg_bobroll; extern vmCvar_t cg_swingSpeed; extern vmCvar_t cg_shadows; -extern vmCvar_t cg_gibs; extern vmCvar_t cg_drawTimer; extern vmCvar_t cg_drawClock; extern vmCvar_t cg_drawFPS; extern vmCvar_t cg_drawDemoState; extern vmCvar_t cg_drawSnapshot; -extern vmCvar_t cg_draw3dIcons; -extern vmCvar_t cg_drawIcons; -extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawChargeBar; extern vmCvar_t cg_drawCrosshair; extern vmCvar_t cg_drawCrosshairNames; -extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_crosshairSize; extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlaySortMode; +extern vmCvar_t cg_teamOverlayMaxPlayers; extern vmCvar_t cg_teamOverlayUserinfo; -extern vmCvar_t cg_crosshairX; -extern vmCvar_t cg_crosshairY; -extern vmCvar_t cg_drawStatus; extern vmCvar_t cg_draw2D; extern vmCvar_t cg_animSpeed; extern vmCvar_t cg_debugAnim; extern vmCvar_t cg_debugPosition; extern vmCvar_t cg_debugEvents; -extern vmCvar_t cg_teslaTrailTime; -extern vmCvar_t cg_railTrailTime; extern vmCvar_t cg_errorDecay; extern vmCvar_t cg_nopredict; extern vmCvar_t cg_debugMove; @@ -1441,65 +1486,46 @@ extern vmCvar_t cg_noPlayerAnims; extern vmCvar_t cg_showmiss; extern vmCvar_t cg_footsteps; extern vmCvar_t cg_addMarks; -extern vmCvar_t cg_brassTime; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_drawGun; extern vmCvar_t cg_gun_frame; extern vmCvar_t cg_gun_x; extern vmCvar_t cg_gun_y; extern vmCvar_t cg_gun_z; -extern vmCvar_t cg_drawGun; -extern vmCvar_t cg_viewsize; extern vmCvar_t cg_tracerChance; extern vmCvar_t cg_tracerWidth; extern vmCvar_t cg_tracerLength; -extern vmCvar_t cg_autoswitch; -extern vmCvar_t cg_ignore; -extern vmCvar_t cg_simpleItems; -extern vmCvar_t cg_fov; -extern vmCvar_t cg_zoomFov; -extern vmCvar_t cg_thirdPersonRange; -extern vmCvar_t cg_thirdPersonAngle; extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPersonShoulderViewMode; +extern vmCvar_t cg_staticDeathCam; +extern vmCvar_t cg_thirdPersonPitchFollow; +extern vmCvar_t cg_thirdPersonRange; extern vmCvar_t cg_stereoSeparation; extern vmCvar_t cg_lagometer; -extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_drawSpeed; +extern vmCvar_t cg_maxSpeedTimeWindow; extern vmCvar_t cg_synchronousClients; extern vmCvar_t cg_stats; -extern vmCvar_t cg_forceModel; -extern vmCvar_t cg_buildScript; extern vmCvar_t cg_paused; extern vmCvar_t cg_blood; -extern vmCvar_t cg_predictItems; -extern vmCvar_t cg_deferPlayers; -extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamOverlayUserinfo; extern vmCvar_t cg_teamChatsOnly; extern vmCvar_t cg_noVoiceChats; extern vmCvar_t cg_noVoiceText; -extern vmCvar_t cg_scorePlum; +extern vmCvar_t cg_hudFiles; extern vmCvar_t cg_smoothClients; extern vmCvar_t pmove_fixed; extern vmCvar_t pmove_msec; -//extern vmCvar_t cg_pmove_fixed; -extern vmCvar_t cg_cameraOrbit; -extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_cameraMode; extern vmCvar_t cg_timescaleFadeEnd; extern vmCvar_t cg_timescaleFadeSpeed; extern vmCvar_t cg_timescale; -extern vmCvar_t cg_cameraMode; -extern vmCvar_t cg_smallFont; -extern vmCvar_t cg_bigFont; extern vmCvar_t cg_noTaunt; -extern vmCvar_t cg_noProjectileTrail; -extern vmCvar_t cg_oldRail; -extern vmCvar_t cg_oldRocket; -extern vmCvar_t cg_oldPlasma; -extern vmCvar_t cg_trueLightning; -extern vmCvar_t cg_creepRes; extern vmCvar_t cg_drawSurfNormal; extern vmCvar_t cg_drawBBOX; -extern vmCvar_t cg_debugAlloc; extern vmCvar_t cg_wwSmoothTime; -extern vmCvar_t cg_wwFollow; -extern vmCvar_t cg_wwToggle; +extern vmCvar_t cg_disableBlueprintErrors; extern vmCvar_t cg_depthSortParticles; extern vmCvar_t cg_bounceParticles; extern vmCvar_t cg_consoleLatency; @@ -1508,21 +1534,38 @@ extern vmCvar_t cg_debugParticles; extern vmCvar_t cg_debugTrails; extern vmCvar_t cg_debugPVS; extern vmCvar_t cg_disableWarningDialogs; +extern vmCvar_t cg_disableUpgradeDialogs; +extern vmCvar_t cg_disableBuildDialogs; +extern vmCvar_t cg_disableCommandDialogs; extern vmCvar_t cg_disableScannerPlane; extern vmCvar_t cg_tutorial; +extern vmCvar_t cg_rangeMarkerDrawSurface; +extern vmCvar_t cg_rangeMarkerDrawIntersection; +extern vmCvar_t cg_rangeMarkerDrawFrontline; +extern vmCvar_t cg_rangeMarkerSurfaceOpacity; +extern vmCvar_t cg_rangeMarkerLineOpacity; +extern vmCvar_t cg_rangeMarkerLineThickness; +extern vmCvar_t cg_rangeMarkerForBlueprint; +extern vmCvar_t cg_rangeMarkerBuildableTypes; +extern vmCvar_t cg_binaryShaderScreenScale; + extern vmCvar_t cg_painBlendUpRate; extern vmCvar_t cg_painBlendDownRate; extern vmCvar_t cg_painBlendMax; extern vmCvar_t cg_painBlendScale; extern vmCvar_t cg_painBlendZoom; -//TA: hack to get class an carriage through to UI module +extern vmCvar_t cg_stickySpec; +extern vmCvar_t cg_sprintToggle; +extern vmCvar_t cg_unlagged; + +extern vmCvar_t cg_debugVoices; + extern vmCvar_t ui_currentClass; extern vmCvar_t ui_carriage; extern vmCvar_t ui_stages; extern vmCvar_t ui_dialog; -extern vmCvar_t ui_loading; extern vmCvar_t ui_voteActive; extern vmCvar_t ui_alienTeamVoteActive; extern vmCvar_t ui_humanTeamVoteActive; @@ -1531,7 +1574,22 @@ extern vmCvar_t cg_debugRandom; extern vmCvar_t cg_optimizePrediction; extern vmCvar_t cg_projectileNudge; -extern vmCvar_t cg_unlagged; + +extern vmCvar_t cg_voice; + +extern vmCvar_t cg_emoticons; + +extern vmCvar_t cg_chatTeamPrefix; + +extern vmCvar_t cg_killMsg; +extern vmCvar_t cg_killMsgTime; +extern vmCvar_t cg_killMsgHeight; + +extern vmCvar_t cg_killMsgTime; +extern vmCvar_t cg_killMsgHeight; + +extern vmCvar_t thz_radar; +extern vmCvar_t thz_radarrange; // // cg_main.c @@ -1539,8 +1597,8 @@ extern vmCvar_t cg_unlagged; const char *CG_ConfigString( int index ); const char *CG_Argv( int arg ); -void QDECL CG_Printf( const char *msg, ... ); -void QDECL CG_Error( const char *msg, ... ); +void QDECL CG_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL CG_Error( const char *msg, ... ) __attribute__ ((noreturn, format (printf, 1, 2))); void CG_StartMusic( void ); int CG_PlayerCount( void ); @@ -1554,17 +1612,22 @@ void CG_KeyEvent( int key, qboolean down ); void CG_MouseEvent( int x, int y ); void CG_EventHandling( int type ); void CG_SetScoreSelection( void *menu ); +qboolean CG_ClientIsReady( int clientNum ); void CG_BuildSpectatorString( void ); -qboolean CG_FileExists( char *filename ); +qboolean CG_FileExists( const char *filename ); void CG_RemoveNotifyLine( void ); void CG_AddNotifyText( void ); +qboolean CG_GetRangeMarkerPreferences( qboolean *drawSurface, qboolean *drawIntersection, + qboolean *drawFrontline, float *surfaceOpacity, + float *lineOpacity, float *lineThickness ); +void CG_UpdateBuildableRangeMarkerMask( void ); // // cg_view.c // -void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); //TA +void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); void CG_TestModel_f( void ); void CG_TestGun_f( void ); void CG_TestModelNextFrame_f( void ); @@ -1573,6 +1636,9 @@ void CG_TestModelNextSkin_f( void ); void CG_TestModelPrevSkin_f( void ); void CG_AddBufferedSound( sfxHandle_t sfx ); void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +void CG_OffsetFirstPersonView( void ); +void CG_OffsetThirdPersonView( void ); +void CG_OffsetShoulderView( void ); // @@ -1584,37 +1650,41 @@ void CG_FillRect( float x, float y, float width, float height, const floa void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor, vec4_t tcolor, float amount, qhandle_t hShader ); +void CG_SetClipRegion( float x, float y, float w, float h ); +void CG_ClearClipRegion( void ); int CG_DrawStrlen( const char *str ); float *CG_FadeColor( int startMsec, int totalMsec ); void CG_TileClear( void ); void CG_ColorForHealth( vec4_t hcolor ); -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); - void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); void CG_DrawSides(float x, float y, float w, float h, float size); void CG_DrawTopBottom(float x, float y, float w, float h, float size); qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ); char *CG_KeyBinding( const char *bind ); - +char CG_GetColorCharForHealth( int clientnum ); +void CG_DrawSphere( const vec3_t center, float radius, int customShader, const float *shaderRGBA ); +void CG_DrawSphericalCone( const vec3_t tip, const vec3_t rotation, float radius, + qboolean a240, int customShader, const float *shaderRGBA ); +void CG_DrawRangeMarker( rangeMarkerType_t rmType, const vec3_t origin, const float *angles, float range, + qboolean drawSurface, qboolean drawIntersection, qboolean drawFrontline, + const vec3_t rgb, float surfaceOpacity, float lineOpacity, float lineThickness ); // // cg_draw.c // -extern int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; -extern int numSortedTeamPlayers; void CG_AddLagometerFrameInfo( void ); void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_AddSpeed( void ); void CG_CenterPrint( const char *str, int y, int charWidth ); void CG_DrawActive( stereoFrame_t stereoView ); -void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, - int ownerDraw, int ownerDrawFlags, int align, float special, - float scale, vec4_t color, qhandle_t shader, int textStyle); -void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); -int CG_Text_Width( const char *text, float scale, int limit ); -int CG_Text_Height( const char *text, float scale, int limit ); +void CG_OwnerDraw( float x, float y, float w, float h, float text_x, + float text_y, int ownerDraw, int ownerDrawFlags, + int align, int textalign, int textvalign, + float borderSize, float scale, vec4_t foreColor, + vec4_t backColor, qhandle_t shader, int textStyle ); float CG_GetValue(int ownerDraw); void CG_RunMenuScript(char **args); void CG_SetPrintString( int type, const char *p ); @@ -1633,13 +1703,12 @@ void CG_DrawField( float x, float y, int width, float cw, float ch, int v void CG_Player( centity_t *cent ); void CG_Corpse( centity_t *cent ); void CG_ResetPlayerEntity( centity_t *cent ); -void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team ); void CG_NewClientInfo( int clientNum ); -void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ); +void CG_PrecacheClientInfo( class_t class, char *model, char *skin ); sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); void CG_PlayerDisconnect( vec3_t org ); void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ); -qboolean CG_AtHighestClass( void ); +centity_t *CG_GetPlayerLocation( void ); // // cg_buildable.c @@ -1651,11 +1720,12 @@ void CG_DrawBuildableStatus( void ); void CG_InitBuildables( void ); void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ); void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ); +qboolean CG_GetBuildableRangeMarkerProperties( buildable_t bType, rangeMarkerType_t *rmType, float *range, vec3_t rgb ); // // cg_animation.c // -void CG_RunLerpFrame( lerpFrame_t *lf ); +void CG_RunLerpFrame( lerpFrame_t *lf, float scale ); // // cg_animmapobj.c @@ -1687,6 +1757,7 @@ void CG_CheckEvents( centity_t *cent ); void CG_EntityEvent( centity_t *cent, vec3_t position ); void CG_PainEvent( centity_t *cent, int health ); +void CG_AddToKillMsg( const char* killername, const char* victimname, int icon ); // // cg_ents.c @@ -1701,8 +1772,7 @@ void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *pare qhandle_t parentModel, char *tagName ); void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ); - - +void CG_RangeMarker( centity_t *cent ); // @@ -1719,8 +1789,9 @@ void CG_RegisterWeapon( int weaponNum ); void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode ); void CG_MissileHitWall( weapon_t weapon, weaponMode_t weaponMode, int clientNum, - vec3_t origin, vec3_t dir, impactSound_t soundType ); -void CG_MissileHitPlayer( weapon_t weapon, weaponMode_t weaponMode, vec3_t origin, vec3_t dir, int entityNum ); + vec3_t origin, vec3_t dir, impactSound_t soundType, int charge ); +void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode, + vec3_t origin, vec3_t dir, int entityNum, int charge ); void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); void CG_ShotgunFire( entityState_t *es ); @@ -1736,6 +1807,7 @@ void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) void CG_UpdateEntityPositions( void ); void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ); void CG_AlienSense( rectDef_t *rect ); +void THZ_DrawScanner( rectDef_t *rect ); // // cg_marks.c @@ -1757,6 +1829,7 @@ void CG_ProcessSnapshots( void ); // // cg_consolecmds.c // +qboolean CG_Console_CompleteArgument( int argNum ); qboolean CG_ConsoleCommand( void ); void CG_InitConsoleCommands( void ); qboolean CG_RequestScores( void ); @@ -1768,6 +1841,8 @@ void CG_ExecuteNewServerCommands( int latestSequence ); void CG_ParseServerinfo( void ); void CG_SetConfigValues( void ); void CG_ShaderStateChanged(void); +void CG_UnregisterCommands( void ); +void CG_CenterPrint_f( void ); // // cg_playerstate.c @@ -1776,14 +1851,6 @@ void CG_Respawn( void ); void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); void CG_CheckChangedPredictableEvents( playerState_t *ps ); -// -// cg_mem.c -// -void CG_InitMemory( void ); -void *CG_Alloc( int size ); -void CG_Free( void *ptr ); -void CG_DefragmentMemory( void ); - // // cg_attachment.c // @@ -1820,6 +1887,7 @@ qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps ); qboolean CG_IsParticleSystemValid( particleSystem_t **ps ); void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ); +void CG_SetParticleSystemLastNormal( particleSystem_t *ps, const float *normal ); void CG_AddParticles( void ); @@ -1855,6 +1923,20 @@ void CG_WritePTRCode( int code ); // const char *CG_TutorialText( void ); +// cg_main.c +qboolean CG_GetRangeMarkerPreferences( qboolean *drawSurface, qboolean *drawIntersection, + qboolean *drawFrontline, float *surfaceOpacity, + float *lineOpacity, float *lineThickness ); +// cg_drawtools.c +void CG_DrawRangeMarker( rangeMarkerType_t rmType, const vec3_t origin, const float *angles, float range, + qboolean drawSurface, qboolean drawIntersection, qboolean drawFrontline, + const vec3_t rgb, float surfaceOpacity, float lineOpacity, float lineThickness ); +// cg_buildable.c +qboolean CG_GetBuildableRangeMarkerProperties( buildable_t bType, rangeMarkerType_t *rmType, float *range, vec3_t rgb ); +void CG_GhostBuildableRangeMarker( buildable_t buildable, const vec3_t origin, const vec3_t normal ); +// cg_ents.c +void CG_RangeMarker( centity_t *cent ); + // //=============================================== @@ -1868,7 +1950,7 @@ const char *CG_TutorialText( void ); void trap_Print( const char *fmt ); // abort the game -void trap_Error( const char *fmt ); +void trap_Error( const char *fmt ) __attribute__((noreturn)); // milliseconds should only be used for performance tuning, never // for anything game related. Get time from the CG_DrawActiveFrame parameter @@ -1888,11 +1970,11 @@ void trap_LiteralArgs( char *buffer, int bufferLength ); // filesystem access // returns length of file -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ); void trap_FS_Read( void *buffer, int len, fileHandle_t f ); void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); -void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +void trap_FS_Seek( fileHandle_t f, long offset, enum FS_Origin origin ); // fsOrigin_t int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); @@ -1905,6 +1987,7 @@ void trap_SendConsoleCommand( const char *text ); // register a command name so the console can perform command completion. // FIXME: replace this with a normal console command "defineCommand"? void trap_AddCommand( const char *cmdName ); +void trap_RemoveCommand( const char *cmdName ); // send a string to the server over the network void trap_SendClientCommand( const char *s ); @@ -1985,11 +2068,13 @@ void trap_R_AddRefEntityToScene( const refEntity_t *re ); // significant construction void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ); void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); void trap_R_RenderScene( const refdef_t *fd ); void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_SetClipRegion( const float *region ); void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); @@ -2180,6 +2265,8 @@ int trap_Key_GetKey( const char *binding ); void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); void trap_Key_SetBinding( int keynum, const char *binding ); +void trap_Key_SetOverstrikeMode( qboolean state ); +qboolean trap_Key_GetOverstrikeMode( void ); int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); e_status trap_CIN_StopCinematic( int handle ); @@ -2199,9 +2286,19 @@ qboolean trap_GetEntityToken( char *buffer, int bufferSize ); int trap_GetDemoState( void ); int trap_GetDemoPos( void ); void trap_GetDemoName( char *buffer, int size ); +void trap_Field_CompleteList( char *listJson ); // cg_drawCrosshair settings #define CROSSHAIR_ALWAYSOFF 0 #define CROSSHAIR_RANGEDONLY 1 #define CROSSHAIR_ALWAYSON 2 +// menu types for cg_disable*Dialogs +typedef enum +{ + DT_INTERACTIVE, // team, class, armoury + DT_ARMOURYEVOLVE, // Insufficient funds et al + DT_BUILD, // build errors + DT_COMMAND, // You must be living/human/spec etc. + +} dialogType_t; diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c index eb19dcb..7ec4ae9 100644 --- a/src/cgame/cg_main.c +++ b/src/cgame/cg_main.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,25 +17,22 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_main.c -- initialization and primary entry point for cgame - #include "cg_local.h" +#include "ui/ui_shared.h" -#include "../ui/ui_shared.h" // display context for new ui stuff displayContextDef_t cgDC; -int forceModelModificationCount = -1; - void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); void CG_Shutdown( void ); -char *CG_VoIPString( void ); +static char *CG_VoIPString( void ); /* ================ @@ -44,9 +42,7 @@ This is the only way control passes into the module. This must be the very first function compiled into the .q3vm file ================ */ -Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, - int arg4, int arg5, int arg6, int arg7, - int arg8, int arg9, int arg10, int arg11 ) +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2 ) { switch( command ) { @@ -80,9 +76,7 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, return 0; case CG_MOUSE_EVENT: - cgDC.cursorx = cgs.cursorX; - cgDC.cursory = cgs.cursorY; - CG_MouseEvent( arg0, arg1 ); + // cgame doesn't care where the cursor is return 0; case CG_EVENT_HANDLING: @@ -92,6 +86,9 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, #ifndef MODULE_INTERFACE_11 case CG_VOIP_STRING: return (intptr_t)CG_VoIPString( ); + + case CG_CONSOLE_COMPLETARGUMENT: + return CG_Console_CompleteArgument( arg0 ); #endif default: @@ -107,39 +104,27 @@ cg_t cg; cgs_t cgs; centity_t cg_entities[ MAX_GENTITIES ]; -//TA: weapons limit expanded: -//weaponInfo_t cg_weapons[MAX_WEAPONS]; weaponInfo_t cg_weapons[ 32 ]; upgradeInfo_t cg_upgrades[ 32 ]; buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ]; vmCvar_t cg_teslaTrailTime; -vmCvar_t cg_railTrailTime; vmCvar_t cg_centertime; vmCvar_t cg_runpitch; vmCvar_t cg_runroll; -vmCvar_t cg_bobup; -vmCvar_t cg_bobpitch; -vmCvar_t cg_bobroll; vmCvar_t cg_swingSpeed; vmCvar_t cg_shadows; -vmCvar_t cg_gibs; vmCvar_t cg_drawTimer; vmCvar_t cg_drawClock; vmCvar_t cg_drawFPS; vmCvar_t cg_drawDemoState; vmCvar_t cg_drawSnapshot; -vmCvar_t cg_draw3dIcons; -vmCvar_t cg_drawIcons; -vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawChargeBar; vmCvar_t cg_drawCrosshair; vmCvar_t cg_drawCrosshairNames; -vmCvar_t cg_drawRewards; -vmCvar_t cg_crosshairX; -vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairSize; vmCvar_t cg_draw2D; -vmCvar_t cg_drawStatus; vmCvar_t cg_animSpeed; vmCvar_t cg_debugAnim; vmCvar_t cg_debugPosition; @@ -151,7 +136,6 @@ vmCvar_t cg_noPlayerAnims; vmCvar_t cg_showmiss; vmCvar_t cg_footsteps; vmCvar_t cg_addMarks; -vmCvar_t cg_brassTime; vmCvar_t cg_viewsize; vmCvar_t cg_drawGun; vmCvar_t cg_gun_frame; @@ -161,59 +145,41 @@ vmCvar_t cg_gun_z; vmCvar_t cg_tracerChance; vmCvar_t cg_tracerWidth; vmCvar_t cg_tracerLength; -vmCvar_t cg_autoswitch; -vmCvar_t cg_ignore; -vmCvar_t cg_simpleItems; -vmCvar_t cg_fov; -vmCvar_t cg_zoomFov; vmCvar_t cg_thirdPerson; -vmCvar_t cg_thirdPersonRange; vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_thirdPersonShoulderViewMode; +vmCvar_t cg_staticDeathCam; +vmCvar_t cg_thirdPersonPitchFollow; +vmCvar_t cg_thirdPersonRange; vmCvar_t cg_stereoSeparation; vmCvar_t cg_lagometer; -vmCvar_t cg_drawAttacker; +vmCvar_t cg_drawSpeed; +vmCvar_t cg_maxSpeedTimeWindow; vmCvar_t cg_synchronousClients; vmCvar_t cg_stats; -vmCvar_t cg_buildScript; -vmCvar_t cg_forceModel; vmCvar_t cg_paused; vmCvar_t cg_blood; -vmCvar_t cg_predictItems; -vmCvar_t cg_deferPlayers; +vmCvar_t cg_teamChatsOnly; vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlaySortMode; +vmCvar_t cg_teamOverlayMaxPlayers; vmCvar_t cg_teamOverlayUserinfo; -vmCvar_t cg_drawFriend; -vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noPrintDuplicate; vmCvar_t cg_noVoiceChats; vmCvar_t cg_noVoiceText; vmCvar_t cg_hudFiles; -vmCvar_t cg_scorePlum; vmCvar_t cg_smoothClients; vmCvar_t pmove_fixed; -//vmCvar_t cg_pmove_fixed; vmCvar_t pmove_msec; -vmCvar_t cg_pmove_msec; vmCvar_t cg_cameraMode; -vmCvar_t cg_cameraOrbit; -vmCvar_t cg_cameraOrbitDelay; vmCvar_t cg_timescaleFadeEnd; vmCvar_t cg_timescaleFadeSpeed; vmCvar_t cg_timescale; -vmCvar_t cg_smallFont; -vmCvar_t cg_bigFont; vmCvar_t cg_noTaunt; -vmCvar_t cg_noProjectileTrail; -vmCvar_t cg_oldRail; -vmCvar_t cg_oldRocket; -vmCvar_t cg_oldPlasma; -vmCvar_t cg_trueLightning; -vmCvar_t cg_creepRes; vmCvar_t cg_drawSurfNormal; vmCvar_t cg_drawBBOX; -vmCvar_t cg_debugAlloc; vmCvar_t cg_wwSmoothTime; -vmCvar_t cg_wwFollow; -vmCvar_t cg_wwToggle; +vmCvar_t cg_disableBlueprintErrors; vmCvar_t cg_depthSortParticles; vmCvar_t cg_bounceParticles; vmCvar_t cg_consoleLatency; @@ -222,21 +188,38 @@ vmCvar_t cg_debugParticles; vmCvar_t cg_debugTrails; vmCvar_t cg_debugPVS; vmCvar_t cg_disableWarningDialogs; +vmCvar_t cg_disableUpgradeDialogs; +vmCvar_t cg_disableBuildDialogs; +vmCvar_t cg_disableCommandDialogs; vmCvar_t cg_disableScannerPlane; vmCvar_t cg_tutorial; +vmCvar_t cg_rangeMarkerDrawSurface; +vmCvar_t cg_rangeMarkerDrawIntersection; +vmCvar_t cg_rangeMarkerDrawFrontline; +vmCvar_t cg_rangeMarkerSurfaceOpacity; +vmCvar_t cg_rangeMarkerLineOpacity; +vmCvar_t cg_rangeMarkerLineThickness; +vmCvar_t cg_rangeMarkerForBlueprint; +vmCvar_t cg_rangeMarkerBuildableTypes; +vmCvar_t cg_binaryShaderScreenScale; + vmCvar_t cg_painBlendUpRate; vmCvar_t cg_painBlendDownRate; vmCvar_t cg_painBlendMax; vmCvar_t cg_painBlendScale; vmCvar_t cg_painBlendZoom; -//TA: hack to get class and carriage through to UI module +vmCvar_t cg_stickySpec; +vmCvar_t cg_sprintToggle; +vmCvar_t cg_unlagged; + +vmCvar_t cg_debugVoices; + vmCvar_t ui_currentClass; vmCvar_t ui_carriage; vmCvar_t ui_stages; vmCvar_t ui_dialog; -vmCvar_t ui_loading; vmCvar_t ui_voteActive; vmCvar_t ui_alienTeamVoteActive; vmCvar_t ui_humanTeamVoteActive; @@ -245,8 +228,19 @@ vmCvar_t cg_debugRandom; vmCvar_t cg_optimizePrediction; vmCvar_t cg_projectileNudge; -vmCvar_t cg_unlagged; +vmCvar_t cg_voice; + +vmCvar_t cg_emoticons; + +vmCvar_t cg_chatTeamPrefix; + +vmCvar_t cg_killMsg; +vmCvar_t cg_killMsgTime; +vmCvar_t cg_killMsgHeight; + +vmCvar_t thz_radar; +vmCvar_t thz_radarrange; typedef struct { @@ -258,46 +252,31 @@ typedef struct static cvarTable_t cvarTable[ ] = { - { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging - { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, - { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, - { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, - { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, - { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE }, { &cg_drawClock, "cg_drawClock", "0", CVAR_ARCHIVE }, { &cg_drawFPS, "cg_drawFPS", "1", CVAR_ARCHIVE }, { &cg_drawDemoState, "cg_drawDemoState", "1", CVAR_ARCHIVE }, { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, - { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, - { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, - { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, - { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, - { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawChargeBar, "cg_drawChargeBar", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "2", CVAR_ARCHIVE }, { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, - { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, - { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, - { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, - { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, - { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "1", CVAR_ARCHIVE }, { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_drawSpeed, "cg_drawSpeed", "0", CVAR_ARCHIVE }, + { &cg_maxSpeedTimeWindow, "cg_maxSpeedTimeWindow", "2000", CVAR_ARCHIVE }, { &cg_teslaTrailTime, "cg_teslaTrailTime", "250", CVAR_ARCHIVE }, - { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, - { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, - { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, - { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, @@ -312,27 +291,31 @@ static cvarTable_t cvarTable[ ] = { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, - { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, - { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "75", CVAR_ARCHIVE }, { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT }, - { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, - { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, - { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, - { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, - { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitchFollow, "cg_thirdPersonPitchFollow", "0", 0 }, + { &cg_thirdPersonShoulderViewMode, "cg_thirdPersonShoulderViewMode", "1", CVAR_ARCHIVE }, + { &cg_staticDeathCam, "cg_staticDeathCam", "0", CVAR_ARCHIVE }, { &cg_stats, "cg_stats", "0", 0 }, - { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "1", CVAR_ARCHIVE }, + { &cg_teamOverlaySortMode, "cg_teamOverlaySortMode", "1", CVAR_ARCHIVE }, + { &cg_teamOverlayMaxPlayers, "cg_teamOverlayMaxPlayers", "8", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "1", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noPrintDuplicate, "cg_noPrintDuplicate", "0", CVAR_ARCHIVE }, { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, - { &cg_creepRes, "cg_creepRes", "16", CVAR_ARCHIVE }, { &cg_drawSurfNormal, "cg_drawSurfNormal", "0", CVAR_CHEAT }, { &cg_drawBBOX, "cg_drawBBOX", "0", CVAR_CHEAT }, - { &cg_debugAlloc, "cg_debugAlloc", "0", 0 }, { &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE }, - { &cg_wwFollow, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO }, - { &cg_wwToggle, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_disableBlueprintErrors", "0", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_stickySpec, "cg_stickySpec", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_sprintToggle, "cg_sprintToggle", "0", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_unlagged, "cg_unlagged", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_flySpeed", "600", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE }, { &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE }, { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE }, @@ -341,24 +324,44 @@ static cvarTable_t cvarTable[ ] = { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT }, { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT }, { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE }, - { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE }, + { &cg_disableUpgradeDialogs, "cg_disableUpgradeDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableBuildDialogs, "cg_disableBuildDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableCommandDialogs, "cg_disableCommandDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableScannerPlane, "cg_disableScannerPlane", "1", CVAR_ARCHIVE }, { &cg_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE }, + + { &cg_rangeMarkerDrawSurface, "cg_rangeMarkerDrawSurface", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerDrawIntersection, "cg_rangeMarkerDrawIntersection", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerDrawFrontline, "cg_rangeMarkerDrawFrontline", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerSurfaceOpacity, "cg_rangeMarkerSurfaceOpacity", "0.08", CVAR_ARCHIVE }, + { &cg_rangeMarkerLineOpacity, "cg_rangeMarkerLineOpacity", "0.4", CVAR_ARCHIVE }, + { &cg_rangeMarkerLineThickness, "cg_rangeMarkerLineThickness", "4.0", CVAR_ARCHIVE }, + { &cg_rangeMarkerForBlueprint, "cg_rangeMarkerForBlueprint", "1", CVAR_ARCHIVE }, + { &cg_rangeMarkerBuildableTypes, "cg_rangeMarkerBuildableTypes", "support", CVAR_ARCHIVE }, + { NULL, "cg_buildableRangeMarkerMask", "", CVAR_USERINFO }, + { &cg_binaryShaderScreenScale, "cg_binaryShaderScreenScale", "1.0", CVAR_ARCHIVE }, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, + { NULL, "cg_alienConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_humanConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_spectatorConfig", "", CVAR_ARCHIVE }, { &cg_painBlendUpRate, "cg_painBlendUpRate", "10.0", 0 }, { &cg_painBlendDownRate, "cg_painBlendDownRate", "0.5", 0 }, { &cg_painBlendMax, "cg_painBlendMax", "0.7", 0 }, { &cg_painBlendScale, "cg_painBlendScale", "7.0", 0 }, { &cg_painBlendZoom, "cg_painBlendZoom", "0.65", 0 }, + + { &cg_debugVoices, "cg_debugVoices", "0", 0 }, - { &ui_currentClass, "ui_currentClass", "0", 0 }, - { &ui_carriage, "ui_carriage", "", 0 }, - { &ui_stages, "ui_stages", "0 0", 0 }, - { &ui_dialog, "ui_dialog", "Text not set", 0 }, - { &ui_loading, "ui_loading", "0", 0 }, - { &ui_voteActive, "ui_voteActive", "0", 0 }, - { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", 0 }, - { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", 0 }, + // communication cvars set by the cgame to be read by ui + { &ui_currentClass, "ui_currentClass", "0", CVAR_ROM }, + { &ui_carriage, "ui_carriage", "", CVAR_ROM }, + { &ui_stages, "ui_stages", "0 0", CVAR_ROM }, + { &ui_dialog, "ui_dialog", "Text not set", CVAR_ROM }, + { &ui_voteActive, "ui_voteActive", "0", CVAR_ROM }, + { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", CVAR_ROM }, + { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", CVAR_ROM }, { &cg_debugRandom, "cg_debugRandom", "0", 0 }, @@ -368,33 +371,36 @@ static cvarTable_t cvarTable[ ] = // the following variables are created in other parts of the system, // but we also reference them here - { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures { &cg_paused, "cl_paused", "0", CVAR_ROM }, - { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_blood, "cg_blood", "1", CVAR_ARCHIVE }, { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo - { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, - { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, - { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, - { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", CVAR_CHEAT }, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", CVAR_CHEAT }, { &cg_timescale, "timescale", "1", 0}, - { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, { &pmove_fixed, "pmove_fixed", "0", 0}, { &pmove_msec, "pmove_msec", "8", 0}, { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, - { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, - { &cg_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE}, - { &cg_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE}, - { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, - { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, - { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, - { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} -// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } + + { &cg_voice, "voice", "default", CVAR_USERINFO|CVAR_ARCHIVE}, + + { &cg_emoticons, "cg_emoticons", "1", CVAR_LATCH|CVAR_ARCHIVE}, + + { &cg_chatTeamPrefix, "cg_chatTeamPrefix", "1", CVAR_ARCHIVE}, + + { &cg_killMsg, "cg_killMsg", "1", CVAR_ARCHIVE }, + { &cg_killMsgTime, "cg_killMsgTime", "4000", CVAR_ARCHIVE }, + { &cg_killMsgHeight, "cg_killMsgHeight", "7", CVAR_ARCHIVE }, + + // Old school thz stuff + { &thz_radar, "thz_radar", "0", CVAR_CHEAT}, + { &thz_radarrange, "thz_radarrange", "600", CVAR_ARCHIVE}, + }; -static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); +static size_t cvarTableSize = ARRAY_LEN( cvarTable ); /* ================= @@ -413,263 +419,98 @@ void CG_RegisterCvars( void ) cv->defaultString, cv->cvarFlags ); } - //repress standard Q3 console - trap_Cvar_Set( "con_notifytime", "-2" ); - // see if we are also running the server on this machine trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); cgs.localServer = atoi( var ); - forceModelModificationCount = cg_forceModel.modificationCount; - - trap_Cvar_Register( NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register( NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register( NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register( NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); } -/* -=================== -CG_ForceModelChange -=================== -*/ -static void CG_ForceModelChange( void ) -{ - int i; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - const char *clientInfo; - - clientInfo = CG_ConfigString( CS_PLAYERS + i ); - - if( !clientInfo[ 0 ] ) - continue; - - CG_NewClientInfo( i ); - } -} - /* =============== -CG_SetPVars +CG_SetUIVars -Set the p_* cvars +Set some cvars used by the UI =============== */ -static void CG_SetPVars( void ) +static void CG_SetUIVars( void ) { - playerState_t *ps; + int i; + char carriageCvar[ MAX_TOKEN_CHARS ]; if( !cg.snap ) return; - ps = &cg.snap->ps; - - trap_Cvar_Set( "player_hp", va( "%d", ps->stats[ STAT_HEALTH ] ) ); - trap_Cvar_Set( "player_maxhp", va( "%d", ps->stats[ STAT_MAX_HEALTH ] ) ); - switch( ps->stats[ STAT_PTEAM ] ) - { - case PTE_NONE: - trap_Cvar_Set( "player_team", "spectator" ); - trap_Cvar_Set( "player_stage", "0" ); - trap_Cvar_Set( "player_spawns","0" ); - trap_Cvar_Set( "player_kns", "0" ); - trap_Cvar_Set( "player_bp", "0" ); - trap_Cvar_Set( "player_maxbp", "0" ); - break; - - case PTE_ALIENS: - trap_Cvar_Set( "player_team", "alien" ); - trap_Cvar_Set( "player_stage", va( "%d", cgs.alienStage+1 ) ); - trap_Cvar_Set( "player_spawns",va( "%d", cgs.numAlienSpawns )); - trap_Cvar_Set( "player_kns", va( "%d",((cgs.alienStage==2)?0:abs(cgs.alienNextStageThreshold-cgs.alienKills)))); - trap_Cvar_Set( "player_stage", va( "%d", cgs.alienStage+1 ) ); - trap_Cvar_Set( "player_bp", va( "%d", cgs.alienBuildPoints )); - trap_Cvar_Set( "player_maxbp", va( "%d", cgs.alienBuildPointsTotal )); - break; - - case PTE_HUMANS: - trap_Cvar_Set( "player_team", "human" ); - trap_Cvar_Set( "player_stage", va( "%d", cgs.humanStage+1 ) ); - trap_Cvar_Set( "player_spawns",va( "%d", cgs.numHumanSpawns )); - trap_Cvar_Set( "player_kns", va( "%d",((cgs.humanStage==2)?0:abs(cgs.humanNextStageThreshold-cgs.humanKills)))); - trap_Cvar_Set( "player_bp", va( "%d", cgs.humanBuildPoints )); - trap_Cvar_Set( "player_maxbp", va( "%d", cgs.humanBuildPointsTotal )); - break; - } + *carriageCvar = 0; - trap_Cvar_Set( "player_credits", va( "%d", ps->persistant[ PERS_CREDIT ] ) ); - trap_Cvar_Set( "player_score", va( "%d", ps->persistant[ PERS_SCORE ] ) ); - trap_Cvar_Set( "player_deaths", va( "%d", ps->persistant[ PERS_KILLED ] ) ); + //determine what the player is carrying + if( BG_Weapon( cg.snap->ps.stats[ STAT_WEAPON ] )->purchasable ) + strcat( carriageCvar, va( "W%d ", cg.snap->ps.stats[ STAT_WEAPON ] ) ); - if ( CG_LastAttacker( ) != -1 ) + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { - trap_Cvar_Set( "player_attacker", cgs.clientinfo[ CG_LastAttacker( ) ].name ); - trap_Cvar_Set( "player_attacker_hp", va( "%d", cgs.clientinfo[ CG_LastAttacker( ) ].health)); + if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && + BG_Upgrade( i )->purchasable ) + strcat( carriageCvar, va( "U%d ", i ) ); } - else - { - trap_Cvar_Set( "player_attacker", "" ); - trap_Cvar_Set( "player_attacker_hp", "" ); - } - + strcat( carriageCvar, "$" ); - if ( CG_CrosshairPlayer( ) != -1 ) - { - trap_Cvar_Set( "player_crosshair", cgs.clientinfo[ CG_CrosshairPlayer( ) ].name ); - //XXX hax required - //trap_Cvar_Set( "player_crosshair_credits", va("%d",cgs.clientinfo[CG_CrosshairPlayer( )].credits)); - } - else - { - trap_Cvar_Set( "player_crosshair", "" ); - //trap_Cvar_Set( "player_crosshair_credits", "" ); - } - - // stages - trap_Cvar_Set( "alien_stage", va( "%d", cgs.alienStage+1 ) ); - trap_Cvar_Set( "human_stage", va( "%d", cgs.humanStage+1 ) ); - - // alien kills to next stage - if( cgs.alienStage == 2 ) - trap_Cvar_Set( "alien_kns", va( "%d", 0 ) ); - else - trap_Cvar_Set( "alien_kns", va( "%d", abs(cgs.alienNextStageThreshold - cgs.alienKills)) ); - - // human kills to next stage - if( cgs.humanStage == 2 ) - trap_Cvar_Set( "human_kns", va( "%d", 0 ) ); - else - trap_Cvar_Set( "human_kns", va( "%d", abs(cgs.humanNextStageThreshold - cgs.humanKills)) ); - - // General score information - trap_Cvar_Set( "alien_score", va( "%d", cgs.alienKills ) ); - trap_Cvar_Set( "human_score", va( "%d", cgs.humanKills ) ); - - // class type - switch ( ps->stats[ STAT_PCLASS ] ) - { - case PCL_ALIEN_BUILDER0: - trap_Cvar_Set( "player_class", "Granger" ); - trap_Cvar_Set( "player_weapon", "Granger" ); - break; - - case PCL_ALIEN_BUILDER0_UPG: - trap_Cvar_Set( "player_class", "Advanced Granger" ); - trap_Cvar_Set( "player_weapon", "Advanced Granger" ); - break; - - case PCL_ALIEN_LEVEL0: - trap_Cvar_Set( "player_class", "Dretch" ); - trap_Cvar_Set( "player_weapon", "Dretch" ); - break; - - case PCL_ALIEN_LEVEL1: - trap_Cvar_Set( "player_class", "Basilisk" ); - trap_Cvar_Set( "player_weapon", "Basilisk" ); - break; - - case PCL_ALIEN_LEVEL1_UPG: - trap_Cvar_Set( "player_class", "Advanced Basilisk" ); - trap_Cvar_Set( "player_weapon", "Advanced Basilisk" ); - break; - - case PCL_ALIEN_LEVEL2: - trap_Cvar_Set( "player_class", "Marauder" ); - trap_Cvar_Set( "player_weapon", "Marauder" ); - break; - - case PCL_ALIEN_LEVEL2_UPG: - trap_Cvar_Set( "player_class", "Advanced Marauder" ); - trap_Cvar_Set( "player_weapon", "Advanced Maruder" ); - break; - - case PCL_ALIEN_LEVEL3: - trap_Cvar_Set( "player_class", "Dragoon" ); - trap_Cvar_Set( "player_weapon", "Dragoon" ); - break; - - case PCL_ALIEN_LEVEL3_UPG: - trap_Cvar_Set( "player_class", "Advanced Dragoon" ); - trap_Cvar_Set( "player_weapon", "Advanced Dragoon" ); - break; - - case PCL_ALIEN_LEVEL4: - trap_Cvar_Set( "player_class", "Tyrant" ); - trap_Cvar_Set( "player_weapon", "Tyrant" ); - break; - - case PCL_HUMAN: - trap_Cvar_Set( "player_class", "Human" ); - break; - - case PCL_HUMAN_BSUIT: - trap_Cvar_Set( "player_class", "Battlesuit" ); - break; - - default: - trap_Cvar_Set( "player_class", "Unknown" ); - } - - // weapons - switch ( ps->weapon ) - { - case WP_HBUILD: - trap_Cvar_Set( "player_weapon", "Construction Kit" ); - break; + trap_Cvar_Set( "ui_carriage", carriageCvar ); - case WP_HBUILD2: - trap_Cvar_Set( "player_weapon", "Advanced Construction Kit" ); - break; - - case WP_BLASTER: - trap_Cvar_Set( "player_weapon", "Blaster" ); - break; - - case WP_MACHINEGUN: - trap_Cvar_Set( "player_weapon", "Machine Gun" ); - break; - - case WP_PAIN_SAW: - trap_Cvar_Set( "player_weapon", "Painsaw" ); - break; + trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); +} - case WP_SHOTGUN: - trap_Cvar_Set( "player_weapon", "Shotgun" ); - break; +/* +================= +CG_SetPVars +================= +*/ +static void CG_SetPVars( void ) +{ + playerState_t *ps; - case WP_LAS_GUN: - trap_Cvar_Set( "player_weapon", "Laser Gun" ); - break; + if( !cg.snap ) return; + ps = &cg.snap->ps; - case WP_MASS_DRIVER: - trap_Cvar_Set( "player_weapon", "Mass Driver" ); - break; + trap_Cvar_Set( "player_hp", va( "%d", ps->stats[ STAT_HEALTH ] )); + trap_Cvar_Set( "player_maxhp",va( "%d", ps->stats[ STAT_MAX_HEALTH ] )); - case WP_CHAINGUN: - trap_Cvar_Set( "player_weapon", "Chain Gun" ); + switch( ps->stats[ STAT_TEAM ] ) + { + case TEAM_NONE: + trap_Cvar_Set( "team_bp", "0" ); + trap_Cvar_Set( "team_kns", "0" ); + trap_Cvar_Set( "team_teamname", "spectator" ); + trap_Cvar_Set( "team_stage", "0" ); break; - case WP_PULSE_RIFLE: - trap_Cvar_Set( "player_weapon", "Pulse Rifle" ); + case TEAM_ALIENS: + //trap_Cvar_Set( "team_bp", va( "%d", cgs.alienBuildPoints )); + trap_Cvar_Set( "team_kns", va("%d", cgs.alienNextStageThreshold) ); + trap_Cvar_Set( "team_teamname", "aliens" ); + trap_Cvar_Set( "team_stage", va( "%d", cgs.alienStage+1 ) ); break; - case WP_FLAMER: - trap_Cvar_Set( "player_weapon", "Flame Thrower" ); + case TEAM_HUMANS: + //trap_Cvar_Set( "team_bp", va("%d",cgs.humanBuildPoints) ); + trap_Cvar_Set( "team_kns", va("%d",cgs.humanNextStageThreshold) ); + trap_Cvar_Set( "team_teamname", "humans" ); + trap_Cvar_Set( "team_stage", va( "%d", cgs.humanStage+1 ) ); break; + } + + trap_Cvar_Set( "player_credits", va( "%d", cg.snap->ps.persistant[ PERS_CREDIT ] ) ); + trap_Cvar_Set( "player_score", va( "%d", cg.snap->ps.persistant[ PERS_SCORE ] ) ); - case WP_LUCIFER_CANNON: - trap_Cvar_Set( "player_weapon", "Lucifier cannon" ); - break; + if ( CG_LastAttacker( ) != -1 ) + trap_Cvar_Set( "player_attackername", cgs.clientinfo[ CG_LastAttacker( ) ].name ); + else + trap_Cvar_Set( "player_attackername", "" ); - case WP_GRENADE: - trap_Cvar_Set( "player_weapon", "Grenade" ); - break; + if ( CG_CrosshairPlayer( ) != -1 ) + trap_Cvar_Set( "player_crosshairname", cgs.clientinfo[ CG_CrosshairPlayer( ) ].name ); + else + trap_Cvar_Set( "player_crosshairname", "" ); - default: - trap_Cvar_Set( "player_weapon", "Unknown" ); - } } /* @@ -682,19 +523,16 @@ void CG_UpdateCvars( void ) int i; cvarTable_t *cv; - CG_SetPVars(); + CG_SetPVars( ); for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) - trap_Cvar_Update( cv->vmCvar ); + if( cv->vmCvar ) + trap_Cvar_Update( cv->vmCvar ); // check for modications here - // if force model changed - if( forceModelModificationCount != cg_forceModel.modificationCount ) - { - forceModelModificationCount = cg_forceModel.modificationCount; - CG_ForceModelChange( ); - } + CG_SetUIVars( ); + CG_UpdateBuildableRangeMarkerMask(); } @@ -715,6 +553,7 @@ int CG_LastAttacker( void ) return cg.snap->ps.persistant[ PERS_ATTACKER ]; } + /* ================= CG_RemoveNotifyLine @@ -735,10 +574,9 @@ void CG_RemoveNotifyLine( void ) cg.consoleText[ i ] = cg.consoleText[ i + offset ]; //pop up the first consoleLine + cg.numConsoleLines--; for( i = 0; i < cg.numConsoleLines; i++ ) cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ]; - - cg.numConsoleLines--; } /* @@ -749,6 +587,7 @@ CG_AddNotifyText void CG_AddNotifyText( void ) { char buffer[ BIG_INFO_STRING ]; + int bufferLen, textLen; trap_LiteralArgs( buffer, BIG_INFO_STRING ); @@ -759,12 +598,24 @@ void CG_AddNotifyText( void ) return; } + bufferLen = strlen( buffer ); + textLen = strlen( cg.consoleText ); + + // Ignore console messages that were just printed + if( cg_noPrintDuplicate.integer && textLen >= bufferLen && + !strcmp( cg.consoleText + textLen - bufferLen, buffer ) ) + return; + if( cg.numConsoleLines == MAX_CONSOLE_LINES ) + { CG_RemoveNotifyLine( ); + textLen = strlen( cg.consoleText ); + } - Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer ); + Q_strncpyz( cg.consoleText + textLen, buffer, MAX_CONSOLE_TEXT - textLen ); cg.consoleLines[ cg.numConsoleLines ].time = cg.time; - cg.consoleLines[ cg.numConsoleLines ].length = strlen( buffer ); + cg.consoleLines[ cg.numConsoleLines ].length = + MIN( bufferLen, MAX_CONSOLE_TEXT - textLen - 1 ); cg.numConsoleLines++; } @@ -774,7 +625,7 @@ void QDECL CG_Printf( const char *msg, ... ) char text[ 1024 ]; va_start( argptr, msg ); - vsprintf( text, msg, argptr ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); trap_Print( text ); @@ -786,7 +637,7 @@ void QDECL CG_Error( const char *msg, ... ) char text[ 1024 ]; va_start( argptr, msg ); - vsprintf( text, msg, argptr ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); trap_Error( text ); @@ -798,7 +649,7 @@ void QDECL Com_Error( int level, const char *error, ... ) char text[1024]; va_start( argptr, error ); - vsprintf( text, error, argptr ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); va_end( argptr ); CG_Error( "%s", text ); @@ -808,9 +659,9 @@ void QDECL Com_Printf( const char *msg, ... ) { va_list argptr; char text[1024]; - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); CG_Printf ("%s", text); } @@ -841,19 +692,9 @@ CG_FileExists Test if a specific file exists or not ================= */ -qboolean CG_FileExists( char *filename ) +qboolean CG_FileExists( const char *filename ) { - fileHandle_t f; - - if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 ) - { - //file exists so close it - trap_FS_FCloseFile( f ); - - return qtrue; - } - else - return qfalse; + return trap_FS_FOpenFile( filename, NULL, FS_READ ); } /* @@ -882,6 +723,8 @@ static void CG_RegisterSounds( void ) cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse ); cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.turretSpinupSound = trap_S_RegisterSound( "sound/buildables/mgturret/spinup.wav", qfalse ); + cgs.media.weaponEmptyClick = trap_S_RegisterSound( "sound/weapons/click.wav", qfalse ); cgs.media.talkSound = trap_S_RegisterSound( "sound/misc/talk.wav", qfalse ); cgs.media.alienTalkSound = trap_S_RegisterSound( "sound/misc/alien_talk.wav", qfalse ); @@ -950,6 +793,7 @@ static void CG_RegisterSounds( void ) cgs.media.buildableRepairedSound = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse ); cgs.media.lCannonWarningSound = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse ); + cgs.media.lCannonWarningSound2 = trap_S_RegisterSound( "models/weapons/lcannon/warning2.wav", qfalse ); } @@ -1011,12 +855,14 @@ static void CG_RegisterGraphics( void ) cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" ); cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" ); + cgs.media.teamOverlayShader = trap_R_RegisterShader( "gfx/2d/teamoverlay" ); + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); cgs.media.backTileShader = trap_R_RegisterShader( "console" ); - //TA: building shaders + // building shaders cgs.media.greenBuildShader = trap_R_RegisterShader("gfx/misc/greenbuild" ); cgs.media.redBuildShader = trap_R_RegisterShader("gfx/misc/redbuild" ); cgs.media.humanSpawningShader = trap_R_RegisterShader("models/buildables/telenode/rep_cyl" ); @@ -1024,8 +870,15 @@ static void CG_RegisterGraphics( void ) for( i = 0; i < 8; i++ ) cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] ); + // player health cross shaders + cgs.media.healthCross = trap_R_RegisterShader( "ui/assets/neutral/cross.tga" ); + cgs.media.healthCross2X = trap_R_RegisterShader( "ui/assets/neutral/cross2.tga" ); + cgs.media.healthCross3X = trap_R_RegisterShader( "ui/assets/neutral/cross3.tga" ); + cgs.media.healthCrossMedkit = trap_R_RegisterShader( "ui/assets/neutral/cross_medkit.tga" ); + cgs.media.healthCrossPoisoned = trap_R_RegisterShader( "ui/assets/neutral/cross_poison.tga" ); + cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" ); - + cgs.media.balloonShader = trap_R_RegisterShader( "gfx/sprites/chatballoon" ); cgs.media.disconnectPS = CG_RegisterParticleSystem( "disconnectPS" ); @@ -1039,6 +892,7 @@ static void CG_RegisterGraphics( void ) cgs.media.wakeMarkShader = trap_R_RegisterShader( "gfx/marks/wake" ); cgs.media.poisonCloudPS = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" ); + cgs.media.poisonCloudedPS = CG_RegisterParticleSystem( "poisonCloudedPS" ); cgs.media.alienEvolvePS = CG_RegisterParticleSystem( "alienEvolvePS" ); cgs.media.alienAcidTubePS = CG_RegisterParticleSystem( "alienAcidTubePS" ); @@ -1051,9 +905,29 @@ static void CG_RegisterGraphics( void ) cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" ); cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" ); + cgs.media.humanBuildableBleedPS = CG_RegisterParticleSystem( "humanBuildableBleedPS"); + cgs.media.alienBuildableBleedPS = CG_RegisterParticleSystem( "alienBuildableBleedPS" ); + cgs.media.alienBleedPS = CG_RegisterParticleSystem( "alienBleedPS" ); cgs.media.humanBleedPS = CG_RegisterParticleSystem( "humanBleedPS" ); + cgs.media.sphereModel = trap_R_RegisterModel( "models/generic/sphere" ); + cgs.media.sphericalCone64Model = trap_R_RegisterModel( "models/generic/sphericalCone64" ); + cgs.media.sphericalCone240Model = trap_R_RegisterModel( "models/generic/sphericalCone240" ); + + cgs.media.plainColorShader = trap_R_RegisterShader( "gfx/plainColor" ); + cgs.media.binaryAlpha1Shader = trap_R_RegisterShader( "gfx/binary/alpha1" ); + + for( i = 0; i < NUM_BINARY_SHADERS; ++i ) + { + cgs.media.binaryShaders[ i ].f1 = trap_R_RegisterShader( va( "gfx/binary/%03i_F1", i ) ); + cgs.media.binaryShaders[ i ].f2 = trap_R_RegisterShader( va( "gfx/binary/%03i_F2", i ) ); + cgs.media.binaryShaders[ i ].f3 = trap_R_RegisterShader( va( "gfx/binary/%03i_F3", i ) ); + cgs.media.binaryShaders[ i ].b1 = trap_R_RegisterShader( va( "gfx/binary/%03i_B1", i ) ); + cgs.media.binaryShaders[ i ].b2 = trap_R_RegisterShader( va( "gfx/binary/%03i_B2", i ) ); + cgs.media.binaryShaders[ i ].b3 = trap_R_RegisterShader( va( "gfx/binary/%03i_B3", i ) ); + } + CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat ); CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat ); @@ -1134,16 +1008,11 @@ void CG_BuildSpectatorString( void ) for( i = 0; i < MAX_CLIENTS; i++ ) { - if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == PTE_NONE ) - Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), va( "%s " S_COLOR_WHITE, cgs.clientinfo[ i ].name ) ); - } - - i = strlen( cg.spectatorList ); - - if( i != cg.spectatorLen ) - { - cg.spectatorLen = i; - cg.spectatorWidth = -1; + if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == TEAM_NONE ) + { + Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), + va( S_COLOR_WHITE "%s ", cgs.clientinfo[ i ].name ) ); + } } } @@ -1164,8 +1033,8 @@ static void CG_RegisterClients( void ) //precache all the models/sounds/etc for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) { - CG_PrecacheClientInfo( i, BG_FindModelNameForClass( i ), - BG_FindSkinNameForClass( i ) ); + CG_PrecacheClientInfo( i, BG_ClassConfig( i )->modelName, + BG_ClassConfig( i )->skinName ); cg.charModelFraction = (float)i / (float)PCL_NUM_CLASSES; trap_UpdateScreen( ); @@ -1246,8 +1115,8 @@ int CG_PlayerCount( void ) for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_ALIENS || - cg.scores[ i ].team == PTE_HUMANS ) + if( cg.scores[ i ].team == TEAM_ALIENS || + cg.scores[ i ].team == TEAM_HUMANS ) count++; } @@ -1452,7 +1321,7 @@ qboolean CG_Asset_Parse( int handle ) } } - return qfalse; // bk001204 - why not? + return qfalse; } void CG_ParseMenu( const char *menuFile ) @@ -1545,11 +1414,11 @@ void CG_LoadMenus( const char *menuFile ) if( !f ) { - trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default", menuFile ) ); len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); if( !f ) - trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n" ) ); + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!" ) ); } if( len >= MAX_MENUDEFFILE ) @@ -1594,13 +1463,13 @@ void CG_LoadMenus( const char *menuFile ) -static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key ) +static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int key ) { return qfalse; } -static int CG_FeederCount( float feederID ) +static int CG_FeederCount( int feederID ) { int i, count = 0; @@ -1608,7 +1477,7 @@ static int CG_FeederCount( float feederID ) { for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_ALIENS ) + if( cg.scores[ i ].team == TEAM_ALIENS ) count++; } } @@ -1616,7 +1485,7 @@ static int CG_FeederCount( float feederID ) { for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_HUMANS ) + if( cg.scores[ i ].team == TEAM_HUMANS ) count++; } } @@ -1636,9 +1505,9 @@ void CG_SetScoreSelection( void *p ) for( i = 0; i < cg.numScores; i++ ) { - if( cg.scores[ i ].team == PTE_ALIENS ) + if( cg.scores[ i ].team == TEAM_ALIENS ) alien++; - else if( cg.scores[ i ].team == PTE_HUMANS ) + else if( cg.scores[ i ].team == TEAM_HUMANS ) human++; if( ps->clientNum == cg.scores[ i ].client ) @@ -1652,7 +1521,7 @@ void CG_SetScoreSelection( void *p ) feeder = FEEDER_ALIENTEAM_LIST; i = alien; - if( cg.scores[ cg.selectedScore ].team == PTE_HUMANS ) + if( cg.scores[ cg.selectedScore ].team == TEAM_HUMANS ) { feeder = FEEDER_HUMANTEAM_LIST; i = human; @@ -1684,7 +1553,16 @@ static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreInde return &cgs.clientinfo[ cg.scores[ index ].client ]; } -static const char *CG_FeederItemText( float feederID, int index, int column, qhandle_t *handle ) +qboolean CG_ClientIsReady( int clientNum ) +{ + clientList_t ready; + + Com_ClientListParse( &ready, CG_ConfigString( CS_CLIENTS_READY ) ); + + return Com_ClientListContains( &ready, clientNum ); +} + +static const char *CG_FeederItemText( int feederID, int index, int column, qhandle_t *handle ) { int scoreIndex = 0; clientInfo_t *info = NULL; @@ -1695,19 +1573,23 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha *handle = -1; if( feederID == FEEDER_ALIENTEAM_LIST ) - team = PTE_ALIENS; + team = TEAM_ALIENS; else if( feederID == FEEDER_HUMANTEAM_LIST ) - team = PTE_HUMANS; + team = TEAM_HUMANS; info = CG_InfoFromScoreIndex( index, team, &scoreIndex ); sp = &cg.scores[ scoreIndex ]; - if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && - cg.intermissionStarted ) + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) showIcons = qfalse; - else if( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.pm_flags & PMF_FOLLOW || - team == cg.snap->ps.stats[ STAT_PTEAM ] || cg.intermissionStarted ) + else if( cg.snap->ps.pm_type == PM_SPECTATOR || + cg.snap->ps.pm_type == PM_NOCLIP || + cg.snap->ps.pm_flags & PMF_FOLLOW || + team == cg.snap->ps.stats[ STAT_TEAM ] || + cg.intermissionStarted ) + { showIcons = qtrue; + } if( info && info->infoValid ) { @@ -1724,9 +1606,9 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha case 1: if( showIcons ) { - if( sp->team == PTE_HUMANS && sp->upgrade != UP_NONE ) + if( sp->team == TEAM_HUMANS && sp->upgrade != UP_NONE ) *handle = cg_upgrades[ sp->upgrade ].upgradeIcon; - else if( sp->team == PTE_ALIENS ) + else if( sp->team == TEAM_ALIENS ) { switch( sp->weapon ) { @@ -1745,17 +1627,16 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha break; case 2: - if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && - cg.intermissionStarted ) + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) return "Ready"; break; case 3: - return info->name; + return va( S_COLOR_WHITE "%s", info->name ); break; case 4: - return va( "%d", info->score ); + return va( "%d", sp->score ); break; case 5: @@ -1764,7 +1645,7 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha case 6: if( sp->ping == -1 ) - return "connecting"; + return ""; return va( "%4d", sp->ping ); break; @@ -1774,15 +1655,15 @@ static const char *CG_FeederItemText( float feederID, int index, int column, qha return ""; } -static qhandle_t CG_FeederItemImage( float feederID, int index ) +static qhandle_t CG_FeederItemImage( int feederID, int index ) { return 0; } -static void CG_FeederSelection( float feederID, int index ) +static void CG_FeederSelection( int feederID, int index ) { int i, count; - int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? PTE_ALIENS : PTE_HUMANS; + int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? TEAM_ALIENS : TEAM_HUMANS; count = 0; for( i = 0; i < cg.numScores; i++ ) @@ -1809,7 +1690,7 @@ static float CG_Cvar_Get( const char *cvar ) void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style ) { - CG_Text_Paint( x, y, scale, color, text, 0, limit, style ); + UI_Text_Paint( x, y, scale, color, text, 0, limit, style ); } static int CG_OwnerDrawWidth( int ownerDraw, float scale ) @@ -1817,7 +1698,7 @@ static int CG_OwnerDrawWidth( int ownerDraw, float scale ) switch( ownerDraw ) { case CG_KILLER: - return CG_Text_Width( CG_GetKillerText( ), scale, 0 ); + return UI_Text_Width( CG_GetKillerText( ), scale ); break; } @@ -1845,7 +1726,7 @@ static void CG_RunCinematicFrame( int handle ) trap_CIN_RunCinematic( handle ); } -//TA: hack to prevent warning +// hack to prevent warning static qboolean CG_OwnerDrawVisible( int parameter ) { return qfalse; @@ -1861,13 +1742,18 @@ void CG_LoadHudMenu( void ) char buff[ 1024 ]; const char *hudSet; + cgDC.aspectScale = ( ( 640.0f * cgs.glconfig.vidHeight ) / + ( 480.0f * cgs.glconfig.vidWidth ) ); + cgDC.xscale = cgs.glconfig.vidWidth / 640.0f; + cgDC.yscale = cgs.glconfig.vidHeight / 480.0f; + + cgDC.smallFontScale = CG_Cvar_Get( "ui_smallFont" ); + cgDC.bigFontScale = CG_Cvar_Get( "ui_bigFont" ); + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; cgDC.setColor = &trap_R_SetColor; cgDC.drawHandlePic = &CG_DrawPic; cgDC.drawStretchPic = &trap_R_DrawStretchPic; - cgDC.drawText = &CG_Text_Paint; - cgDC.textWidth = &CG_Text_Width; - cgDC.textHeight = &CG_Text_Height; cgDC.registerModel = &trap_R_RegisterModel; cgDC.modelBounds = &trap_R_ModelBounds; cgDC.fillRect = &CG_FillRect; @@ -1882,13 +1768,11 @@ void CG_LoadHudMenu( void ) cgDC.getValue = &CG_GetValue; cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; cgDC.runScript = &CG_RunMenuScript; - cgDC.getTeamColor = &CG_GetTeamColor; cgDC.setCVar = trap_Cvar_Set; cgDC.getCVarString = trap_Cvar_VariableStringBuffer; cgDC.getCVarValue = CG_Cvar_Get; - cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; - //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; - //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; cgDC.startLocalSound = &trap_S_StartLocalSound; cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; cgDC.feederCount = &CG_FeederCount; @@ -1902,6 +1786,7 @@ void CG_LoadHudMenu( void ) cgDC.Error = &Com_Error; cgDC.Print = &Com_Printf; cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.ownerDrawText = &CG_OwnerDrawText; //cgDC.Pause = &CG_Pause; cgDC.registerSound = &trap_S_RegisterSound; cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; @@ -1926,6 +1811,8 @@ void CG_LoadHudMenu( void ) void CG_AssetCache( void ) { + int i; + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); @@ -1935,6 +1822,21 @@ void CG_AssetCache( void ) cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + if( cg_emoticons.integer ) + { + cgDC.Assets.emoticonCount = BG_LoadEmoticons( cgDC.Assets.emoticons, + MAX_EMOTICONS ); + } + else + cgDC.Assets.emoticonCount = 0; + + for( i = 0; i < cgDC.Assets.emoticonCount; i++ ) + { + cgDC.Assets.emoticons[ i ].shader = trap_R_RegisterShaderNoMip( + va( "emoticons/%s_%dx1.tga", cgDC.Assets.emoticons[ i ].name, + cgDC.Assets.emoticons[ i ].width ) ); + } } /* @@ -1952,7 +1854,6 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) // clear everything memset( &cgs, 0, sizeof( cgs ) ); memset( &cg, 0, sizeof( cg ) ); - memset( &cg.pmext, 0, sizeof( cg.pmext ) ); memset( cg_entities, 0, sizeof( cg_entities ) ); cg.clientNum = clientNum; @@ -1960,45 +1861,52 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) cgs.processedSnapshotNum = serverMessageNum; cgs.serverCommandSequence = serverCommandSequence; + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0f; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0f; + // load a few needed things before we do any screen updates cgs.media.whiteShader = trap_R_RegisterShader( "white" ); cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); cgs.media.outlineShader = trap_R_RegisterShader( "outline" ); - //inform UI to repress cursor whilst loading - trap_Cvar_Set( "ui_loading", "1" ); - - //TA: load overrides - BG_InitClassOverrides( ); - BG_InitBuildableOverrides( ); + // load overrides + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); BG_InitAllowedGameElements( ); - //TA: dyn memory - CG_InitMemory( ); + // Dynamic memory + BG_InitMemory( ); CG_RegisterCvars( ); CG_InitConsoleCommands( ); - //TA: moved up for LoadHudMenu String_Init( ); - //TA: TA UI CG_AssetCache( ); - CG_LoadHudMenu( ); // load new hud stuff + CG_LoadHudMenu( ); cg.weaponSelect = WP_NONE; // old servers - // get the rendering configuration from the client system - trap_GetGlconfig( &cgs.glconfig ); - cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; - cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; - // get the gamestate from the client system trap_GetGameState( &cgs.gameState ); + // copy vote display strings so they don't show up blank if we see + // the same one directly after connecting + Q_strncpyz( cgs.voteString[ TEAM_NONE ], + CG_ConfigString( CS_VOTE_STRING + TEAM_NONE ), + sizeof( cgs.voteString ) ); + Q_strncpyz( cgs.voteString[ TEAM_ALIENS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_ALIENS ] ) ); + Q_strncpyz( cgs.voteString[ TEAM_HUMANS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_HUMANS ] ) ); + // check version s = CG_ConfigString( CS_GAME_VERSION ); @@ -2033,8 +1941,10 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) CG_InitUpgrades( ); CG_UpdateMediaFraction( 1.0f ); - //TA: CG_InitBuildables( ); + + cgs.voices = BG_VoiceInit( ); + BG_PrintVoices( cgs.voices, cg_debugVoices.integer ); CG_RegisterClients( ); // if low on memory, some clients will be deferred @@ -2053,8 +1963,6 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) CG_ShaderStateChanged( ); trap_S_ClearLoopingSounds( qtrue ); - - trap_Cvar_Set( "ui_loading", "0" ); } /* @@ -2066,8 +1974,7 @@ Called before every level change or subsystem restart */ void CG_Shutdown( void ) { - // some mods may need to do cleanup work here, - // like closing files or archiving session data + CG_UnregisterCommands( ); } /* @@ -2075,7 +1982,7 @@ void CG_Shutdown( void ) CG_VoIPString ================ */ -char *CG_VoIPString( void ) +static char *CG_VoIPString( void ) { // a generous overestimate of the space needed for 0,1,2...61,62,63 static char voipString[ MAX_CLIENTS * 4 ]; @@ -2086,7 +1993,7 @@ char *CG_VoIPString( void ) if( Q_stricmp( voipSendTarget, "team" ) == 0 ) { - int i, slen; + int i, slen, nlen; for( slen = i = 0; i < cgs.maxclients; i++ ) { if( !cgs.clientinfo[ i ].infoValid || i == cg.clientNum ) @@ -2094,14 +2001,15 @@ char *CG_VoIPString( void ) if( cgs.clientinfo[ i ].team != cgs.clientinfo[ cg.clientNum ].team ) continue; - Com_sprintf( &voipString[ slen ], sizeof( voipString ) - slen, - "%s%d", ( slen > 0 ) ? "," : "", i ); - slen = strlen( voipString ); - if( slen + 1 >= sizeof( voipString ) ) + nlen = Q_snprintf( &voipString[ slen ], sizeof( voipString ) - slen, + "%s%d", ( slen > 0 ) ? "," : "", i ); + if( slen + nlen + 1 >= sizeof( voipString ) ) { CG_Printf( S_COLOR_YELLOW "WARNING: voipString overflowed\n" ); break; } + + slen += nlen; } // Notice that if the snprintf was truncated, slen was not updated @@ -2119,3 +2027,26 @@ char *CG_VoIPString( void ) return voipString; } + +#ifdef MODULE_INTERFACE_11 +int trap_S_SoundDuration( sfxHandle_t handle ) +{ + return 1; +} + +void trap_R_SetClipRegion( const float *region ) +{ +} + +static qboolean keyOverstrikeMode = qfalse; + +void trap_Key_SetOverstrikeMode( qboolean state ) +{ + keyOverstrikeMode = state; +} + +qboolean trap_Key_GetOverstrikeMode( void ) +{ + return keyOverstrikeMode; +} +#endif diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c index 380f1f0..a5e2351 100644 --- a/src/cgame/cg_marks.c +++ b/src/cgame/cg_marks.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_marks.c -- wall marks - #include "cg_local.h" /* @@ -286,4 +286,3 @@ void CG_AddMarks( void ) trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); } } - diff --git a/src/cgame/cg_mem.c b/src/cgame/cg_mem.c deleted file mode 100644 index 6cf5ddd..0000000 --- a/src/cgame/cg_mem.c +++ /dev/null @@ -1,202 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous 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. - -Tremulous 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "cg_local.h" - -#define POOLSIZE (256 * 1024) -#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value -#define ROUNDBITS 31 // Round to 32 bytes - -struct freememnode -{ - // Size of ROUNDBITS - int cookie, size; // Size includes node (obviously) - struct freememnode *prev, *next; -}; - -static char memoryPool[ POOLSIZE ]; -static struct freememnode *freehead; -static int freemem; - -void *CG_Alloc( int size ) -{ - // Find a free block and allocate. - // Does two passes, attempts to fill same-sized free slot first. - - struct freememnode *fmn, *prev, *next, *smallest; - int allocsize, smallestsize; - char *endptr; - int *ptr; - - allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary - ptr = NULL; - - smallest = NULL; - smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :) - for( fmn = freehead; fmn; fmn = fmn->next ) - { - if( fmn->cookie != FREEMEMCOOKIE ) - CG_Error( "CG_Alloc: Memory corruption detected!\n" ); - - if( fmn->size >= allocsize ) - { - // We've got a block - if( fmn->size == allocsize ) - { - // Same size, just remove - - prev = fmn->prev; - next = fmn->next; - if( prev ) - prev->next = next; // Point previous node to next - if( next ) - next->prev = prev; // Point next node to previous - if( fmn == freehead ) - freehead = next; // Set head pointer to next - ptr = (int *) fmn; - break; // Stop the loop, this is fine - } - else - { - // Keep track of the smallest free slot - if( fmn->size < smallestsize ) - { - smallest = fmn; - smallestsize = fmn->size; - } - } - } - } - - if( !ptr && smallest ) - { - // We found a slot big enough - smallest->size -= allocsize; - endptr = (char *) smallest + smallest->size; - ptr = (int *) endptr; - } - - if( ptr ) - { - freemem -= allocsize; - if( cg_debugAlloc.integer ) - CG_Printf( "CG_Alloc of %i bytes (%i left)\n", allocsize, freemem ); - memset( ptr, 0, allocsize ); - *ptr++ = allocsize; // Store a copy of size for deallocation - return( (void *) ptr ); - } - - CG_Error( "CG_Alloc: failed on allocation of %i bytes\n", size ); - return( NULL ); -} - -void CG_Free( void *ptr ) -{ - // Release allocated memory, add it to the free list. - - struct freememnode *fmn; - char *freeend; - int *freeptr; - - freeptr = ptr; - freeptr--; - - freemem += *freeptr; - if( cg_debugAlloc.integer ) - CG_Printf( "CG_Free of %i bytes (%i left)\n", *freeptr, freemem ); - - for( fmn = freehead; fmn; fmn = fmn->next ) - { - freeend = ((char *) fmn) + fmn->size; - if( freeend == (char *) freeptr ) - { - // Released block can be merged to an existing node - - fmn->size += *freeptr; // Add size of node. - return; - } - } - // No merging, add to head of list - - fmn = (struct freememnode *) freeptr; - fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr - fmn->cookie = FREEMEMCOOKIE; - fmn->prev = NULL; - fmn->next = freehead; - freehead->prev = fmn; - freehead = fmn; -} - -void CG_InitMemory( void ) -{ - // Set up the initial node - - freehead = (struct freememnode *) memoryPool; - freehead->cookie = FREEMEMCOOKIE; - freehead->size = POOLSIZE; - freehead->next = NULL; - freehead->prev = NULL; - freemem = sizeof(memoryPool); -} - -void CG_DefragmentMemory( void ) -{ - // If there's a frenzy of deallocation and we want to - // allocate something big, this is useful. Otherwise... - // not much use. - - struct freememnode *startfmn, *endfmn, *fmn; - - for( startfmn = freehead; startfmn; ) - { - endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size); - for( fmn = freehead; fmn; ) - { - if( fmn->cookie != FREEMEMCOOKIE ) - CG_Error( "CG_DefragmentMemory: Memory corruption detected!\n" ); - - if( fmn == endfmn ) - { - // We can add fmn onto startfmn. - - if( fmn->prev ) - fmn->prev->next = fmn->next; - if( fmn->next ) - { - if( !(fmn->next->prev = fmn->prev) ) - freehead = fmn->next; // We're removing the head node - } - startfmn->size += fmn->size; - memset( fmn, 0, sizeof(struct freememnode) ); // A redundant call, really. - - startfmn = freehead; - endfmn = fmn = NULL; // Break out of current loop - } - else - fmn = fmn->next; - } - - if( endfmn ) - startfmn = startfmn->next; // endfmn acts as a 'restart' flag here - } -} diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c index 80c4b23..eca9de2 100644 --- a/src/cgame/cg_particles.c +++ b/src/cgame/cg_particles.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,14 +16,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_particles.c -- the particle system - #include "cg_local.h" static baseParticleSystem_t baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ]; @@ -157,6 +157,8 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p p->radius.delay = (int)CG_RandomiseValue( (float)bp->radius.delay, bp->radius.delayRandFrac ); p->radius.initial = CG_RandomiseValue( bp->radius.initial, bp->radius.initialRandFrac ); p->radius.final = CG_RandomiseValue( bp->radius.final, bp->radius.finalRandFrac ); + + p->radius.initial += bp->scaleWithCharge * pe->parent->charge; p->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac ); p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac ); @@ -208,7 +210,7 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p VectorAdd( p->origin, bp->displacement, p->origin ); for( j = 0; j <= 2; j++ ) - p->origin[ j ] += ( crandom( ) * bp->randDisplacement ); + p->origin[ j ] += ( crandom( ) * bp->randDisplacement[ j ] ); switch( bp->velMoveType ) { @@ -259,6 +261,21 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p VectorNormalize( p->velocity ); VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); break; + + case PMT_LAST_NORMAL: + VectorCopy( ps->lastNormal, p->velocity ); + VectorNormalize( p->velocity ); + VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); + break; + + case PMT_OPPORTUNISTIC_NORMAL: + if( ps->lastNormalIsCurrent ) + { + VectorCopy( ps->lastNormal, p->velocity ); + VectorNormalize( p->velocity ); + VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); + } + break; } VectorNormalize( p->velocity ); @@ -281,12 +298,18 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p //this particle has a child particle system attached if( bp->childSystemName[ 0 ] != '\0' ) { - particleSystem_t *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); + particleSystem_t *chps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); - if( CG_IsParticleSystemValid( &ps ) ) + if( CG_IsParticleSystemValid( &chps ) ) { - CG_SetAttachmentParticle( &ps->attachment, p ); - CG_AttachToParticle( &ps->attachment ); + CG_SetAttachmentParticle( &chps->attachment, p ); + CG_AttachToParticle( &chps->attachment ); + p->childParticleSystem = chps; + + if( ps->lastNormalIsCurrent ) + CG_SetParticleSystemLastNormal( chps, ps->lastNormal ); + else + VectorCopy( ps->lastNormal, chps->lastNormal ); } } @@ -465,6 +488,9 @@ particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ) ps->valid = qtrue; ps->lazyRemove = qfalse; + // use "up" as an arbitrary (non-null) "last" normal + VectorSet( ps->lastNormal, 0, 0, 1 ); + for( j = 0; j < bps->numEjectors; j++ ) CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps ); @@ -567,13 +593,11 @@ Parse a value and its random variance static void CG_ParseValueAndVariance( char *token, float *value, float *variance, qboolean allowNegative ) { char valueBuffer[ 16 ]; - char varianceBuffer[ 16 ]; char *variancePtr = NULL, *varEndPointer = NULL; float localValue = 0.0f; float localVariance = 0.0f; Q_strncpyz( valueBuffer, token, sizeof( valueBuffer ) ); - Q_strncpyz( varianceBuffer, token, sizeof( varianceBuffer ) ); variancePtr = strchr( valueBuffer, '~' ); @@ -623,13 +647,96 @@ static qboolean CG_ParseColor( byte *c, char **text_p ) for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); } + token = COM_Parse( text_p ); + if( strcmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + return qfalse; + } + + return qtrue; +} + +/* +CG_ParseParticle helpers +*/ +static void CG_CopyLine( int *i, char *toks, size_t num, size_t size, char **text_p ) +{ + char *token; + + while( *i < num ) + { + token = COM_ParseExt( text_p, qfalse ); + if( !*token ) + break; + + Q_strncpyz( toks, token, size ); + ( *i )++; + + toks += size; + } +} + +static qboolean CG_ParseType( pMoveType_t *pmt, char **text_p ) +{ + char *token = COM_Parse( text_p ); + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "static" ) ) + *pmt = PMT_STATIC; + else if( !Q_stricmp( token, "static_transform" ) ) + *pmt = PMT_STATIC_TRANSFORM; + else if( !Q_stricmp( token, "tag" ) ) + *pmt = PMT_TAG; + else if( !Q_stricmp( token, "cent" ) ) + *pmt = PMT_CENT_ANGLES; + else if( !Q_stricmp( token, "normal" ) ) + *pmt = PMT_NORMAL; + else if( !Q_stricmp( token, "last_normal" ) ) + *pmt = PMT_LAST_NORMAL; + else if( !Q_stricmp( token, "opportunistic_normal" ) ) + *pmt = PMT_OPPORTUNISTIC_NORMAL; + + return qtrue; +} + +static qboolean CG_ParseDir( pMoveValues_t *pmv, char **text_p ) +{ + char *token = COM_Parse( text_p ); + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "linear" ) ) + pmv->dirType = PMD_LINEAR; + else if( !Q_stricmp( token, "point" ) ) + pmv->dirType = PMD_POINT; + + return qtrue; +} + +static qboolean CG_ParseFinal( pLerpValues_t *plv, char **text_p ) +{ + char *token = COM_Parse( text_p ); + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "-" ) ) + { + plv->final = PARTICLES_SAME_AS_INITIAL; + plv->finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &plv->final, &plv->finalRandFrac, qfalse ); + } + return qtrue; } @@ -650,17 +757,13 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) while( 1 ) { token = COM_Parse( text_p ); - - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; if( !Q_stricmp( token, "bounce" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "cull" ) ) @@ -672,13 +775,9 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) } else { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceFrac = number; - bp->bounceFracRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceFrac, + &bp->bounceFracRandFrac, qfalse ); } - - continue; } else if( !Q_stricmp( token, "bounceMark" ) ) { @@ -686,27 +785,21 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceMarkCount = number; - bp->bounceMarkCountRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceMarkCount, + &bp->bounceMarkCountRandFrac, qfalse ); token = COM_Parse( text_p ); if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceMarkRadius = number; - bp->bounceMarkRadiusRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceMarkRadius, + &bp->bounceMarkRadiusRandFrac, qfalse ); token = COM_ParseExt( text_p, qfalse ); if( !*token ) break; Q_strncpyz( bp->bounceMarkName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "bounceSound" ) ) { @@ -714,30 +807,26 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->bounceSoundCount = number; - bp->bounceSoundCountRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->bounceSoundCount, + &bp->bounceSoundCountRandFrac, qfalse ); token = COM_Parse( text_p ); if( !*token ) break; Q_strncpyz( bp->bounceSoundName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "shader" ) ) { if( bp->numModels > 0 ) { CG_Printf( S_COLOR_RED "ERROR: 'shader' not allowed in " - "conjunction with 'model'\n", token ); + "conjunction with 'model'\n" ); break; } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "sync" ) ) @@ -745,38 +834,20 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) else bp->framerate = atof_neg( token, qfalse ); - token = COM_ParseExt( text_p, qfalse ); - if( !*token ) - break; - - while( *token && bp->numFrames < MAX_PS_SHADER_FRAMES ) - { - Q_strncpyz( bp->shaderNames[ bp->numFrames++ ], token, MAX_QPATH ); - token = COM_ParseExt( text_p, qfalse ); - } - - continue; + CG_CopyLine( &bp->numFrames, bp->shaderNames[ 0 ], + ARRAY_LEN( bp->shaderNames ), MAX_QPATH, text_p ); } else if( !Q_stricmp( token, "model" ) ) { if( bp->numFrames > 0 ) { CG_Printf( S_COLOR_RED "ERROR: 'model' not allowed in " - "conjunction with 'shader'\n", token ); + "conjunction with 'shader'\n" ); break; } - token = COM_ParseExt( text_p, qfalse ); - if( !*token ) - break; - - while( *token && bp->numModels < MAX_PS_MODELS ) - { - Q_strncpyz( bp->modelNames[ bp->numModels++ ], token, MAX_QPATH ); - token = COM_ParseExt( text_p, qfalse ); - } - - continue; + CG_CopyLine( &bp->numModels, bp->modelNames[ 0 ], + ARRAY_LEN( bp->modelNames ), MAX_QPATH, text_p ); } else if( !Q_stricmp( token, "modelAnimation" ) ) { @@ -823,200 +894,130 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( fps == 0.0f ) fps = 1.0f; - bp->modelAnimation.frameLerp = 1000 / fps; - bp->modelAnimation.initialLerp = 1000 / fps; + bp->modelAnimation.frameLerp = bp->modelAnimation.initialLerp = + 1000 / fps; } - - continue; } /// else if( !Q_stricmp( token, "velocityType" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseType( &bp->velMoveType, text_p ) ) break; - - if( !Q_stricmp( token, "static" ) ) - bp->velMoveType = PMT_STATIC; - else if( !Q_stricmp( token, "static_transform" ) ) - bp->velMoveType = PMT_STATIC_TRANSFORM; - else if( !Q_stricmp( token, "tag" ) ) - bp->velMoveType = PMT_TAG; - else if( !Q_stricmp( token, "cent" ) ) - bp->velMoveType = PMT_CENT_ANGLES; - else if( !Q_stricmp( token, "normal" ) ) - bp->velMoveType = PMT_NORMAL; - - continue; } else if( !Q_stricmp( token, "velocityDir" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseDir( &bp->velMoveValues, text_p ) ) break; - - if( !Q_stricmp( token, "linear" ) ) - bp->velMoveValues.dirType = PMD_LINEAR; - else if( !Q_stricmp( token, "point" ) ) - bp->velMoveValues.dirType = PMD_POINT; - - continue; } else if( !Q_stricmp( token, "velocityMagnitude" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->velMoveValues.mag = number; - bp->velMoveValues.magRandFrac = randFrac; - - continue; + CG_ParseValueAndVariance( token, &bp->velMoveValues.mag, + &bp->velMoveValues.magRandFrac, qtrue ); } else if( !Q_stricmp( token, "parentVelocityFraction" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->velMoveValues.parentVelFrac = number; - bp->velMoveValues.parentVelFracRandFrac = randFrac; - - continue; + CG_ParseValueAndVariance( token, &bp->velMoveValues.parentVelFrac, + &bp->velMoveValues.parentVelFracRandFrac, qfalse ); } else if( !Q_stricmp( token, "velocity" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->velMoveValues.dir[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->velMoveValues.dirRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->velMoveValues.dirRandAngle, + qfalse ); } else if( !Q_stricmp( token, "velocityPoint" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->velMoveValues.point[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->velMoveValues.pointRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->velMoveValues.pointRandAngle, + qfalse ); } /// else if( !Q_stricmp( token, "accelerationType" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseType( &bp->accMoveType, text_p ) ) break; - - if( !Q_stricmp( token, "static" ) ) - bp->accMoveType = PMT_STATIC; - else if( !Q_stricmp( token, "static_transform" ) ) - bp->accMoveType = PMT_STATIC_TRANSFORM; - else if( !Q_stricmp( token, "tag" ) ) - bp->accMoveType = PMT_TAG; - else if( !Q_stricmp( token, "cent" ) ) - bp->accMoveType = PMT_CENT_ANGLES; - else if( !Q_stricmp( token, "normal" ) ) - bp->accMoveType = PMT_NORMAL; - - continue; } else if( !Q_stricmp( token, "accelerationDir" ) ) { - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseDir( &bp->accMoveValues, text_p ) ) break; - - if( !Q_stricmp( token, "linear" ) ) - bp->accMoveValues.dirType = PMD_LINEAR; - else if( !Q_stricmp( token, "point" ) ) - bp->accMoveValues.dirType = PMD_POINT; - - continue; } else if( !Q_stricmp( token, "accelerationMagnitude" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->accMoveValues.mag = number; - bp->accMoveValues.magRandFrac = randFrac; - - continue; + CG_ParseValueAndVariance( token, &bp->accMoveValues.mag, + &bp->accMoveValues.magRandFrac, qtrue ); } else if( !Q_stricmp( token, "acceleration" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->accMoveValues.dir[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->accMoveValues.dirRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->accMoveValues.dirRandAngle, + qfalse ); } else if( !Q_stricmp( token, "accelerationPoint" ) ) { for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->accMoveValues.point[ i ] = atof_neg( token, qtrue ); } token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); - - bp->accMoveValues.pointRandAngle = randFrac; - - continue; + CG_ParseValueAndVariance( token, NULL, &bp->accMoveValues.pointRandAngle, + qfalse ); } /// else if( !Q_stricmp( token, "displacement" ) ) @@ -1024,43 +1025,45 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) for( i = 0; i <= 2; i++ ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - bp->displacement[ i ] = atof_neg( token, qtrue ); + CG_ParseValueAndVariance( token, &bp->displacement[ i ], + &bp->randDisplacement[ i ], qtrue ); } - token = COM_Parse( text_p ); - if( !token ) - break; - - CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + // if there is another token on the same line interpret it as an + // additional displacement in all three directions, for compatibility + // with the old scripts where this was the only option + randFrac = 0; + token = COM_ParseExt( text_p, qfalse ); + if( token ) + CG_ParseValueAndVariance( token, NULL, &randFrac, qtrue ); - bp->randDisplacement = randFrac; + for( i = 0; i < 3; i++ ) + { + // convert randDisplacement from proportions to absolute values + if( bp->displacement[ i ] != 0 ) + bp->randDisplacement[ i ] *= bp->displacement[ i ]; - continue; + bp->randDisplacement[ i ] += randFrac; + } } else if( !Q_stricmp( token, "normalDisplacement" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bp->normalDisplacement = atof_neg( token, qtrue ); - - continue; } else if( !Q_stricmp( token, "overdrawProtection" ) ) { bp->overdrawProtection = qtrue; - - continue; } else if( !Q_stricmp( token, "realLight" ) ) { bp->realLight = qtrue; - - continue; } else if( !Q_stricmp( token, "dynamicLight" ) ) { @@ -1070,37 +1073,20 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->dLightRadius.delayRandFrac, + qfalse ); bp->dLightRadius.delay = (int)number; - bp->dLightRadius.delayRandFrac = randFrac; token = COM_Parse( text_p ); if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + CG_ParseValueAndVariance( token, &bp->dLightRadius.initial, + &bp->dLightRadius.initialRandFrac, qfalse ); - bp->dLightRadius.initial = number; - bp->dLightRadius.initialRandFrac = randFrac; - - token = COM_Parse( text_p ); - if( !*token ) + if( !CG_ParseFinal( &bp->dLightRadius, text_p ) ) break; - if( !Q_stricmp( token, "-" ) ) - { - bp->dLightRadius.final = PARTICLES_SAME_AS_INITIAL; - bp->dLightRadius.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->dLightRadius.final = number; - bp->dLightRadius.finalRandFrac = randFrac; - } - token = COM_Parse( text_p ); if( !*token ) break; @@ -1109,100 +1095,59 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) { if( !CG_ParseColor( bp->dLightColor, text_p ) ) break; - - token = COM_Parse( text_p ); - if( Q_stricmp( token, "}" ) ) - { - CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); - break; - } } - - continue; } else if( !Q_stricmp( token, "cullOnStartSolid" ) ) { bp->cullOnStartSolid = qtrue; - - continue; } else if( !Q_stricmp( token, "radius" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->radius.delayRandFrac, + qfalse ); bp->radius.delay = (int)number; - bp->radius.delayRandFrac = randFrac; token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->radius.initial = number; - bp->radius.initialRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->radius.initial, + &bp->radius.initialRandFrac, qfalse ); + if( !CG_ParseFinal( &bp->radius, text_p ) ) + break; + } + else if( !Q_stricmp( token, "physicsRadius" ) ) + { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - - if( !Q_stricmp( token, "-" ) ) - { - bp->radius.final = PARTICLES_SAME_AS_INITIAL; - bp->radius.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->radius.final = number; - bp->radius.finalRandFrac = randFrac; - } - - continue; + + bp->physicsRadius = atoi( token ); } else if( !Q_stricmp( token, "alpha" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->alpha.delayRandFrac, + qfalse ); bp->alpha.delay = (int)number; - bp->alpha.delayRandFrac = randFrac; token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->alpha.initial = number; - bp->alpha.initialRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->alpha.initial, + &bp->alpha.initialRandFrac, qfalse ); - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseFinal( &bp->alpha, text_p ) ) break; - - if( !Q_stricmp( token, "-" ) ) - { - bp->alpha.final = PARTICLES_SAME_AS_INITIAL; - bp->alpha.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - - bp->alpha.final = number; - bp->alpha.finalRandFrac = randFrac; - } - - continue; } else if( !Q_stricmp( token, "color" ) ) { @@ -1210,10 +1155,9 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->colorDelayRandFrac, + qfalse ); bp->colorDelay = (int)number; - bp->colorDelayRandFrac = randFrac; token = COM_Parse( text_p ); if( !*token ) @@ -1224,34 +1168,18 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) if( !CG_ParseColor( bp->initialColor, text_p ) ) break; - token = COM_Parse( text_p ); - if( Q_stricmp( token, "}" ) ) - { - CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); - break; - } - token = COM_Parse( text_p ); if( !*token ) break; if( !Q_stricmp( token, "-" ) ) { - bp->finalColor[ 0 ] = bp->initialColor[ 0 ]; - bp->finalColor[ 1 ] = bp->initialColor[ 1 ]; - bp->finalColor[ 2 ] = bp->initialColor[ 2 ]; + memcpy( bp->finalColor, bp->initialColor, sizeof( bp->finalColor ) ); } else if( !Q_stricmp( token, "{" ) ) { if( !CG_ParseColor( bp->finalColor, text_p ) ) break; - - token = COM_Parse( text_p ); - if( Q_stricmp( token, "}" ) ) - { - CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); - break; - } } else { @@ -1264,90 +1192,69 @@ static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); break; } - - continue; } else if( !Q_stricmp( token, "rotation" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->rotation.delayRandFrac, + qfalse ); bp->rotation.delay = (int)number; - bp->rotation.delayRandFrac = randFrac; token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); - - bp->rotation.initial = number; - bp->rotation.initialRandFrac = randFrac; + CG_ParseValueAndVariance( token, &bp->rotation.initial, + &bp->rotation.initialRandFrac, qtrue ); - token = COM_Parse( text_p ); - if( !token ) + if( !CG_ParseFinal( &bp->rotation, text_p ) ) break; - - if( !Q_stricmp( token, "-" ) ) - { - bp->rotation.final = PARTICLES_SAME_AS_INITIAL; - bp->rotation.finalRandFrac = 0.0f; - } - else - { - CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); - - bp->rotation.final = number; - bp->rotation.finalRandFrac = randFrac; - } - - continue; } else if( !Q_stricmp( token, "lifeTime" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bp->lifeTimeRandFrac, qfalse ); bp->lifeTime = (int)number; - bp->lifeTimeRandFrac = randFrac; continue; } else if( !Q_stricmp( token, "childSystem" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; Q_strncpyz( bp->childSystemName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "onDeathSystem" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; Q_strncpyz( bp->onDeathSystemName, token, MAX_QPATH ); - - continue; } else if( !Q_stricmp( token, "childTrailSystem" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; Q_strncpyz( bp->childTrailSystemName, token, MAX_QPATH ); + } + else if( !Q_stricmp( token, "scaleWithCharge" ) ) + { + token = COM_Parse( text_p ); + if( !*token ) + break; - continue; + bp->scaleWithCharge = atof( token ); } else if( !Q_stricmp( token, "}" ) ) return qtrue; //reached the end of this particle @@ -1384,17 +1291,14 @@ Parse a particle ejector section static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p ) { char *token; - float number, randFrac; + float number; // read optional parameters while( 1 ) { token = COM_Parse( text_p ); - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; if( !Q_stricmp( token, "{" ) ) @@ -1412,43 +1316,38 @@ static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR ); return qfalse; } - else if( numBaseParticles == MAX_BASEPARTICLES ) + + if( numBaseParticles == MAX_BASEPARTICLES ) { CG_Printf( S_COLOR_RED "ERROR: maximum number of particles (%d) reached\n", MAX_BASEPARTICLES ); return qfalse; } - else - { - //start parsing particles again - bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ]; - bpe->numParticles++; - numBaseParticles++; - } - continue; + + //start parsing particles again + bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ]; + bpe->numParticles++; + numBaseParticles++; } else if( !Q_stricmp( token, "delay" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bpe->eject.delayRandFrac, + qfalse ); bpe->eject.delay = (int)number; - bpe->eject.delayRandFrac = randFrac; - - continue; } else if( !Q_stricmp( token, "period" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; bpe->eject.initial = atoi_neg( token, qfalse ); token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "-" ) ) @@ -1457,17 +1356,15 @@ static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text bpe->eject.final = atoi_neg( token, qfalse ); token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; CG_ParseValueAndVariance( token, NULL, &bpe->eject.randFrac, qfalse ); - - continue; } else if( !Q_stricmp( token, "count" ) ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "infinite" ) ) @@ -1477,13 +1374,10 @@ static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text } else { - CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); - + CG_ParseValueAndVariance( token, &number, &bpe->totalParticlesRandFrac, + qfalse ); bpe->totalParticles = (int)number; - bpe->totalParticlesRandFrac = randFrac; } - - continue; } else if( !Q_stricmp( token, "particle" ) ) //acceptable text continue; @@ -1517,10 +1411,7 @@ static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p { token = COM_Parse( text_p ); - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) return qfalse; if( !Q_stricmp( token, "{" ) ) @@ -1546,20 +1437,18 @@ static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM ); return qfalse; } - else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) + + if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) { CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n", MAX_BASEPARTICLE_EJECTORS ); return qfalse; } - else - { - //start parsing ejectors again - bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ]; - bps->numEjectors++; - numBaseParticleEjectors++; - } - continue; + + //start parsing ejectors again + bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ]; + bps->numEjectors++; + numBaseParticleEjectors++; } else if( !Q_stricmp( token, "thirdPersonOnly" ) ) bps->thirdPersonOnly = qtrue; @@ -1602,12 +1491,14 @@ static qboolean CG_ParseParticleFile( const char *fileName ) // load the file len = trap_FS_FOpenFile( fileName, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( S_COLOR_RED "ERROR: particle file %s too long\n", fileName ); + trap_FS_FCloseFile( f ); + CG_Printf( S_COLOR_RED "ERROR: particle file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); return qfalse; } @@ -1623,23 +1514,13 @@ static qboolean CG_ParseParticleFile( const char *fileName ) { token = COM_Parse( &text_p ); - if( !Q_stricmp( token, "" ) ) + if( !*token ) break; if( !Q_stricmp( token, "{" ) ) { if( psNameSet ) { - //check for name space clashes - for( i = 0; i < numBaseParticleSystems; i++ ) - { - if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) ) - { - CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName ); - return qfalse; - } - } - Q_strncpyz( baseParticleSystems[ numBaseParticleSystems ].name, psName, MAX_QPATH ); if( !CG_ParseParticleSystem( &baseParticleSystems[ numBaseParticleSystems ], &text_p, psName ) ) @@ -1657,10 +1538,8 @@ static qboolean CG_ParseParticleFile( const char *fileName ) MAX_BASEPARTICLE_SYSTEMS ); return qfalse; } - else - numBaseParticleSystems++; - continue; + numBaseParticleSystems++; } else { @@ -1668,10 +1547,25 @@ static qboolean CG_ParseParticleFile( const char *fileName ) return qfalse; } } - - if( !psNameSet ) + else if( !psNameSet ) { Q_strncpyz( psName, token, sizeof( psName ) ); + + //check for name space clashes + for( i = 0; i < numBaseParticleSystems; i++ ) + { + if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName ); + break; + } + } + if( i < numBaseParticleSystems ) + { + SkipBracedSection( &text_p, 0 ); + continue; + } + psNameSet = qtrue; } else @@ -1810,6 +1704,31 @@ void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ) ps->normalValid = qtrue; VectorCopy( normal, ps->normal ); VectorNormalize( ps->normal ); + + CG_SetParticleSystemLastNormal( ps, normal ); +} + +/* +=============== +CG_SetParticleSystemLastNormal +=============== +*/ +void CG_SetParticleSystemLastNormal( particleSystem_t *ps, const float *normal ) +{ + if( ps == NULL || !ps->valid ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); + return; + } + + if( normal ) + { + ps->lastNormalIsCurrent = qtrue; + VectorCopy( normal, ps->lastNormal ); + VectorNormalize( ps->lastNormal ); + } + else + ps->lastNormalIsCurrent = qfalse; } @@ -2056,6 +1975,17 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) VectorCopy( ps->normal, acceleration ); break; + + case PMT_LAST_NORMAL: + VectorCopy( ps->lastNormal, acceleration ); + break; + + case PMT_OPPORTUNISTIC_NORMAL: + if( ps->lastNormalIsCurrent ) + VectorCopy( ps->lastNormal, acceleration ); + else + VectorClear( acceleration ); + break; } #define MAX_ACC_RADIUS 1000.0f @@ -2086,11 +2016,13 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) acceleration ); } - radius = CG_LerpValues( p->radius.initial, - p->radius.final, - CG_CalculateTimeFrac( p->birthTime, - p->lifeTime, - p->radius.delay ) ); + // Some particles have a visual radius that differs from their collision radius + if( bp->physicsRadius ) + radius = bp->physicsRadius; + else + radius = CG_LerpValues( p->radius.initial, p->radius.final, + CG_CalculateTimeFrac( p->birthTime, p->lifeTime, + p->radius.delay ) ); VectorSet( mins, -radius, -radius, -radius ); VectorSet( maxs, radius, radius, radius ); @@ -2121,6 +2053,8 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) if( trace.fraction == 1.0f || bounce == 0.0f ) { VectorCopy( newOrigin, p->origin ); + if( CG_IsParticleSystemValid( &p->childParticleSystem ) ) + CG_SetParticleSystemLastNormal( p->childParticleSystem, NULL ); return; } @@ -2162,6 +2096,12 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) } VectorCopy( trace.endpos, p->origin ); + + if( !trace.allsolid ) + { + if( CG_IsParticleSystemValid( &p->childParticleSystem ) ) + CG_SetParticleSystemLastNormal( p->childParticleSystem, trace.plane.normal ); + } } @@ -2394,7 +2334,7 @@ static void CG_RenderParticle( particle_t *p ) p->lf.animation = &bp->modelAnimation; //run animation - CG_RunLerpFrame( &p->lf ); + CG_RunLerpFrame( &p->lf, 1.0f ); re.oldframe = p->lf.oldFrame; re.frame = p->lf.frame; @@ -2505,9 +2445,8 @@ void CG_ParticleSystemEntity( centity_t *cent ) if( CG_IsParticleSystemValid( ¢->entityPS ) ) { - CG_SetAttachmentPoint( ¢->entityPS->attachment, cent->lerpOrigin ); CG_SetAttachmentCent( ¢->entityPS->attachment, cent ); - CG_AttachToPoint( ¢->entityPS->attachment ); + CG_AttachToCent( ¢->entityPS->attachment ); } else cent->entityPSMissing = qtrue; diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c index 3bd0e65..16779cb 100644 --- a/src/cgame/cg_players.c +++ b/src/cgame/cg_players.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_players.c -- handle the media and animation for player entities - #include "cg_local.h" char *cg_customSoundNames[ MAX_CUSTOM_SOUNDS ] = @@ -107,12 +107,12 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); trap_FS_FCloseFile( f ); return qfalse; } @@ -480,51 +480,17 @@ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelN // if any skins failed to load, return failure if( !CG_RegisterClientSkin( ci, modelName, skinName ) ) { - Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); - return qfalse; + Com_Printf( "Failed to load skin file: %s : %s. Loading default\n", modelName, skinName ); + if( !CG_RegisterClientSkin( ci, modelName, "default" ) ) + { + Com_Printf( S_COLOR_RED "Failed to load default skin file!\n" ); + return qfalse; + } } - //FIXME: skins do not load without icon present. do we want icons anyway? -/* Com_sprintf( filename, sizeof( filename ), "models/players/%s/icon_%s.tga", modelName, skinName ); - ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); - if( !ci->modelIcon ) - { - Com_Printf( "Failed to load icon file: %s\n", filename ); - return qfalse; - }*/ - return qtrue; } -/* -==================== -CG_ColorFromString -==================== -*/ -static void CG_ColorFromString( const char *v, vec3_t color ) -{ - int val; - - VectorClear( color ); - - val = atoi( v ); - - if( val < 1 || val > 7 ) - { - VectorSet( color, 1, 1, 1 ); - return; - } - - if( val & 1 ) - color[ 2 ] = 1.0f; - - if( val & 2 ) - color[ 1 ] = 1.0f; - - if( val & 4 ) - color[ 0 ] = 1.0f; -} - /* =================== @@ -535,24 +501,16 @@ Load it now, taking the disk hits */ static void CG_LoadClientInfo( clientInfo_t *ci ) { - const char *dir, *fallback; + const char *dir; int i; const char *s; int clientNum; if( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) ) - { - if( cg_buildScript.integer ) - CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName ); - - // fall back - if( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default" ) ) - CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); - } + CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName ); // sounds dir = ci->modelName; - fallback = DEFAULT_MODEL; for( i = 0; i < MAX_CUSTOM_SOUNDS; i++ ) { @@ -578,15 +536,11 @@ static void CG_LoadClientInfo( clientInfo_t *ci ) s = cg_customSoundNames[ 0 ]; //death1 ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse ); - if( !ci->sounds[ i ] ) - ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse ); } } else { ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse ); - if( !ci->sounds[ i ] ) - ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse ); } } @@ -649,15 +603,15 @@ static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) CG_GetCorpseNum ====================== */ -static int CG_GetCorpseNum( pClass_t class ) +static int CG_GetCorpseNum( class_t class ) { int i; clientInfo_t *match; char *modelName; char *skinName; - modelName = BG_FindModelNameForClass( class ); - skinName = BG_FindSkinNameForClass( class ); + modelName = BG_ClassConfig( class )->modelName; + skinName = BG_ClassConfig( class )->skinName; for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) { @@ -666,10 +620,10 @@ static int CG_GetCorpseNum( pClass_t class ) if( !match->infoValid ) continue; - if( !Q_stricmp( modelName, match->modelName ) - && !Q_stricmp( skinName, match->skinName ) ) + if( !Q_stricmp( modelName, match->modelName ) && + !Q_stricmp( skinName, match->skinName ) ) { - // this clientinfo is identical, so use it's handles + // this clientinfo is identical, so use its handles return i; } } @@ -716,7 +670,7 @@ static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) CG_PrecacheClientInfo ====================== */ -void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) +void CG_PrecacheClientInfo( class_t class, char *model, char *skin ) { clientInfo_t *ci; clientInfo_t newInfo; @@ -728,19 +682,12 @@ void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) // model Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); - Q_strncpyz( newInfo.headModelName, model, sizeof( newInfo.headModelName ) ); - // modelName didn not include a skin name + // modelName did not include a skin name if( !skin ) - { Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else - { Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); - Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); - } newInfo.infoValid = qtrue; @@ -749,6 +696,34 @@ void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin ) CG_LoadClientInfo( ci ); } +/* +============= +CG_StatusMessages + +Print messages for player status changes +============= +*/ +static void CG_StatusMessages( clientInfo_t *new, clientInfo_t *old ) +{ + if( !old->infoValid ) + return; + + if( strcmp( new->name, old->name ) ) + CG_Printf( "%s" S_COLOR_WHITE " renamed to %s\n", old->name, new->name ); + + if( old->team != new->team ) + { + if( new->team == TEAM_NONE ) + CG_Printf( "%s" S_COLOR_WHITE " left the %ss\n", new->name, + BG_TeamName( old->team ) ); + else if( old->team == TEAM_NONE ) + CG_Printf( "%s" S_COLOR_WHITE " joined the %ss\n", new->name, + BG_TeamName( new->team ) ); + else + CG_Printf( "%s" S_COLOR_WHITE " left the %ss and joined the %ss\n", + new->name, BG_TeamName( old->team ), BG_TeamName( new->team ) ); + } +} /* ====================== @@ -774,45 +749,35 @@ void CG_NewClientInfo( int clientNum ) // the old value memset( &newInfo, 0, sizeof( newInfo ) ); - + // isolate the player's name v = Info_ValueForKey( configstring, "n" ); Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); - // colors - v = Info_ValueForKey( configstring, "c1" ); - CG_ColorFromString( v, newInfo.color1 ); - - v = Info_ValueForKey( configstring, "c2" ); - CG_ColorFromString( v, newInfo.color2 ); - - // bot skill - v = Info_ValueForKey( configstring, "skill" ); - newInfo.botSkill = atoi( v ); - - // handicap - v = Info_ValueForKey( configstring, "hc" ); - newInfo.handicap = atoi( v ); - - // wins - v = Info_ValueForKey( configstring, "w" ); - newInfo.wins = atoi( v ); - - // losses - v = Info_ValueForKey( configstring, "l" ); - newInfo.losses = atoi( v ); - // team v = Info_ValueForKey( configstring, "t" ); newInfo.team = atoi( v ); - // team task - v = Info_ValueForKey( configstring, "tt" ); - newInfo.teamTask = atoi( v ); + // if this is us, execute team-specific config files + // the spectator config is a little unreliable because it's easy to get on + // to the spectator team without joining it - e.g. when a new game starts. + // It's not a big deal because the spec config is the least important + // slash used anyway. + // I guess it's possible for someone to change teams during a restart and + // for that to then be missed here. But that's rare enough that people can + // just exec the configs manually, I think. + if( clientNum == cg.clientNum && ci->infoValid && + ci->team != newInfo.team ) + { + char config[ MAX_CVAR_VALUE_STRING ]; - // team leader - v = Info_ValueForKey( configstring, "tl" ); - newInfo.teamLeader = atoi( v ); + trap_Cvar_VariableStringBuffer( + va( "cg_%sConfig", BG_TeamName( newInfo.team ) ), + config, sizeof( config ) ); + + if( config[ 0 ] ) + trap_SendConsoleCommand( va( "exec \"%s\"\n", config ) ); + } // model v = Info_ValueForKey( configstring, "model" ); @@ -832,25 +797,11 @@ void CG_NewClientInfo( int clientNum ) *slash = 0; } - //CG_Printf( "NCI: %s\n", v ); - - // head model - v = Info_ValueForKey( configstring, "hmodel" ); - Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + // voice + v = Info_ValueForKey( configstring, "v" ); + Q_strncpyz( newInfo.voice, v, sizeof( newInfo.voice ) ); - slash = strchr( newInfo.headModelName, '/' ); - - if( !slash ) - { - // modelName didn not include a skin name - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } - else - { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - // truncate modelName - *slash = 0; - } + CG_StatusMessages( &newInfo, ci ); // replace whatever was there with the new one newInfo.infoValid = qtrue; @@ -909,90 +860,11 @@ cg.time should be between oldFrameTime and frameTime after exit */ static void CG_RunPlayerLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { - int f, numFrames; - animation_t *anim; - - // debugging tool to get no animations - if( cg_animSpeed.integer == 0 ) - { - lf->oldFrame = lf->frame = lf->backlerp = 0; - return; - } - // see if the animation sequence is switching if( newAnimation != lf->animationNumber || !lf->animation ) - { CG_SetLerpFrameAnimation( ci, lf, newAnimation ); - } - - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if( cg.time >= lf->frameTime ) - { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; - - // get the next frame based on the animation - anim = lf->animation; - if( !anim->frameLerp ) - return; // shouldn't happen - - if( cg.time < lf->animationTime ) - lf->frameTime = lf->animationTime; // initial lerp - else - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - f *= speedScale; // adjust for haste, etc - numFrames = anim->numFrames; - - if( anim->flipflop ) - numFrames *= 2; - - if( f >= numFrames ) - { - f -= numFrames; - if( anim->loopFrames ) - { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } - else - { - f = numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = cg.time; - } - } - - if( anim->reversed ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - f; - else if( anim->flipflop && f>=anim->numFrames ) - lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames ); - else - lf->frame = anim->firstFrame + f; - - if( cg.time > lf->frameTime ) - { - lf->frameTime = cg.time; - - if( cg_debugAnim.integer ) - CG_Printf( "Clamp lf->frameTime\n" ); - } - } - - if( lf->frameTime > cg.time + 200 ) - lf->frameTime = cg.time; - - if( lf->oldFrameTime > cg.time ) - lf->oldFrameTime = cg.time; - - // calculate current lerp value - if( lf->frameTime == lf->oldFrameTime ) - lf->backlerp = 0; - else - lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + CG_RunLerpFrame( lf, speedScale ); } @@ -1735,13 +1607,6 @@ static void CG_PlayerSprites( centity_t *cent ) CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); return; } - - if( cent->currentState.eFlags & EF_TALK ) - { - // the masses have decreed this to be wrong -/* CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); - return;*/ - } } /* @@ -1754,7 +1619,7 @@ Returns the Z component of the surface being shadowed =============== */ #define SHADOW_DISTANCE 128 -static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t class ) +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, class_t class ) { vec3_t end, mins, maxs; trace_t trace; @@ -1762,7 +1627,7 @@ static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t c entityState_t *es = ¢->currentState; vec3_t surfNormal = { 0.0f, 0.0f, 1.0f }; - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); mins[ 2 ] = 0.0f; maxs[ 2 ] = 2.0f; @@ -1808,7 +1673,7 @@ static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t c // without taking a spot in the cg_marks array CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, 0.0f, 0.0f, 0.0f, alpha, qfalse, - 24.0f * BG_FindShadowScaleForClass( class ), qtrue ); + 24.0f * BG_ClassConfig( class )->shadowScale, qtrue ); return qtrue; } @@ -1821,7 +1686,7 @@ CG_PlayerSplash Draw a mark at the water surface =============== */ -static void CG_PlayerSplash( centity_t *cent, pClass_t class ) +static void CG_PlayerSplash( centity_t *cent, class_t class ) { vec3_t start, end; vec3_t mins, maxs; @@ -1831,7 +1696,7 @@ static void CG_PlayerSplash( centity_t *cent, pClass_t class ) if( !cg_shadows.integer ) return; - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); VectorCopy( cent->lerpOrigin, end ); end[ 2 ] += mins[ 2 ]; @@ -1861,7 +1726,7 @@ static void CG_PlayerSplash( centity_t *cent, pClass_t class ) CG_ImpactMark( cgs.media.wakeMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, - 32.0f * BG_FindShadowScaleForClass( class ), qtrue ); + 32.0f * BG_ClassConfig( class )->shadowScale, qtrue ); } @@ -2012,7 +1877,7 @@ void CG_Player( centity_t *cent ) qboolean shadow = qfalse; float shadowPlane = 0.0f; entityState_t *es = ¢->currentState; - pClass_t class = ( es->misc >> 8 ) & 0xFF; + class_t class = ( es->misc >> 8 ) & 0xFF; float scale; vec3_t tempAxis[ 3 ], tempAxis2[ 3 ]; vec3_t angles; @@ -2051,7 +1916,7 @@ void CG_Player( centity_t *cent ) { vec3_t mins, maxs; - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); } @@ -2150,7 +2015,7 @@ void CG_Player( centity_t *cent ) else VectorCopy( es->angles2, surfNormal ); - BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); VectorMA( legs.origin, -TRACE_DEPTH, surfNormal, end ); VectorMA( legs.origin, 1.0f, surfNormal, start ); @@ -2166,7 +2031,7 @@ void CG_Player( centity_t *cent ) } //rescale the model - scale = BG_FindModelScaleForClass( class ); + scale = BG_ClassConfig( class )->modelScale; if( scale != 1.0f ) { @@ -2178,7 +2043,7 @@ void CG_Player( centity_t *cent ) } //offset on the Z axis if required - VectorMA( legs.origin, BG_FindZOffsetForClass( class ), surfNormal, legs.origin ); + VectorMA( legs.origin, BG_ClassConfig( class )->zOffset, surfNormal, legs.origin ); VectorCopy( legs.origin, legs.lightingOrigin ); VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all @@ -2233,6 +2098,21 @@ void CG_Player( centity_t *cent ) head.renderfx = renderfx; trap_R_AddRefEntityToScene( &head ); + + // if this player has been hit with poison cloud, add an effect PS + if( ( es->eFlags & EF_POISONCLOUDED ) && + ( es->number != cg.snap->ps.clientNum || cg.renderingThirdPerson ) ) + { + if( !CG_IsParticleSystemValid( ¢->poisonCloudedPS ) ) + cent->poisonCloudedPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudedPS ); + + CG_SetAttachmentTag( ¢->poisonCloudedPS->attachment, + head, head.hModel, "tag_head" ); + CG_SetAttachmentCent( ¢->poisonCloudedPS->attachment, cent ); + CG_AttachToTag( ¢->poisonCloudedPS->attachment ); + } + else if( CG_IsParticleSystemValid( ¢->poisonCloudedPS ) ) + CG_DestroyParticleSystem( ¢->poisonCloudedPS ); } // @@ -2247,7 +2127,7 @@ void CG_Player( centity_t *cent ) } CG_PlayerUpgrades( cent, &torso ); - + //sanity check that particle systems are stopped when dead if( es->eFlags & EF_DEAD ) { @@ -2277,7 +2157,7 @@ void CG_Corpse( centity_t *cent ) int renderfx; qboolean shadow = qfalse; float shadowPlane; - vec3_t origin, liveZ, deadZ; + vec3_t origin, aliveZ, deadZ; float scale; corpseNum = CG_GetCorpseNum( es->clientNum ); @@ -2297,10 +2177,8 @@ void CG_Corpse( centity_t *cent ) memset( &head, 0, sizeof( head ) ); VectorCopy( cent->lerpOrigin, origin ); - BG_FindBBoxForClass( es->clientNum, liveZ, NULL, NULL, deadZ, NULL ); - origin[ 2 ] -= ( liveZ[ 2 ] - deadZ[ 2 ] ); - - VectorCopy( es->angles, cent->lerpAngles ); + BG_ClassBoundingBox( es->clientNum, aliveZ, NULL, NULL, deadZ, NULL ); + origin[ 2 ] -= ( aliveZ[ 2 ] - deadZ[ 2 ] ); // get the rotation information if( !ci->nonsegmented ) @@ -2364,11 +2242,11 @@ void CG_Corpse( centity_t *cent ) VectorCopy( origin, legs.lightingOrigin ); legs.shadowPlane = shadowPlane; legs.renderfx = renderfx; - legs.origin[ 2 ] += BG_FindZOffsetForClass( es->clientNum ); + legs.origin[ 2 ] += BG_ClassConfig( es->clientNum )->zOffset; VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all //rescale the model - scale = BG_FindModelScaleForClass( es->clientNum ); + scale = BG_ClassConfig( es->clientNum )->modelScale; if( scale != 1.0f ) { @@ -2379,7 +2257,6 @@ void CG_Corpse( centity_t *cent ) legs.nonNormalizedAxes = qtrue; } - //CG_AddRefEntityWithPowerups( &legs, es->misc, ci->team ); trap_R_AddRefEntityToScene( &legs ); // if the model failed, allow the default nullmodel to be displayed @@ -2404,7 +2281,6 @@ void CG_Corpse( centity_t *cent ) torso.shadowPlane = shadowPlane; torso.renderfx = renderfx; - //CG_AddRefEntityWithPowerups( &torso, es->misc, ci->team ); trap_R_AddRefEntityToScene( &torso ); // @@ -2423,7 +2299,6 @@ void CG_Corpse( centity_t *cent ) head.shadowPlane = shadowPlane; head.renderfx = renderfx; - //CG_AddRefEntityWithPowerups( &head, es->misc, ci->team ); trap_R_AddRefEntityToScene( &head ); } } @@ -2475,7 +2350,7 @@ void CG_ResetPlayerEntity( centity_t *cent ) cent->pe.nonseg.pitching = qfalse; if( cg_debugPosition.integer ) - CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + CG_Printf( "%i ResetPlayerEntity yaw=%f\n", cent->currentState.number, cent->pe.torso.yawAngle ); } /* @@ -2500,66 +2375,35 @@ void CG_PlayerDisconnect( vec3_t org ) } } -/* -================= -CG_Bleed - -This is the spurt of blood when a character gets hit -================= -*/ -void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ) +centity_t *CG_GetPlayerLocation( void ) { - pTeam_t team = cgs.clientinfo[ entityNum ].team; - qhandle_t bleedPS; - particleSystem_t *ps; - - if( !cg_blood.integer ) - return; + int i; + centity_t *eloc, *best; + float bestlen, len; + vec3_t origin; - if( team == PTE_ALIENS ) - bleedPS = cgs.media.alienBleedPS; - else if( team == PTE_HUMANS ) - bleedPS = cgs.media.humanBleedPS; - else - return; + best = NULL; + bestlen = 0.0f; - ps = CG_SpawnNewParticleSystem( bleedPS ); + VectorCopy( cg.predictedPlayerState.origin, origin ); - if( CG_IsParticleSystemValid( &ps ) ) + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { - CG_SetAttachmentPoint( &ps->attachment, origin ); - CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] ); - CG_AttachToPoint( &ps->attachment ); + eloc = &cg_entities[ i ]; + if( !eloc->valid || eloc->currentState.eType != ET_LOCATION ) + continue; - CG_SetParticleSystemNormal( ps, normal ); - } -} + len = DistanceSquared(origin, eloc->lerpOrigin); -/* -=============== -CG_AtHighestClass + if( best != NULL && len > bestlen ) + continue; -Is the local client at the highest class possible? -=============== -*/ -qboolean CG_AtHighestClass( void ) -{ - int i; - qboolean superiorClasses = qfalse; + if( !trap_R_inPVS( origin, eloc->lerpOrigin ) ) + continue; - for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) - { - if( BG_ClassCanEvolveFromTo( - cg.predictedPlayerState.stats[ STAT_PCLASS ], i, - ALIEN_MAX_KILLS, 0 ) >= 0 && - BG_FindStagesForClass( i, cgs.alienStage ) && - BG_ClassIsAllowed( i ) ) - { - superiorClasses = qtrue; - break; - } + bestlen = len; + best = eloc; } - return !superiorClasses; + return best; } - diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c index e1bcb09..8ff9be4 100644 --- a/src/cgame/cg_playerstate.c +++ b/src/cgame/cg_playerstate.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -26,7 +27,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // following another player or playing back a demo, it will be checked // when the snapshot transitions like all the other entities - #include "cg_local.h" /* @@ -245,10 +245,8 @@ CG_CheckLocalSounds */ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { - int reward; - - // don't play the sounds if the player just changed teams - if( ps->persistant[ PERS_TEAM ] != ops->persistant[ PERS_TEAM ] ) + // don't play the sounds if the player just spawned + if( ps->persistant[ PERS_SPECSTATE ] != ops->persistant[ PERS_SPECSTATE ] ) return; // health changes of more than -1 should make pain sounds @@ -257,14 +255,6 @@ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) if( ps->stats[ STAT_HEALTH ] > 0 ) CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[ STAT_HEALTH ] ); } - - - // if we are going into the intermission, don't start any voices - if( cg.intermissionStarted ) - return; - - // reward sounds - reward = qfalse; } @@ -301,7 +291,7 @@ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) } if( cg.snap->ps.pm_type != PM_INTERMISSION && - ps->persistant[ PERS_TEAM ] != TEAM_SPECTATOR ) + ps->persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT ) CG_CheckLocalSounds( ps, ops ); // run events @@ -313,5 +303,11 @@ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) cg.duckChange = ps->viewheight - ops->viewheight; cg.duckTime = cg.time; } + + // changed team + if( ps->stats[ STAT_TEAM ] != ops->stats[ STAT_TEAM ] ) + { + cg.lastHealthCross = 0; + cg.chargeMeterAlpha = 0.0f; + } } - diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c index 03442ed..a791303 100644 --- a/src/cgame/cg_predict.c +++ b/src/cgame/cg_predict.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -26,7 +27,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // ahead the client's movement. // It also handles local physics interaction, like fragments bouncing off walls - #include "cg_local.h" static pmove_t cg_pmove; @@ -138,7 +138,7 @@ static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, bmaxs[ 2 ] = zu; if( i == cg_numSolidEntities ) - BG_FindBBoxForClass( ( ent->misc >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( ( ent->misc >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL ); cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); VectorCopy( vec3_origin, angles ); @@ -273,7 +273,7 @@ int CG_PointContents( const vec3_t point, int passEntityNum ) if( !cmodel ) continue; - contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + contents |= trap_CM_TransformedPointContents( point, cmodel, cent->lerpOrigin, cent->lerpAngles ); } return contents; @@ -476,13 +476,13 @@ static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps ) if( fabs( AngleDelta( ps->viewangles[ 0 ], pps->viewangles[ 0 ] ) ) > 1.0f || fabs( AngleDelta( ps->viewangles[ 1 ], pps->viewangles[ 1 ] ) ) > 1.0f || - fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f ) + fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f ) { return 12; } if( pps->viewheight != ps->viewheight ) - return 13; + return 13; if( pps->damageEvent != ps->damageEvent || pps->damageYaw != ps->damageYaw || @@ -504,13 +504,6 @@ static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps ) return 16; } - for( i = 0; i < MAX_WEAPONS; i++ ) - { - // GH FIXME - if( pps->ammo != ps->ammo || pps->clips != ps->clips ) - return 18; - } - if( pps->generic1 != ps->generic1 || pps->loopSound != ps->loopSound ) { @@ -551,7 +544,6 @@ void CG_PredictPlayerState( void ) { int cmdNum, current, i; playerState_t oldPlayerState; - qboolean moved; usercmd_t oldestCmd; usercmd_t latestCmd; int stateIndex = 0, predictCmd = 0; @@ -590,12 +582,12 @@ void CG_PredictPlayerState( void ) cg_pmove.debugLevel = cg_debugMove.integer; if( cg_pmove.ps->pm_type == PM_DEAD ) - cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + cg_pmove.tracemask = MASK_DEADSOLID; else cg_pmove.tracemask = MASK_PLAYERSOLID; - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) - cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + cg_pmove.tracemask = MASK_DEADSOLID; // spectators can fly through bodies cg_pmove.noFootsteps = 0; @@ -638,9 +630,15 @@ void CG_PredictPlayerState( void ) } if( pmove_msec.integer < 8 ) + { trap_Cvar_Set( "pmove_msec", "8" ); + trap_Cvar_Update(&pmove_msec); + } else if( pmove_msec.integer > 33 ) + { trap_Cvar_Set( "pmove_msec", "33" ); + trap_Cvar_Update(&pmove_msec); + } cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; cg_pmove.pmove_msec = pmove_msec.integer; @@ -700,22 +698,22 @@ void CG_PredictPlayerState( void ) // make sure the state differences are acceptable errorcode = CG_IsUnacceptableError( &cg.predictedPlayerState, &cg.savedPmoveStates[ i ] ); - + if( errorcode ) { if( cg_showmiss.integer ) CG_Printf("errorcode %d at %d\n", errorcode, cg.time); break; } - + // this one is almost exact, so we'll copy it in as the starting point *cg_pmove.ps = cg.savedPmoveStates[ i ]; // advance the head cg.stateHead = ( i + 1 ) % NUM_SAVED_STATES; - + // set the next command to predict predictCmd = cg.lastPredictedCommand + 1; - + // a saved state matched, so flag it error = qfalse; break; @@ -737,9 +735,6 @@ void CG_PredictPlayerState( void ) stateIndex = cg.stateHead; } - // run cmds - moved = qfalse; - for( cmdNum = current - CMD_BACKUP + 1; cmdNum <= current; cmdNum++ ) { // get the command @@ -857,8 +852,6 @@ void CG_PredictPlayerState( void ) stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES; } - moved = qtrue; - // add push trigger movement effects CG_TouchTriggerPrediction( ); diff --git a/src/cgame/cg_ptr.c b/src/cgame/cg_ptr.c deleted file mode 100644 index 1881087..0000000 --- a/src/cgame/cg_ptr.c +++ /dev/null @@ -1,81 +0,0 @@ -/* -=========================================================================== -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous 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. - -Tremulous 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// cg_ptr.c -- post timeout restoration handling - - -#include "cg_local.h" - -#define PTRC_FILE "ptrc.cfg" - -/* -=============== -CG_ReadPTRCode - -Read a PTR code from disk -=============== -*/ -int CG_ReadPTRCode( void ) -{ - int len; - char text[ 16 ]; - fileHandle_t f; - - // load the file - len = trap_FS_FOpenFile( PTRC_FILE, &f, FS_READ ); - if( len <= 0 ) - return 0; - - // should never happen - malformed write - if( len >= sizeof( text ) - 1 ) - return 0; - - trap_FS_Read( text, len, f ); - text[ len ] = 0; - trap_FS_FCloseFile( f ); - - return atoi( text ); -} - -/* -=============== -CG_WritePTRCode - -Write a PTR code to disk -=============== -*/ -void CG_WritePTRCode( int code ) -{ - char text[ 16 ]; - fileHandle_t f; - - Com_sprintf( text, 16, "%d", code ); - - // open file - if( trap_FS_FOpenFile( PTRC_FILE, &f, FS_WRITE ) < 0 ) - return; - - // write the code - trap_FS_Write( text, strlen( text ), f ); - - trap_FS_FCloseFile( f ); -} diff --git a/src/cgame/cg_public.h b/src/cgame/cg_public.h index 543a222..eef8d40 100644 --- a/src/cgame/cg_public.h +++ b/src/cgame/cg_public.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,51 +17,47 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ +#ifndef CGAME_PUBLIC_H +#define CGAME_PUBLIC_H + +#include "qcommon/q_shared.h" -#define CMD_BACKUP 64 -#define CMD_MASK (CMD_BACKUP - 1) +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) // allow a lot of command backups for very fast systems // multiple commands may be combined into a single packet, so this // needs to be larger than PACKET_BACKUP - -#define MAX_ENTITIES_IN_SNAPSHOT 256 +#define MAX_ENTITIES_IN_SNAPSHOT 256 // snapshots are a view of the server at a given time // Snapshots are generated at regular time intervals by the server, // but they may not be sent if a client's rate level is exceeded, or // they may be dropped by the network. -typedef struct -{ - int snapFlags; // SNAPFLAG_RATE_DELAYED, etc - int ping; +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; - int serverTime; // server time the message is valid for (in msec) + int serverTime; // server time the message is valid for (in msec) - byte areamask[ MAX_MAP_AREA_BYTES ]; // portalarea visibility bits + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits - playerState_t ps; // complete information about the current player at this time + playerState_t ps; // complete information about the current player at this time - int numEntities; // all of the entities that need to be presented - entityState_t entities[ MAX_ENTITIES_IN_SNAPSHOT ]; // at the time of this snapshot + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot - int numServerCommands; // text based server commands to execute when this - int serverCommandSequence; // snapshot becomes current + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current } snapshot_t; -enum -{ - CGAME_EVENT_NONE, - CGAME_EVENT_TEAMMENU, - CGAME_EVENT_SCOREBOARD, - CGAME_EVENT_EDITHUD -}; +enum { CGAME_EVENT_NONE, CGAME_EVENT_TEAMMENU, CGAME_EVENT_SCOREBOARD, CGAME_EVENT_EDITHUD }; /* ================================================================== @@ -70,147 +67,146 @@ functions imported from the main executable ================================================================== */ -#define CGAME_IMPORT_API_VERSION 4 - -typedef enum -{ - CG_PRINT, - CG_ERROR, - CG_MILLISECONDS, - CG_CVAR_REGISTER, - CG_CVAR_UPDATE, - CG_CVAR_SET, - CG_CVAR_VARIABLESTRINGBUFFER, - CG_ARGC, - CG_ARGV, - CG_ARGS, - CG_FS_FOPENFILE, - CG_FS_READ, - CG_FS_WRITE, - CG_FS_FCLOSEFILE, - CG_SENDCONSOLECOMMAND, - CG_ADDCOMMAND, - CG_SENDCLIENTCOMMAND, - CG_UPDATESCREEN, - CG_CM_LOADMAP, - CG_CM_NUMINLINEMODELS, - CG_CM_INLINEMODEL, - CG_CM_LOADMODEL, - CG_CM_TEMPBOXMODEL, - CG_CM_POINTCONTENTS, - CG_CM_TRANSFORMEDPOINTCONTENTS, - CG_CM_BOXTRACE, - CG_CM_TRANSFORMEDBOXTRACE, - CG_CM_MARKFRAGMENTS, - CG_S_STARTSOUND, - CG_S_STARTLOCALSOUND, - CG_S_CLEARLOOPINGSOUNDS, - CG_S_ADDLOOPINGSOUND, - CG_S_UPDATEENTITYPOSITION, - CG_S_RESPATIALIZE, - CG_S_REGISTERSOUND, - CG_S_STARTBACKGROUNDTRACK, - CG_R_LOADWORLDMAP, - CG_R_REGISTERMODEL, - CG_R_REGISTERSKIN, - CG_R_REGISTERSHADER, - CG_R_CLEARSCENE, - CG_R_ADDREFENTITYTOSCENE, - CG_R_ADDPOLYTOSCENE, - CG_R_ADDLIGHTTOSCENE, - CG_R_RENDERSCENE, - CG_R_SETCOLOR, +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, #ifndef MODULE_INTERFACE_11 - CG_R_SETCLIPREGION, + CG_R_SETCLIPREGION, #endif - CG_R_DRAWSTRETCHPIC, - CG_R_MODELBOUNDS, - CG_R_LERPTAG, - CG_GETGLCONFIG, - CG_GETGAMESTATE, - CG_GETCURRENTSNAPSHOTNUMBER, - CG_GETSNAPSHOT, - CG_GETSERVERCOMMAND, - CG_GETCURRENTCMDNUMBER, - CG_GETUSERCMD, - CG_SETUSERCMDVALUE, - CG_R_REGISTERSHADERNOMIP, - CG_MEMORY_REMAINING, - CG_R_REGISTERFONT, - CG_KEY_ISDOWN, - CG_KEY_GETCATCHER, - CG_KEY_SETCATCHER, - CG_KEY_GETKEY, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, #ifdef MODULE_INTERFACE_11 - CG_PARSE_ADD_GLOBAL_DEFINE, - CG_PARSE_LOAD_SOURCE, - CG_PARSE_FREE_SOURCE, - CG_PARSE_READ_TOKEN, - CG_PARSE_SOURCE_FILE_AND_LINE, + CG_PARSE_ADD_GLOBAL_DEFINE, + CG_PARSE_LOAD_SOURCE, + CG_PARSE_FREE_SOURCE, + CG_PARSE_READ_TOKEN, + CG_PARSE_SOURCE_FILE_AND_LINE, #endif - CG_S_STOPBACKGROUNDTRACK, - CG_REAL_TIME, - CG_SNAPVECTOR, - CG_REMOVECOMMAND, - CG_R_LIGHTFORPOINT, - CG_CIN_PLAYCINEMATIC, - CG_CIN_STOPCINEMATIC, - CG_CIN_RUNCINEMATIC, - CG_CIN_DRAWCINEMATIC, - CG_CIN_SETEXTENTS, - CG_R_REMAP_SHADER, - CG_S_ADDREALLOOPINGSOUND, - CG_S_STOPLOOPINGSOUND, - - CG_CM_TEMPCAPSULEMODEL, - CG_CM_CAPSULETRACE, - CG_CM_TRANSFORMEDCAPSULETRACE, - CG_R_ADDADDITIVELIGHTTOSCENE, - CG_GET_ENTITY_TOKEN, - CG_R_ADDPOLYSTOSCENE, - CG_R_INPVS, - CG_FS_SEEK, - CG_FS_GETFILELIST, - CG_LITERAL_ARGS, - CG_CM_BISPHERETRACE, - CG_CM_TRANSFORMEDBISPHERETRACE, - CG_GETDEMOSTATE, - CG_GETDEMOPOS, - CG_GETDEMONAME, - - CG_KEY_KEYNUMTOSTRINGBUF, - CG_KEY_GETBINDINGBUF, - CG_KEY_SETBINDING, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + CG_FS_SEEK, + CG_FS_GETFILELIST, + CG_LITERAL_ARGS, + CG_CM_BISPHERETRACE, + CG_CM_TRANSFORMEDBISPHERETRACE, + CG_GETDEMOSTATE, + CG_GETDEMOPOS, + CG_GETDEMONAME, + + CG_KEY_KEYNUMTOSTRINGBUF, + CG_KEY_GETBINDINGBUF, + CG_KEY_SETBINDING, #ifndef MODULE_INTERFACE_11 - CG_PARSE_ADD_GLOBAL_DEFINE, - CG_PARSE_LOAD_SOURCE, - CG_PARSE_FREE_SOURCE, - CG_PARSE_READ_TOKEN, - CG_PARSE_SOURCE_FILE_AND_LINE, + CG_PARSE_ADD_GLOBAL_DEFINE, + CG_PARSE_LOAD_SOURCE, + CG_PARSE_FREE_SOURCE, + CG_PARSE_READ_TOKEN, + CG_PARSE_SOURCE_FILE_AND_LINE, - CG_KEY_SETOVERSTRIKEMODE, - CG_KEY_GETOVERSTRIKEMODE, + CG_KEY_SETOVERSTRIKEMODE, + CG_KEY_GETOVERSTRIKEMODE, - CG_S_SOUNDDURATION, + CG_S_SOUNDDURATION, + CG_FIELD_COMPLETELIST, #endif - CG_MEMSET = 200, - CG_MEMCPY, - CG_STRNCPY, - CG_SIN, - CG_COS, - CG_ATAN2, - CG_SQRT, - CG_FLOOR, - CG_CEIL, - - CG_TESTPRINTINT, - CG_TESTPRINTFLOAT, - CG_ACOS + CG_MEMSET = 200, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS } cgameImport_t; - /* ================================================================== @@ -219,55 +215,62 @@ functions exported to the main executable ================================================================== */ -typedef enum -{ - CG_INIT, - // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) - // called when the level loads or when the renderer is restarted - // all media should be registered at this time - // cgame will display loading status by calling SCR_Update, which - // will call CG_DrawInformation during the loading process - // reliableCommandSequence will be 0 on fresh loads, but higher for - // demos, tourney restarts, or vid_restarts - - CG_SHUTDOWN, - // void (*CG_Shutdown)( void ); - // oportunity to flush and close any open files - - CG_CONSOLE_COMMAND, - // qboolean (*CG_ConsoleCommand)( void ); - // a console command has been issued locally that is not recognized by the - // main game system. - // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the - // command is not known to the game - - CG_DRAW_ACTIVE_FRAME, - // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); - // Generates and draws a game scene and status information at the given time. - // If demoPlayback is set, local movement prediction will not be enabled - - CG_CROSSHAIR_PLAYER, - // int (*CG_CrosshairPlayer)( void ); - - CG_LAST_ATTACKER, - // int (*CG_LastAttacker)( void ); - - CG_KEY_EVENT, - // void (*CG_KeyEvent)( int key, qboolean down ); - - CG_MOUSE_EVENT, - // void (*CG_MouseEvent)( int dx, int dy ); - CG_EVENT_HANDLING, - // void (*CG_EventHandling)(int type); - - CG_CONSOLE_TEXT, - // void (*CG_ConsoleText)( void ); - // pass text that has been printed to the console to cgame - // use Cmd_Argc() / Cmd_Argv() to read it - - CG_VOIP_STRING - // char *(*CG_VoIPString)( void ); - // returns a string of comma-delimited clientnums based on cl_voipSendTarget +typedef enum { + CG_INIT, + // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, + // void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, + // qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, + // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, + // int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, + // int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, + // void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, + // void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING, + // void (*CG_EventHandling)(int type); + + CG_CONSOLE_TEXT, + // void (*CG_ConsoleText)( void ); + // pass text that has been printed to the console to cgame + // use Cmd_Argc() / Cmd_Argv() to read it + + CG_VOIP_STRING, + // char *(*CG_VoIPString)( void ); + // returns a string of comma-delimited clientnums based on cl_voipSendTarget + + CG_CONSOLE_COMPLETARGUMENT + // qboolean (*CG_Console_CompleteArgument)( int argNum ) + // Requests CGAME to try to complete the command line + // argument. argNum indicates which argument we're completing. CGAME + // uses trap_Argv and trap_Argc to read the command line + // contents. Returns true if a completion function is found in + // CGAME, otherwise client tries another completion method. } cgameExport_t; -//---------------------------------------------- +#endif diff --git a/src/cgame/cg_rangemarker.c b/src/cgame/cg_rangemarker.c new file mode 100644 index 0000000..2810b90 --- /dev/null +++ b/src/cgame/cg_rangemarker.c @@ -0,0 +1,399 @@ +#include "cg_local.h" + +const vec3_t cg_shaderColors[ SHC_NUM_SHADER_COLORS ] = +{ + { 0.0f, 0.0f, 0.75f }, // dark blue + { 0.3f, 0.35f, 0.625f }, // light blue + { 0.0f, 0.625f, 0.563f }, // green-cyan + { 0.313f, 0.0f, 0.625f }, // violet + { 0.625f, 0.625f, 0.0f }, // yellow + { 0.875f, 0.313f, 0.0f }, // orange + { 0.375f, 0.625f, 0.375f }, // light green + { 0.0f, 0.438f, 0.0f }, // dark green + { 1.0f, 0.0f, 0.0f }, // red + { 0.625f, 0.375f, 0.4f }, // pink + { 0.313f, 0.313f, 0.313f } // grey +}; + +/* +================ +CG_RangeMarkerPreferences +================ +*/ +qboolean CG_GetRangeMarkerPreferences( qboolean *drawSurface, qboolean *drawIntersection, + qboolean *drawFrontline, float *surfaceOpacity, + float *lineOpacity, float *lineThickness ) +{ + *drawSurface = !!cg_rangeMarkerDrawSurface.integer; + *drawIntersection = !!cg_rangeMarkerDrawIntersection.integer; + *drawFrontline = !!cg_rangeMarkerDrawFrontline.integer; + *surfaceOpacity = cg_rangeMarkerSurfaceOpacity.value; + *lineOpacity = cg_rangeMarkerLineOpacity.value; + *lineThickness = cg_rangeMarkerLineThickness.value; + + if( ( *drawSurface && *surfaceOpacity > 0.0f ) || + ( ( *drawIntersection || *drawFrontline ) && *lineOpacity > 0.0f && + *lineThickness > 0.0f && cg_binaryShaderScreenScale.value > 0.0f ) ) + { + if( *surfaceOpacity > 1.0f ) + *surfaceOpacity = 1.0f; + if( *lineOpacity > 1.0f ) + *lineOpacity = 1.0f; + return qtrue; + } + + return qfalse; +} + +/* +================ +CG_UpdateBuildableRangeMarkerMask +================ +*/ +void CG_UpdateBuildableRangeMarkerMask( void ) +{ + static int mc = 0; + + if( cg_rangeMarkerBuildableTypes.modificationCount != mc ) + { + int brmMask; + char buffer[ MAX_CVAR_VALUE_STRING ]; + char *p, *q; + buildable_t buildable; + + brmMask = 0; + + if( !cg_rangeMarkerBuildableTypes.string[ 0 ] ) + goto empty; + + Q_strncpyz( buffer, cg_rangeMarkerBuildableTypes.string, sizeof( buffer ) ); + p = &buffer[ 0 ]; + + for(;;) + { + q = strchr( p, ',' ); + if( q ) + *q = '\0'; + + while( *p == ' ' ) + ++p; + + buildable = BG_BuildableByName( p )->number; + + if( buildable != BA_NONE ) + { + brmMask |= 1 << buildable; + } + else if( !Q_stricmp( p, "all" ) ) + { + brmMask |= ( 1 << BA_A_OVERMIND ) | ( 1 << BA_A_SPAWN ) | + ( 1 << BA_A_ACIDTUBE ) | ( 1 << BA_A_TRAPPER ) | ( 1 << BA_A_HIVE ) | + ( 1 << BA_H_REACTOR ) | ( 1 << BA_H_REPEATER ) | ( 1 << BA_H_DCC ) | + ( 1 << BA_H_MGTURRET ) | ( 1 << BA_H_TESLAGEN ); + } + else + { + char *pp; + int only; + + if( !Q_stricmpn( p, "alien", 5 ) ) + { + pp = p + 5; + only = ( 1 << BA_A_OVERMIND ) | ( 1 << BA_A_SPAWN ) | + ( 1 << BA_A_ACIDTUBE ) | ( 1 << BA_A_TRAPPER ) | ( 1 << BA_A_HIVE ); + } + else if( !Q_stricmpn( p, "human", 5 ) ) + { + pp = p + 5; + only = ( 1 << BA_H_REACTOR ) | ( 1 << BA_H_REPEATER ) | ( 1 << BA_H_DCC ) | + ( 1 << BA_H_MGTURRET ) | ( 1 << BA_H_TESLAGEN ); + } + else + { + pp = p; + only = ~0; + } + + if( pp != p && !*pp ) + { + brmMask |= only; + } + else if( !Q_stricmp( pp, "support" ) ) + { + brmMask |= only & ( ( 1 << BA_A_OVERMIND ) | ( 1 << BA_A_SPAWN ) | + ( 1 << BA_H_REACTOR ) | ( 1 << BA_H_REPEATER ) | ( 1 << BA_H_DCC ) ); + } + else if( !Q_stricmp( pp, "offensive" ) ) + { + brmMask |= only & ( ( 1 << BA_A_ACIDTUBE ) | ( 1 << BA_A_TRAPPER ) | ( 1 << BA_A_HIVE ) | + ( 1 << BA_H_MGTURRET ) | ( 1 << BA_H_TESLAGEN ) ); + } + else + Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable or group: %s\n", p ); + } + + if( q ) + p = q + 1; + else + break; + } + + empty: + trap_Cvar_Set( "cg_buildableRangeMarkerMask", va( "%i", brmMask ) ); + + mc = cg_rangeMarkerBuildableTypes.modificationCount; + } +} + +// cg_drawtools.c +// +/* +================ +CG_DrawSphere +================ +*/ +void CG_DrawSphere( const vec3_t center, float radius, int customShader, const float *shaderRGBA ) +{ + refEntity_t re; + memset( &re, 0, sizeof( re ) ); + + re.reType = RT_MODEL; + re.hModel = cgs.media.sphereModel; + re.customShader = customShader; + re.renderfx = RF_NOSHADOW; + if( shaderRGBA != NULL ) + { + int i; + for( i = 0; i < 4; ++i ) + re.shaderRGBA[ i ] = 255 * shaderRGBA[ i ]; + } + + VectorCopy( center, re.origin ); + + radius *= 0.01f; + VectorSet( re.axis[ 0 ], radius, 0, 0 ); + VectorSet( re.axis[ 1 ], 0, radius, 0 ); + VectorSet( re.axis[ 2 ], 0, 0, radius ); + re.nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( &re ); +} + +/* +================ +CG_DrawSphericalCone +================ +*/ +void CG_DrawSphericalCone( const vec3_t tip, const vec3_t rotation, float radius, + qboolean a240, int customShader, const float *shaderRGBA ) +{ + refEntity_t re; + memset( &re, 0, sizeof( re ) ); + + re.reType = RT_MODEL; + re.hModel = a240 ? cgs.media.sphericalCone240Model : cgs.media.sphericalCone64Model; + re.customShader = customShader; + re.renderfx = RF_NOSHADOW; + if( shaderRGBA != NULL ) + { + int i; + for( i = 0; i < 4; ++i ) + re.shaderRGBA[ i ] = 255 * shaderRGBA[ i ]; + } + + VectorCopy( tip, re.origin ); + + radius *= 0.01f; + AnglesToAxis( rotation, re.axis ); + VectorScale( re.axis[ 0 ], radius, re.axis[ 0 ] ); + VectorScale( re.axis[ 1 ], radius, re.axis[ 1 ] ); + VectorScale( re.axis[ 2 ], radius, re.axis[ 2 ] ); + re.nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( &re ); +} + + +/* +================ +CG_DrawRangeMarker +================ +*/ +void CG_DrawRangeMarker( rangeMarkerType_t rmType, const vec3_t origin, const float *angles, float range, + qboolean drawSurface, qboolean drawIntersection, qboolean drawFrontline, + const vec3_t rgb, float surfaceOpacity, float lineOpacity, float lineThickness ) +{ + if( drawSurface ) + { + qhandle_t pcsh; + vec4_t rgba; + + pcsh = cgs.media.plainColorShader; + VectorCopy( rgb, rgba ); + rgba[ 3 ] = surfaceOpacity; + + switch( rmType ) + { + case RMT_SPHERE: + CG_DrawSphere( origin, range, pcsh, rgba ); + break; + case RMT_SPHERICAL_CONE_64: + CG_DrawSphericalCone( origin, angles, range, qfalse, pcsh, rgba ); + break; + case RMT_SPHERICAL_CONE_240: + CG_DrawSphericalCone( origin, angles, range, qtrue, pcsh, rgba ); + break; + } + } + + if( drawIntersection || drawFrontline ) + { + const cgMediaBinaryShader_t *mbsh; + cgBinaryShaderSetting_t *bshs; + int i; + + if( cg.numBinaryShadersUsed >= NUM_BINARY_SHADERS ) + return; + mbsh = &cgs.media.binaryShaders[ cg.numBinaryShadersUsed ]; + + if( rmType == RMT_SPHERE ) + { + if( range > lineThickness / 2 ) + { + if( drawIntersection ) + CG_DrawSphere( origin, range - lineThickness / 2, mbsh->b1, NULL ); + CG_DrawSphere( origin, range - lineThickness / 2, mbsh->f2, NULL ); + } + + if( drawIntersection ) + CG_DrawSphere( origin, range + lineThickness / 2, mbsh->b2, NULL ); + CG_DrawSphere( origin, range + lineThickness / 2, mbsh->f1, NULL ); + } + else if( rmType == RMT_SPHERICAL_CONE_64 || rmType == RMT_SPHERICAL_CONE_240 ) + { + qboolean a240; + float f, r; + vec3_t forward, tip; + + a240 = ( rmType == RMT_SPHERICAL_CONE_240 ); + f = lineThickness * ( a240 ? 0.26f : 0.8f ); + r = f + lineThickness * ( a240 ? 0.23f : 0.43f ); + AngleVectors( angles, forward, NULL, NULL ); + + if( range > r ) + { + VectorMA( origin, f, forward, tip ); + if( drawIntersection ) + CG_DrawSphericalCone( tip, angles, range - r, a240, mbsh->b1, NULL ); + CG_DrawSphericalCone( tip, angles, range - r, a240, mbsh->f2, NULL ); + } + + VectorMA( origin, -f, forward, tip ); + if( drawIntersection ) + CG_DrawSphericalCone( tip, angles, range + r, a240, mbsh->b2, NULL ); + CG_DrawSphericalCone( tip, angles, range + r, a240, mbsh->f1, NULL ); + } + + bshs = &cg.binaryShaderSettings[ cg.numBinaryShadersUsed ]; + + for( i = 0; i < 3; ++i ) + bshs->color[ i ] = 255 * lineOpacity * rgb[ i ]; + bshs->drawIntersection = drawIntersection; + bshs->drawFrontline = drawFrontline; + + ++cg.numBinaryShadersUsed; + } +} + +// cg_buildable.c + +/* +================ +CG_BuildableRangeMarkerProperties +================ +*/ +qboolean CG_GetBuildableRangeMarkerProperties( buildable_t bType, rangeMarkerType_t *rmType, float *range, vec3_t rgb ) +{ + shaderColorEnum_t shc; + + switch( bType ) + { + case BA_A_SPAWN: *range = CREEP_BASESIZE; shc = SHC_LIGHT_GREEN; break; + case BA_A_OVERMIND: *range = CREEP_BASESIZE; shc = SHC_DARK_GREEN; break; + case BA_A_ACIDTUBE: *range = ACIDTUBE_RANGE; shc = SHC_RED; break; + case BA_A_TRAPPER: *range = TRAPPER_RANGE; shc = SHC_PINK; break; + case BA_A_HIVE: *range = HIVE_SENSE_RANGE; shc = SHC_YELLOW; break; + case BA_H_MGTURRET: *range = MGTURRET_RANGE; shc = SHC_ORANGE; break; + case BA_H_TESLAGEN: *range = TESLAGEN_RANGE; shc = SHC_VIOLET; break; + case BA_H_DCC: *range = DC_RANGE; shc = SHC_GREEN_CYAN; break; + case BA_H_REACTOR: *range = REACTOR_BASESIZE; shc = SHC_DARK_BLUE; break; + case BA_H_REPEATER: *range = REPEATER_BASESIZE; shc = SHC_LIGHT_BLUE; break; + default: return qfalse; + } + + if( bType == BA_A_TRAPPER ) + *rmType = RMT_SPHERICAL_CONE_64; + else if( bType == BA_H_MGTURRET ) + *rmType = RMT_SPHERICAL_CONE_240; + else + *rmType = RMT_SPHERE; + + VectorCopy( cg_shaderColors[ shc ], rgb ); + + return qtrue; +} + +/* +================ +CG_GhostBuildableRangeMarker +================ +*/ +void CG_GhostBuildableRangeMarker( buildable_t buildable, const vec3_t origin, const vec3_t normal ) +{ + qboolean drawS, drawI, drawF; + float so, lo, th; + rangeMarkerType_t rmType; + float range; + vec3_t rgb; + + if( CG_GetRangeMarkerPreferences( &drawS, &drawI, &drawF, &so, &lo, &th ) && + CG_GetBuildableRangeMarkerProperties( buildable, &rmType, &range, rgb ) ) + { + vec3_t localOrigin, angles; + + if( buildable == BA_A_HIVE || buildable == BA_H_TESLAGEN ) + VectorMA( origin, BG_BuildableConfig( buildable )->maxs[ 2 ], normal, localOrigin ); + else + VectorCopy( origin, localOrigin ); + + if( rmType != RMT_SPHERE ) + vectoangles( normal, angles ); + + CG_DrawRangeMarker( rmType, localOrigin, ( rmType != RMT_SPHERE ? angles : NULL ), + range, drawS, drawI, drawF, rgb, so, lo, th ); + } +} + +// cg_ents.c + +/* +================ +CG_RangeMarker +================ +*/ +void CG_RangeMarker( centity_t *cent ) +{ + qboolean drawS, drawI, drawF; + float so, lo, th; + rangeMarkerType_t rmType; + float range; + vec3_t rgb; + + if( CG_GetRangeMarkerPreferences( &drawS, &drawI, &drawF, &so, &lo, &th ) && + CG_GetBuildableRangeMarkerProperties( cent->currentState.modelindex, &rmType, &range, rgb ) ) + { + CG_DrawRangeMarker( rmType, cent->lerpOrigin, ( rmType > 0 ? cent->lerpAngles : NULL ), + range, drawS, drawI, drawF, rgb, so, lo, th ); + } +} + diff --git a/src/cgame/cg_scanner.c b/src/cgame/cg_scanner.c index 033960e..a7df030 100644 --- a/src/cgame/cg_scanner.c +++ b/src/cgame/cg_scanner.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,15 +16,14 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ - #include "cg_local.h" -static entityPos_t entityPositions; +static entityPos_t entityPositions; #define HUMAN_SCANNER_UPDATE_PERIOD 700 @@ -39,7 +39,7 @@ void CG_UpdateEntityPositions( void ) centity_t *cent = NULL; int i; - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) { if( entityPositions.lastUpdateTime + HUMAN_SCANNER_UPDATE_PERIOD > cg.time ) return; @@ -58,10 +58,11 @@ void CG_UpdateEntityPositions( void ) { cent = &cg_entities[ cg.snap->entities[ i ].number ]; - if( cent->currentState.eType == ET_BUILDABLE ) + if( cent->currentState.eType == ET_BUILDABLE && + !( cent->currentState.eFlags & EF_DEAD ) ) { - //TA: add to list of item positions (for creep) - if( cent->currentState.modelindex2 == BIT_ALIENS ) + // add to list of item positions (for creep) + if( cent->currentState.modelindex2 == TEAM_ALIENS ) { VectorCopy( cent->lerpOrigin, entityPositions.alienBuildablePos[ entityPositions.numAlienBuildables ] ); @@ -71,7 +72,7 @@ void CG_UpdateEntityPositions( void ) if( entityPositions.numAlienBuildables < MAX_GENTITIES ) entityPositions.numAlienBuildables++; } - else if( cent->currentState.modelindex2 == BIT_HUMANS ) + else if( cent->currentState.modelindex2 == TEAM_HUMANS ) { VectorCopy( cent->lerpOrigin, entityPositions.humanBuildablePos[ entityPositions.numHumanBuildables ] ); @@ -84,7 +85,7 @@ void CG_UpdateEntityPositions( void ) { int team = cent->currentState.misc & 0x00FF; - if( team == PTE_ALIENS ) + if( team == TEAM_ALIENS ) { VectorCopy( cent->lerpOrigin, entityPositions.alienClientPos[ entityPositions.numAlienClients ] ); @@ -92,7 +93,7 @@ void CG_UpdateEntityPositions( void ) if( entityPositions.numAlienClients < MAX_CLIENTS ) entityPositions.numAlienClients++; } - else if( team == PTE_HUMANS ) + else if( team == TEAM_HUMANS ) { VectorCopy( cent->lerpOrigin, entityPositions.humanClientPos[ entityPositions.numHumanClients ] ); @@ -104,8 +105,8 @@ void CG_UpdateEntityPositions( void ) } } -#define STALKWIDTH 2.0f -#define BLIPX 16.0f +#define STALKWIDTH (2.0f * cgDC.aspectScale) +#define BLIPX (16.0f * cgDC.aspectScale) #define BLIPY 8.0f #define FAR_ALPHA 0.8f #define NEAR_ALPHA 1.2f @@ -162,7 +163,7 @@ static void CG_DrawBlips( rectDef_t *rect, vec3_t origin, vec4_t colour ) trap_R_SetColor( NULL ); } -#define BLIPX2 24.0f +#define BLIPX2 (24.0f * cgDC.aspectScale) #define BLIPY2 24.0f /* @@ -183,15 +184,7 @@ static void CG_DrawDir( rectDef_t *rect, vec3_t origin, vec4_t colour ) float angle; playerState_t *ps = &cg.snap->ps; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( ps, normal ); AngleVectors( entityPositions.vangles, view, NULL, NULL ); @@ -363,3 +356,125 @@ void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ) CG_DrawBlips( rect, relOrigin, aIabove ); } } + +void THZ_DrawScanner( rectDef_t *rect ) +{ + vec4_t colorB = { 0.0f, 0.0f, 0.0f, 0.5f }; + vec4_t color = { 1.0f, 1.0f, 1.0f, 0.2f }; + + vec4_t aliencolor = { 1.0f, 0.0f, 0.0f, 0.8f }; + vec4_t humancolor = { 0.0f, 0.0f, 1.0f, 0.8f }; + vec4_t buildcolor = { 0.0f, 1.0f, 1.0f, 0.8f }; + vec4_t buildcolor2 = { 1.0f, 1.0f, 0.0f, 0.8f }; + + vec3_t drawOrigin = { 0.0f, 0.0f, 0.0f }; + vec3_t origin = { 0.0f, 0.0f, 0.0f }; + vec3_t relOrigin = { 0.0f, 0.0f, 0.0f }; + + static vec3_t up = { 0.0f, 0.0f, 1.0f }; + + int i; + + if( !thz_radar.integer ) + return; + + //CG_FillRect( rect->x, rect->y, rect->w, rect->h, colorB ); + + // Draw cross + CG_FillRect( rect->x + (rect->w/2), + rect->y, + 1, + rect->h, + color ); + CG_FillRect( rect->x, + rect->y+(rect->h/2), + rect->w, + 1, + color ); + + // update the player positions + CG_UpdateEntityPositions( ); + + // blips + VectorCopy( entityPositions.origin, origin ); + + // human buildables + for( i = 0; i < entityPositions.numHumanBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] - 5, + rect->y + (rect->h / 2) + drawOrigin[1] - 5, + 10, 10, buildcolor ); + } + } + + // humans + for( i = 0; i < entityPositions.numHumanClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] -3, + rect->y + (rect->h / 2) + drawOrigin[1] -3, + 6, 6, humancolor ); + } + } + + // alien structures + for( i = 0; i < entityPositions.numAlienBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] - 5, + rect->y + (rect->h / 2) + drawOrigin[1] - 5, + 10, 10, buildcolor2 ); + } + } + + // aliens + for( i = 0; i < entityPositions.numAlienClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < thz_radarrange.integer ) + { + RotatePointAroundVector( drawOrigin, up, relOrigin, -entityPositions.vangles[ 1 ] - 90 ); + + drawOrigin[ 0 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + drawOrigin[ 1 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->h); + drawOrigin[ 2 ] /= ((float)(1.25f * (float)thz_radarrange.integer) / (float)rect->w); + + CG_FillRect( rect->x + (rect->w / 2) + -drawOrigin[0] -3, + rect->y + (rect->h / 2) + drawOrigin[1] -3, + 6, 6, aliencolor ); + } + } +} diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c index 7fb3e06..4de7586 100644 --- a/src/cgame/cg_servercmds.c +++ b/src/cgame/cg_servercmds.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -25,7 +26,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // these are processed at snapshot transition time, so there will definately // be a valid snapshot this frame - #include "cg_local.h" /* @@ -38,13 +38,13 @@ static void CG_ParseScores( void ) { int i; - cg.numScores = atoi( CG_Argv( 1 ) ); + cg.numScores = ( trap_Argc( ) - 3 ) / 6; if( cg.numScores > MAX_CLIENTS ) cg.numScores = MAX_CLIENTS; - cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) ); - cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) ); + cg.teamScores[ 0 ] = atoi( CG_Argv( 1 ) ); + cg.teamScores[ 1 ] = atoi( CG_Argv( 2 ) ); memset( cg.scores, 0, sizeof( cg.scores ) ); @@ -54,18 +54,17 @@ static void CG_ParseScores( void ) for( i = 0; i < cg.numScores; i++ ) { // - cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 4 ) ); - cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 5 ) ); - cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 6 ) ); - cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 7 ) ); - cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 8 ) ); - cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 9 ) ); + cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 3 ) ); + cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 4 ) ); + cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 5 ) ); + cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 6 ) ); + cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 7 ) ); + cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 8 ) ); if( cg.scores[ i ].client < 0 || cg.scores[ i ].client >= MAX_CLIENTS ) cg.scores[ i ].client = 0; cgs.clientinfo[ cg.scores[ i ].client ].score = cg.scores[ i ].score; - cgs.clientinfo[ cg.scores[ i ].client ].powerups = 0; cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team; } @@ -80,22 +79,33 @@ CG_ParseTeamInfo static void CG_ParseTeamInfo( void ) { int i; + int count; int client; - numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + count = trap_Argc( ); - for( i = 0; i < numSortedTeamPlayers; i++ ) + for( i = 1; i < count; i++ ) // i is also incremented when writing into cgs.clientinfo { - client = atoi( CG_Argv( i * 6 + 2 ) ); + client = atoi( CG_Argv( i ) ); + + // wrong team? drop the remaining info + if( cgs.clientinfo[ client ].team != cg.snap->ps.stats[ STAT_TEAM ] ) + return; - sortedTeamPlayers[ i ] = client; + if( client < 0 || client >= MAX_CLIENTS ) + { + CG_Printf( "[skipnotify]CG_ParseTeamInfo: bad client number: %d\n", client ); + return; + } - cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); - cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); - cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); - cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); - cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); + cgs.clientinfo[ client ].location = atoi( CG_Argv( ++i ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( ++i ) ); + cgs.clientinfo[ client ].curWeaponClass = atoi( CG_Argv( ++i ) ); + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + cgs.clientinfo[ client ].upgrade = atoi( CG_Argv( ++i ) ); } + + cgs.teaminfoReceievedTime = cg.time; } @@ -133,13 +143,7 @@ static void CG_ParseWarmup( void ) info = CG_ConfigString( CS_WARMUP ); warmup = atoi( info ); - cg.warmupCount = -1; - - if( warmup == 0 && cg.warmup ) - { - } - - cg.warmup = warmup; + cg.warmupTime = warmup; } /* @@ -151,19 +155,28 @@ Called on load to set the initial values from configure strings */ void CG_SetConfigValues( void ) { - sscanf( CG_ConfigString( CS_BUILDPOINTS ), - "%d %d %d %d %d", &cgs.alienBuildPoints, - &cgs.alienBuildPointsTotal, - &cgs.humanBuildPoints, - &cgs.humanBuildPointsTotal, - &cgs.humanBuildPointsPowered ); + const char *alienStages = CG_ConfigString( CS_ALIEN_STAGES ); + const char *humanStages = CG_ConfigString( CS_HUMAN_STAGES ); - sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage, - &cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold ); - sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns ); + if( alienStages[0] ) + { + sscanf( alienStages, "%d %d %d", &cgs.alienStage, &cgs.alienCredits, + &cgs.alienNextStageThreshold ); + } + else + cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0; + + + if( humanStages[0] ) + { + sscanf( humanStages, "%d %d %d", &cgs.humanStage, &cgs.humanCredits, + &cgs.humanNextStageThreshold ); + } + else + cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0; cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); - cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); + cg.warmupTime = atoi( CG_ConfigString( CS_WARMUP ) ); } @@ -224,7 +237,7 @@ CG_AnnounceAlienStageTransistion */ static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to ) { - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS ) return; trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER ); @@ -238,7 +251,7 @@ CG_AnnounceHumanStageTransistion */ static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to ) { - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER ); @@ -272,91 +285,72 @@ static void CG_ConfigStringModified( void ) CG_ParseServerinfo( ); else if( num == CS_WARMUP ) CG_ParseWarmup( ); - else if( num == CS_BUILDPOINTS ) - sscanf( str, "%d %d %d %d %d", &cgs.alienBuildPoints, - &cgs.alienBuildPointsTotal, - &cgs.humanBuildPoints, - &cgs.humanBuildPointsTotal, - &cgs.humanBuildPointsPowered ); - else if( num == CS_STAGES ) + else if( num == CS_ALIEN_STAGES ) { stage_t oldAlienStage = cgs.alienStage; - stage_t oldHumanStage = cgs.humanStage; - - sscanf( str, "%d %d %d %d %d %d", - &cgs.alienStage, &cgs.humanStage, - &cgs.alienKills, &cgs.humanKills, - &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold ); - - if( cgs.alienStage != oldAlienStage ) - CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage ); - if( cgs.humanStage != oldHumanStage ) - CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage ); - } - else if( num == CS_SPAWNS ) - sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns ); - else if( num == CS_LEVEL_START_TIME ) - cgs.levelStartTime = atoi( str ); - else if( num == CS_VOTE_TIME ) - { - cgs.voteTime = atoi( str ); - cgs.voteModified = qtrue; + if( str[0] ) + { + sscanf( str, "%d %d %d", &cgs.alienStage, &cgs.alienCredits, + &cgs.alienNextStageThreshold ); - if( cgs.voteTime ) - trap_Cvar_Set( "ui_voteActive", "1" ); + if( cgs.alienStage != oldAlienStage ) + CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage ); + } else - trap_Cvar_Set( "ui_voteActive", "0" ); - } - else if( num == CS_VOTE_YES ) - { - cgs.voteYes = atoi( str ); - cgs.voteModified = qtrue; - } - else if( num == CS_VOTE_NO ) - { - cgs.voteNo = atoi( str ); - cgs.voteModified = qtrue; + { + cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0; + } } - else if( num == CS_VOTE_STRING ) - Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); - else if( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 ) + else if( num == CS_HUMAN_STAGES ) { - int cs_offset = num - CS_TEAMVOTE_TIME; - - cgs.teamVoteTime[ cs_offset ] = atoi( str ); - cgs.teamVoteModified[ cs_offset ] = qtrue; + stage_t oldHumanStage = cgs.humanStage; - if( cs_offset == 0 ) + if( str[0] ) { - if( cgs.teamVoteTime[ cs_offset ] ) - trap_Cvar_Set( "ui_humanTeamVoteActive", "1" ); - else - trap_Cvar_Set( "ui_humanTeamVoteActive", "0" ); + sscanf( str, "%d %d %d", &cgs.humanStage, &cgs.humanCredits, + &cgs.humanNextStageThreshold ); + + if( cgs.humanStage != oldHumanStage ) + CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage ); } - else if( cs_offset == 1 ) + else { - if( cgs.teamVoteTime[ cs_offset ] ) - trap_Cvar_Set( "ui_alienTeamVoteActive", "1" ); - else - trap_Cvar_Set( "ui_alienTeamVoteActive", "0" ); + cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0; } } - else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 ) + else if( num == CS_LEVEL_START_TIME ) + cgs.levelStartTime = atoi( str ); + else if( num >= CS_VOTE_TIME && num < CS_VOTE_TIME + NUM_TEAMS ) { - cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str ); - cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue; + cgs.voteTime[ num - CS_VOTE_TIME ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_TIME ] = qtrue; + + if( num - CS_VOTE_TIME == TEAM_NONE ) + trap_Cvar_Set( "ui_voteActive", cgs.voteTime[ TEAM_NONE ] ? "1" : "0" ); + else if( num - CS_VOTE_TIME == TEAM_ALIENS ) + trap_Cvar_Set( "ui_alienTeamVoteActive", + cgs.voteTime[ TEAM_ALIENS ] ? "1" : "0" ); + else if( num - CS_VOTE_TIME == TEAM_HUMANS ) + trap_Cvar_Set( "ui_humanTeamVoteActive", + cgs.voteTime[ TEAM_HUMANS ] ? "1" : "0" ); } - else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 ) + else if( num >= CS_VOTE_YES && num < CS_VOTE_YES + NUM_TEAMS ) { - cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str ); - cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue; + cgs.voteYes[ num - CS_VOTE_YES ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_YES ] = qtrue; } - else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 ) + else if( num >= CS_VOTE_NO && num < CS_VOTE_NO + NUM_TEAMS ) { - Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str, - sizeof( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ] ) ); + cgs.voteNo[ num - CS_VOTE_NO ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_NO ] = qtrue; } + else if( num >= CS_VOTE_STRING && num < CS_VOTE_STRING + NUM_TEAMS ) + Q_strncpyz( cgs.voteString[ num - CS_VOTE_STRING ], str, + sizeof( cgs.voteString[ num - CS_VOTE_STRING ] ) ); + else if( num >= CS_VOTE_CALLER && num < CS_VOTE_CALLER + NUM_TEAMS ) + Q_strncpyz( cgs.voteCaller[ num - CS_VOTE_CALLER ], str, + sizeof( cgs.voteCaller[ num - CS_VOTE_CALLER ] ) ); else if( num == CS_INTERMISSION ) cg.intermissionStarted = atoi( str ); else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) @@ -413,7 +407,7 @@ static void CG_MapRestart( void ) cg.intermissionStarted = qfalse; - cgs.voteTime = 0; + cgs.voteTime[ TEAM_NONE ] = 0; cg.mapRestart = qtrue; @@ -423,574 +417,903 @@ static void CG_MapRestart( void ) // we really should clear more parts of cg here and stop sounds - // play the "fight" sound if this is a restart without warmup - if( cg.warmup == 0 ) - CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 ); - trap_Cvar_Set( "cg_thirdPerson", "0" ); } -/* -================= -CG_RemoveChatEscapeChar -================= -*/ -static void CG_RemoveChatEscapeChar( char *text ) -{ - int i, l; - - l = 0; - for( i = 0; text[ i ]; i++ ) - { - if( text[ i ] == '\x19' ) - continue; - - text[ l++ ] = text[ i ]; - } - - text[ l ] = '\0'; -} - -/* -=============== -CG_SetUIVars - -Set some cvars used by the UI -=============== -*/ -static void CG_SetUIVars( void ) -{ - int i; - char carriageCvar[ MAX_TOKEN_CHARS ]; - - *carriageCvar = 0; - - //determine what the player is carrying - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) && - BG_FindPurchasableForWeapon( i ) ) - strcat( carriageCvar, va( "W%d ", i ) ); - } - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && - BG_FindPurchasableForUpgrade( i ) ) - strcat( carriageCvar, va( "U%d ", i ) ); - } - strcat( carriageCvar, "$" ); - - trap_Cvar_Set( "ui_carriage", carriageCvar ); - - trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); -} - - /* ============== CG_Menu ============== */ -void CG_Menu( int menu ) +void CG_Menu( int menu, int arg ) { - const char *cmd = NULL; // command to send - const char *longMsg = NULL; // command parameter - const char *shortMsg = NULL; // non-modal version of message - CG_SetUIVars( ); + const char *cmd; // command to send + const char *longMsg = NULL; // command parameter + const char *shortMsg = NULL; // non-modal version of message + const char *dialog; + dialogType_t type = 0; // controls which cg_disable var will switch it off + + switch( cg.snap->ps.stats[ STAT_TEAM ] ) + { + case TEAM_ALIENS: + dialog = "menu tremulous_alien_dialog\n"; + break; + case TEAM_HUMANS: + dialog = "menu tremulous_human_dialog\n"; + break; + default: + dialog = "menu tremulous_default_dialog\n"; + } + cmd = dialog; - // string literals have static storage duration, this is safe, - // cleaner and much more readable. switch( menu ) { case MN_TEAM: cmd = "menu tremulous_teamselect\n"; + type = DT_INTERACTIVE; break; case MN_A_CLASS: cmd = "menu tremulous_alienclass\n"; + type = DT_INTERACTIVE; break; case MN_H_SPAWN: cmd = "menu tremulous_humanitem\n"; + type = DT_INTERACTIVE; break; case MN_A_BUILD: cmd = "menu tremulous_alienbuild\n"; + type = DT_INTERACTIVE; break; case MN_H_BUILD: cmd = "menu tremulous_humanbuild\n"; + type = DT_INTERACTIVE; break; case MN_H_ARMOURY: cmd = "menu tremulous_humanarmoury\n"; + type = DT_INTERACTIVE; + break; + + case MN_H_UNKNOWNITEM: + shortMsg = "Unknown item"; + type = DT_ARMOURYEVOLVE; break; case MN_A_TEAMFULL: longMsg = "The alien team has too many players. Please wait until slots " "become available or join the human team."; - shortMsg = "The alien team has too many players\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "The alien team has too many players"; + type = DT_COMMAND; break; case MN_H_TEAMFULL: longMsg = "The human team has too many players. Please wait until slots " "become available or join the alien team."; - shortMsg = "The human team has too many players\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "The human team has too many players"; + type = DT_COMMAND; break; - case MN_A_TEAMCHANGEBUILDTIMER: - longMsg = "You cannot leave the Alien team until your build timer " - "has expired."; - shortMsg = "You cannot change teams until your build timer expires.\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_TEAMLOCKED: + longMsg = "The alien team is locked. You cannot join the aliens " + "at this time."; + shortMsg = "The alien team is locked"; + type = DT_COMMAND; break; - case MN_H_TEAMCHANGEBUILDTIMER: - longMsg = "You cannot leave the Human team until your build timer " - "has expired."; - shortMsg = "You cannot change teams until your build timer expires.\n"; - cmd = "menu tremulous_human_dialog\n"; + case MN_H_TEAMLOCKED: + longMsg = "The human team is locked. You cannot join the humans " + "at this time."; + shortMsg = "The human team is locked"; + type = DT_COMMAND; + break; + + case MN_PLAYERLIMIT: + longMsg = "The maximum number of playing clients has been reached. " + "Please wait until slots become available."; + shortMsg = "No free player slots"; + type = DT_COMMAND; break; //=============================== - case MN_H_NOROOM: - longMsg = "There is no room to build here. Move until the buildable turns " - "translucent green indicating a valid build location."; - shortMsg = "There is no room to build here\n"; - cmd = "menu tremulous_human_dialog\n"; + // Since cheating commands have no default binds, they will often be done + // via console. In light of this, perhaps opening a menu is + // counterintuitive + case MN_CMD_CHEAT: + //longMsg = "This action is considered cheating. It can only be used " + // "in cheat mode, which is not enabled on this server."; + shortMsg = "Cheats are not enabled on this server"; + type = DT_COMMAND; break; - case MN_H_NOPOWER: - longMsg = "There is no power remaining. Free up power by destroying " - "existing buildable objects."; - shortMsg = "There is no power remaining\n"; - cmd = "menu tremulous_human_dialog\n"; + case MN_CMD_CHEAT_TEAM: + shortMsg = "Cheats are not enabled on this server, so " + "you may not use this command while on a team"; + type = DT_COMMAND; break; - case MN_H_NOTPOWERED: - longMsg = "This buildable is not powered. Build a Reactor and/or Repeater " - "in order to power it."; - shortMsg = "This buildable is not powered\n"; - cmd = "menu tremulous_human_dialog\n"; + case MN_CMD_TEAM: + //longMsg = "You must be on a team to perform this action. Join the alien" + // "or human team and try again."; + shortMsg = "Join a team first"; + type = DT_COMMAND; break; - case MN_H_NORMAL: + case MN_CMD_SPEC: + //longMsg = "You may not perform this action while on a team. Become a " + // "spectator before trying again."; + shortMsg = "You can only use this command when spectating"; + type = DT_COMMAND; + break; + + case MN_CMD_ALIEN: + //longMsg = "You must be on the alien team to perform this action."; + shortMsg = "Must be alien to use this command"; + type = DT_COMMAND; + break; + + case MN_CMD_HUMAN: + //longMsg = "You must be on the human team to perform this action."; + shortMsg = "Must be human to use this command"; + type = DT_COMMAND; + break; + + case MN_CMD_ALIVE: + //longMsg = "You must be alive to perform this action."; + shortMsg = "Must be alive to use this command"; + type = DT_COMMAND; + break; + + + //=============================== + + case MN_B_NOROOM: + longMsg = "There is no room to build here. Move until the structure turns " + "translucent green, indicating a valid build location."; + shortMsg = "There is no room to build here"; + type = DT_BUILD; + break; + + case MN_B_NORMAL: longMsg = "Cannot build on this surface. The surface is too steep or " - "unsuitable to build on. Please choose another site for this " - "structure."; - shortMsg = "Cannot build on this surface\n"; - cmd = "menu tremulous_human_dialog\n"; + "unsuitable for building. Please choose another site for this " + "structure."; + shortMsg = "Cannot build on this surface"; + type = DT_BUILD; + break; + + case MN_B_CANNOT: + longMsg = NULL; + shortMsg = "You cannot build that structure"; + type = DT_BUILD; + break; + + // FIXME: MN_H_ and MN_A_? + case MN_B_LASTSPAWN: + longMsg = "This action would remove your team's last spawn point, " + "which often quickly results in a loss. Try building more " + "spawns."; + shortMsg = "You may not deconstruct the last spawn"; + break; + + case MN_B_SUDDENDEATH: + longMsg = "Neither team has prevailed after a certain time and the " + "game has entered Sudden Death. During Sudden Death " + "building is not allowed."; + shortMsg = "Cannot build during Sudden Death"; + type = DT_BUILD; + break; + + case MN_B_REVOKED: + longMsg = "Your teammates have lost faith in your ability to build " + "for the team. You will not be allowed to build until your " + "team votes to reinstate your building rights."; + shortMsg = "Your building rights have been revoked"; + type = DT_BUILD; break; - case MN_H_REACTOR: - longMsg = "There can only be one Reactor. Destroy the existing one if you " + case MN_B_SURRENDER: + longMsg = "Your team has decided to admit defeat and concede the game:" + "traitors and cowards are not allowed to build."; + // too harsh? + shortMsg = "Building is denied to traitorous cowards"; + break; + + //=============================== + + case MN_H_NOBP: + if( cgs.markDeconstruct ) + longMsg = "There is no power remaining. Free up power by marking " + "existing buildable objects."; + else + longMsg = "There is no power remaining. Free up power by deconstructing " + "existing buildable objects."; + shortMsg = "There is no power remaining"; + type = DT_BUILD; + break; + + case MN_H_NOTPOWERED: + longMsg = "This buildable is not powered. Build a Reactor and/or Repeater " + "in order to power it."; + shortMsg = "This buildable is not powered"; + type = DT_BUILD; + break; + + case MN_H_ONEREACTOR: + longMsg = "There can only be one Reactor. Deconstruct the existing one if you " "wish to move it."; - shortMsg = "There can only be one Reactor\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "There can only be one Reactor"; + type = DT_BUILD; break; - case MN_H_REPEATER: + case MN_H_NOPOWERHERE: longMsg = "There is no power here. If available, a Repeater may be used to " "transmit power to this location."; - shortMsg = "There is no power here\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "There is no power here"; + type = DT_BUILD; break; case MN_H_NODCC: longMsg = "There is no Defense Computer. A Defense Computer is needed to " "build this."; - shortMsg = "There is no Defense Computer\n"; - cmd = "menu tremulous_human_dialog\n"; - break; - - case MN_H_TNODEWARN: - longMsg = "WARNING: This Telenode will not be powered. Build near a power " - "structure to prevent seeing this message again."; - shortMsg = "This Telenode will not be powered\n"; - cmd = "menu tremulous_human_dialog\n"; - break; - - case MN_H_RPTWARN: - longMsg = "WARNING: This Repeater will not be powered as there is no parent " - "Reactor providing power. Build a Reactor."; - shortMsg = "This Repeater will not be powered\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "There is no Defense Computer"; + type = DT_BUILD; break; - case MN_H_RPTWARN2: + case MN_H_RPTPOWERHERE: longMsg = "This area already has power. A Repeater is not required here."; - shortMsg = "This area already has power\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "This area already has power"; + type = DT_BUILD; break; case MN_H_NOSLOTS: longMsg = "You have no room to carry this. Please sell any conflicting " "upgrades before purchasing this item."; - shortMsg = "You have no room to carry this\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "You have no room to carry this"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOFUNDS: longMsg = "Insufficient funds. You do not have enough credits to perform " "this action."; - shortMsg = "Insufficient funds\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "Insufficient funds"; + type = DT_ARMOURYEVOLVE; break; case MN_H_ITEMHELD: longMsg = "You already hold this item. It is not possible to carry multiple " "items of the same type."; - shortMsg = "You already hold this item\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "You already hold this item"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOARMOURYHERE: longMsg = "You must be near a powered Armoury in order to purchase " - "weapons, upgrades or non-energy ammunition."; - shortMsg = "You must be near a powered Armoury\n"; - cmd = "menu tremulous_human_dialog\n"; + "weapons, upgrades or ammunition."; + shortMsg = "You must be near a powered Armoury"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOENERGYAMMOHERE: - longMsg = "You must be near an Armoury, Reactor or Repeater in order " - "to purchase energy ammunition."; - shortMsg = "You must be near an Armoury, Reactor or Repeater\n"; - cmd = "menu tremulous_human_dialog\n"; + longMsg = "You must be near a Reactor or a powered Armoury or Repeater " + "in order to purchase energy ammunition."; + shortMsg = "You must be near a Reactor or a powered Armoury or Repeater"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOROOMBSUITON: longMsg = "There is not enough room here to put on a Battle Suit. " "Make sure you have enough head room to climb in."; - shortMsg = "Not enough room here to put on a Battle Suit\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "Not enough room here to put on a Battle Suit"; + type = DT_ARMOURYEVOLVE; break; case MN_H_NOROOMBSUITOFF: longMsg = "There is not enough room here to take off your Battle Suit. " "Make sure you have enough head room to climb out."; - shortMsg = "Not enough room here to take off your Battle Suit\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "Not enough room here to take off your Battle Suit"; + type = DT_ARMOURYEVOLVE; break; case MN_H_ARMOURYBUILDTIMER: longMsg = "You are not allowed to buy or sell weapons until your " "build timer has expired."; - shortMsg = "You can not buy or sell weapos until your build timer " - "expires\n"; - cmd = "menu tremulous_human_dialog\n"; + shortMsg = "You can not buy or sell weapons until your build timer " + "expires"; + type = DT_ARMOURYEVOLVE; break; + case MN_H_DEADTOCLASS: + shortMsg = "You must be dead to use the class command"; + type = DT_COMMAND; + break; - //=============================== - - case MN_A_NOROOM: - longMsg = "There is no room to build here. Move until the structure turns " - "translucent green indicating a valid build location."; - shortMsg = "There is no room to build here\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_H_UNKNOWNSPAWNITEM: + shortMsg = "Unknown starting item"; + type = DT_COMMAND; break; + //=============================== + case MN_A_NOCREEP: longMsg = "There is no creep here. You must build near existing Eggs or " "the Overmind. Alien structures will not support themselves."; - shortMsg = "There is no creep here\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "There is no creep here"; + type = DT_BUILD; break; case MN_A_NOOVMND: longMsg = "There is no Overmind. An Overmind must be built to control " - "the structure you tried to place"; - shortMsg = "There is no Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; - break; - - case MN_A_OVERMIND: - longMsg = "There can only be one Overmind. Destroy the existing one if you " - "wish to move it."; - shortMsg = "There can only be one Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; + "the structure you tried to place."; + shortMsg = "There is no Overmind"; + type = DT_BUILD; break; - case MN_A_HOVEL: - longMsg = "There can only be one Hovel. Destroy the existing one if you " + case MN_A_ONEOVERMIND: + longMsg = "There can only be one Overmind. Deconstruct the existing one if you " "wish to move it."; - shortMsg = "There can only be one Hovel\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "There can only be one Overmind"; + type = DT_BUILD; break; - case MN_A_NOASSERT: - longMsg = "The Overmind cannot control any more structures. Destroy existing " + case MN_A_NOBP: + longMsg = "The Overmind cannot control any more structures. Deconstruct existing " "structures to build more."; - shortMsg = "The Overmind cannot control any more structures\n"; - cmd = "menu tremulous_alien_dialog\n"; - break; - - case MN_A_SPWNWARN: - longMsg = "WARNING: This spawn will not be controlled by an Overmind. " - "Build an Overmind to prevent seeing this message again."; - shortMsg = "This spawn will not be controlled by an Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; - break; - - case MN_A_NORMAL: - longMsg = "Cannot build on this surface. This surface is too steep or " - "unsuitable to build on. Please choose another site for this " - "structure."; - shortMsg = "Cannot build on this surface\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "The Overmind cannot control any more structures"; + type = DT_BUILD; break; case MN_A_NOEROOM: longMsg = "There is no room to evolve here. Move away from walls or other " - "nearby objects and try again."; - cmd = "menu tremulous_alien_dialog\n"; - shortMsg = "There is no room to evolve here\n"; + "nearby objects and try again."; + shortMsg = "There is no room to evolve here"; + type = DT_ARMOURYEVOLVE; break; case MN_A_TOOCLOSE: longMsg = "This location is too close to the enemy to evolve. Move away " - "until you are no longer aware of the enemy's presence and try " - "again."; - shortMsg = "This location is too close to the enemy to evolve\n"; - cmd = "menu tremulous_alien_dialog\n"; + "from the enemy's presence and try again."; + shortMsg = "This location is too close to the enemy to evolve"; + type = DT_ARMOURYEVOLVE; break; case MN_A_NOOVMND_EVOLVE: longMsg = "There is no Overmind. An Overmind must be built to allow " "you to upgrade."; - shortMsg = "There is no Overmind\n"; - cmd = "menu tremulous_alien_dialog\n"; + shortMsg = "There is no Overmind"; + type = DT_ARMOURYEVOLVE; break; case MN_A_EVOLVEBUILDTIMER: - longMsg = "You cannot Evolve until your build timer has expired."; - shortMsg = "You cannot Evolve until your build timer expires\n"; - cmd = "menu tremulous_alien_dialog\n"; + longMsg = "You cannot evolve until your build timer has expired."; + shortMsg = "You cannot evolve until your build timer expires"; + type = DT_ARMOURYEVOLVE; break; - case MN_A_HOVEL_OCCUPIED: - longMsg = "This Hovel is already occupied by another builder."; - shortMsg = "This Hovel is already occupied by another builder\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_INFEST: + trap_Cvar_Set( "ui_currentClass", + va( "%d %d", cg.snap->ps.stats[ STAT_CLASS ], + cg.snap->ps.persistant[ PERS_CREDIT ] ) ); + + cmd = "menu tremulous_alienupgrade\n"; + type = DT_INTERACTIVE; break; - case MN_A_HOVEL_BLOCKED: - longMsg = "The exit to this Hovel is currently blocked. Please wait until it " - "becomes clear then try again."; - shortMsg = "The exit to this Hovel is currently blocked\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_CANTEVOLVE: + shortMsg = va( "You cannot evolve into a %s", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; break; - case MN_A_HOVEL_EXIT: - longMsg = "The exit to this Hovel would always be blocked. Please choose " - "a more suitable location."; - shortMsg = "The exit to this Hovel would always be blocked\n"; - cmd = "menu tremulous_alien_dialog\n"; + case MN_A_EVOLVEWALLWALK: + shortMsg = "You cannot evolve while wallwalking"; + type = DT_ARMOURYEVOLVE; break; - case MN_A_INFEST: - trap_Cvar_Set( "ui_currentClass", va( "%d %d", cg.snap->ps.stats[ STAT_PCLASS ], - cg.snap->ps.persistant[ PERS_CREDIT ] ) ); - cmd = "menu tremulous_alienupgrade\n"; + case MN_A_UNKNOWNCLASS: + shortMsg = "Unknown class"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTSPAWN: + shortMsg = va( "You cannot spawn as a %s", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTALLOWED: + shortMsg = va( "The %s is not allowed", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTATSTAGE: + shortMsg = va( "The %s is not allowed at Stage %d", + BG_ClassConfig( arg )->humanName, + cgs.alienStage + 1 ); + type = DT_ARMOURYEVOLVE; break; default: Com_Printf( "cgame: debug: no such menu %d\n", menu ); } + + if( type == DT_ARMOURYEVOLVE && cg_disableUpgradeDialogs.integer ) + return; - if( !cg_disableWarningDialogs.integer || !shortMsg ) - { - // Player either wants dialog window or there's no short message - if( cmd ) - { - if( longMsg ) - trap_Cvar_Set( "ui_dialog", longMsg ); + if( type == DT_BUILD && cg_disableBuildDialogs.integer ) + return; - trap_SendConsoleCommand( cmd ); - } - } - else + if( type == DT_COMMAND && cg_disableCommandDialogs.integer ) + return; + + if( cmd != dialog ) + { + trap_SendConsoleCommand( cmd ); + } + else if( longMsg && cg_disableWarningDialogs.integer == 0 ) { - // There is short message and player wants it - CG_Printf( shortMsg ); - } + trap_Cvar_Set( "ui_dialog", longMsg ); + trap_SendConsoleCommand( cmd ); + } + else if( shortMsg && cg_disableWarningDialogs.integer < 2 ) + { + CG_Printf( "%s\n", shortMsg ); + } } /* ================= -CG_ServerCommand - -The string has been tokenized and can be retrieved with -Cmd_Argc() / Cmd_Argv() +CG_Say ================= */ -static void CG_ServerCommand( void ) +static void CG_Say( int clientNum, saymode_t mode, const char *text ) { - const char *cmd; - char text[ MAX_SAY_TEXT ]; + char *name; + char prefix[ 11 ] = ""; + char *ignore = ""; + const char *location = ""; + char *color; + char *maybeColon; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) + { + clientInfo_t *ci = &cgs.clientinfo[ clientNum ]; + char *tcolor = S_COLOR_WHITE; - cmd = CG_Argv( 0 ); + name = ci->name; - if( !cmd[ 0 ] ) - { - // server claimed the command - return; - } + if( ci->team == TEAM_ALIENS ) + tcolor = S_COLOR_RED; + else if( ci->team == TEAM_HUMANS ) + tcolor = S_COLOR_CYAN; - if( !strcmp( cmd, "cp" ) ) - { - CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - return; - } + if( cg_chatTeamPrefix.integer ) + Com_sprintf( prefix, sizeof( prefix ), "[%s%c" S_COLOR_WHITE "] ", + tcolor, toupper( *( BG_TeamName( ci->team ) ) ) ); - if( !strcmp( cmd, "cs" ) ) - { - CG_ConfigStringModified( ); - return; + if( ( mode == SAY_TEAM || mode == SAY_AREA ) && + cg.snap->ps.pm_type != PM_INTERMISSION ) + { + int locationNum; + + if( clientNum == cg.snap->ps.clientNum ) + { + centity_t *locent; + + locent = CG_GetPlayerLocation( ); + if( locent ) + locationNum = locent->currentState.generic1; + else + locationNum = -1; + } + else + locationNum = ci->location; + + if( locationNum >= 0 && locationNum < MAX_LOCATIONS ) + { + const char *s = CG_ConfigString( CS_LOCATIONS + locationNum ); + + if( *s ) + location = va( " (%s" S_COLOR_WHITE ")", s ); + } + } } + else + name = "console"; - if( !strcmp( cmd, "print" ) ) + // IRC-like /me parsing + if( mode != SAY_RAW && Q_stricmpn( text, "/me ", 4 ) == 0 ) { - CG_Printf( "%s", CG_Argv( 1 ) ); - return; + text += 4; + Q_strcat( prefix, sizeof( prefix ), "* " ); + maybeColon = ""; } + else + maybeColon = ":"; - if( !strcmp( cmd, "chat" ) ) + switch( mode ) { - if( !cg_teamChatsOnly.integer ) - { - Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT ); - if( Q_stricmpn( text, "[skipnotify]", 12 ) ) - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - CG_RemoveChatEscapeChar( text ); + case SAY_ALL: + // might already be ignored but in that case no harm is done + if( cg_teamChatsOnly.integer ) + ignore = "[skipnotify]"; + +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s%s" S_COLOR_WHITE "%s " S_COLOR_GREEN "%s\n", + ignore, prefix, name, maybeColon, text ); +#else + CG_Printf( "%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_GREEN "%s\n", + ignore, prefix, name, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_TEAM: +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s " S_COLOR_CYAN "%s\n", + ignore, prefix, name, location, maybeColon, text ); +#else + CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s %c" S_COLOR_CYAN "%s\n", + ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_ADMINS: + case SAY_ADMINS_PUBLIC: +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s " S_COLOR_MAGENTA "%s\n", + ignore, prefix, + ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", + name, maybeColon, text ); +#else + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_MAGENTA "%s\n", + ignore, prefix, + ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", + name, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_AREA: +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s " S_COLOR_BLUE "%s\n", + ignore, prefix, name, location, maybeColon, text ); +#else + CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s %c" S_COLOR_BLUE "%s\n", + ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); +#endif + break; + case SAY_PRIVMSG: + case SAY_TPRIVMSG: + color = ( mode == SAY_TPRIVMSG ) ? S_COLOR_CYAN : S_COLOR_GREEN; +#ifdef MODULE_INTERFACE_11 + CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %s%s\n", + ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name, + maybeColon, color, text ); +#else + CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %c%s%s\n", + ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name, + maybeColon, INDENT_MARKER, color, text ); +#endif + if( !ignore[0] ) + { + CG_CenterPrint( va( "%sPrivate message from: " S_COLOR_WHITE "%s", + color, name ), 200, GIANTCHAR_WIDTH * 4 ); + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + clientNum = cg.clientNum; + CG_Printf( ">> to reply, say: /m %d [your message] <<\n", clientNum ); + } + break; + case SAY_RAW: CG_Printf( "%s\n", text ); - } - - return; + break; } - if( !strcmp( cmd, "tchat" ) ) + switch( mode ) { - Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT ); - if( Q_stricmpn( text, "[skipnotify]", 12 ) ) - { - if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + case SAY_TEAM: + case SAY_AREA: + case SAY_TPRIVMSG: + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND ); - else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + break; + } + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND ); - else - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } - CG_RemoveChatEscapeChar( text ); - CG_Printf( "%s\n", text ); - return; + break; + } + default: + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } +} - if( !strcmp( cmd, "scores" ) ) - { - CG_ParseScores( ); - return; - } +/* +================= +CG_VoiceTrack - if( !strcmp( cmd, "tinfo" ) ) +return the voice indexed voice track or print errors quietly to console +in case someone is on an unpure server and wants to know which voice pak +is missing or incomplete +================= +*/ +static voiceTrack_t *CG_VoiceTrack( char *voice, int cmd, int track ) +{ + voice_t *v; + voiceCmd_t *c; + voiceTrack_t *t; + + v = BG_VoiceByName( cgs.voices, voice ); + if( !v ) { - CG_ParseTeamInfo( ); - return; + CG_Printf( "[skipnotify]WARNING: could not find voice \"%s\"\n", voice ); + return NULL; } - - if( !strcmp( cmd, "map_restart" ) ) + c = BG_VoiceCmdByNum( v->cmds, cmd ); + if( !c ) { - CG_MapRestart( ); - return; + CG_Printf( "[skipnotify]WARNING: could not find command %d " + "in voice \"%s\"\n", cmd, voice ); + return NULL; } - - if( Q_stricmp( cmd, "remapShader" ) == 0 ) + t = BG_VoiceTrackByNum( c->tracks, track ); + if( !t ) { - if( trap_Argc( ) == 4 ) - trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) ); + CG_Printf( "[skipnotify]WARNING: could not find track %d for command %d in " + "voice \"%s\"\n", track, cmd, voice ); + return NULL; } + return t; +} - // clientLevelShot is sent before taking a special screenshot for - // the menu system during development - if( !strcmp( cmd, "clientLevelShot" ) ) - { - cg.levelShot = qtrue; +/* +================= +CG_ParseVoice + +voice clientNum vChan cmdNum trackNum [sayText] +================= +*/ +static void CG_ParseVoice( void ) +{ + int clientNum; + int vChan; + char sayText[ MAX_SAY_TEXT] = {""}; + voiceTrack_t *track; + clientInfo_t *ci; + + if( trap_Argc() < 5 || trap_Argc() > 6 ) return; - } - //the server has triggered a menu - if( !strcmp( cmd, "servermenu" ) ) - { - if( trap_Argc( ) == 2 && !cg.demoPlayback ) - CG_Menu( atoi( CG_Argv( 1 ) ) ); + if( trap_Argc() == 6 ) + Q_strncpyz( sayText, CG_Argv( 5 ), sizeof( sayText ) ); + clientNum = atoi( CG_Argv( 1 ) ); + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) return; - } - //the server thinks this client should close all menus - if( !strcmp( cmd, "serverclosemenus" ) ) - { - trap_SendConsoleCommand( "closemenus\n" ); + vChan = atoi( CG_Argv( 2 ) ); + if( vChan < 0 || vChan >= VOICE_CHAN_NUM_CHANS ) return; - } - //poison cloud effect needs to be reliable - if( !strcmp( cmd, "poisoncloud" ) ) - { - cg.poisonedTime = cg.time; + if( cg_teamChatsOnly.integer && vChan != VOICE_CHAN_TEAM ) + return; - if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) - { - cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); - CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity ); - CG_AttachToCent( &cg.poisonCloudPS->attachment ); - } + ci = &cgs.clientinfo[ clientNum ]; + // this joker is still talking + if( ci->voiceTime > cg.time ) return; - } - if( !strcmp( cmd, "weaponswitch" ) ) + track = CG_VoiceTrack( ci->voice, atoi( CG_Argv( 3 ) ), atoi( CG_Argv( 4 ) ) ); + + // keep track of how long the player will be speaking + // assume it takes 3s to say "*unintelligible gibberish*" + if( track ) + ci->voiceTime = cg.time + track->duration; + else + ci->voiceTime = cg.time + 3000; + + if( !sayText[ 0 ] ) { - CG_Printf( "client weaponswitch\n" ); - if( trap_Argc( ) == 2 ) + if( track ) + Q_strncpyz( sayText, track->text, sizeof( sayText ) ); + else + Q_strncpyz( sayText, "*unintelligible gibberish*", sizeof( sayText ) ); + } + + if( !cg_noVoiceText.integer ) + { + switch( vChan ) { - cg.weaponSelect = atoi( CG_Argv( 1 ) ); - cg.weaponSelectTime = cg.time; + case VOICE_CHAN_ALL: + CG_Say( clientNum, SAY_ALL, sayText ); + break; + case VOICE_CHAN_TEAM: + CG_Say( clientNum, SAY_TEAM, sayText ); + break; + default: + break; } + } + // playing voice audio tracks disabled + if( cg_noVoiceChats.integer ) return; - } - // server requests a ptrc - if( !strcmp( cmd, "ptrcrequest" ) ) + // no audio track to play + if( !track ) + return; + + switch( vChan ) { - int code = CG_ReadPTRCode( ); + case VOICE_CHAN_ALL: + trap_S_StartLocalSound( track->track, CHAN_VOICE ); + break; + case VOICE_CHAN_TEAM: + trap_S_StartLocalSound( track->track, CHAN_VOICE ); + break; + case VOICE_CHAN_LOCAL: + trap_S_StartSound( NULL, clientNum, CHAN_VOICE, track->track ); + break; + default: + break; + } +} - trap_SendClientCommand( va( "ptrcverify %d", code ) ); - return; - } +/* +================= +CG_CenterPrint_f +================= +*/ +void CG_CenterPrint_f( void ) +{ + CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +} + +/* +================= +CG_Print_f +================= +*/ +static void CG_Print_f( void ) +{ + CG_Printf( "%s", CG_Argv( 1 ) ); +} + +/* +================= +CG_Chat_f +================= +*/ +static void CG_Chat_f( void ) +{ + char id[ 3 ]; + char mode[ 3 ]; + + trap_Argv( 1, id, sizeof( id ) ); + trap_Argv( 2, mode, sizeof( mode ) ); + + CG_Say( atoi( id ), atoi( mode ), CG_Argv( 3 ) ); +} - // server issues a ptrc - if( !strcmp( cmd, "ptrcissue" ) ) +/* +================= +CG_ServerMenu_f +================= +*/ +static void CG_ServerMenu_f( void ) +{ + if( !cg.demoPlayback ) { if( trap_Argc( ) == 2 ) - { - int code = atoi( CG_Argv( 1 ) ); + CG_Menu( atoi( CG_Argv( 1 ) ), 0 ); + else if( trap_Argc( ) == 3 ) + CG_Menu( atoi( CG_Argv( 1 ) ), atoi( CG_Argv( 2 ) ) ); + } +} - CG_WritePTRCode( code ); - } +/* +================= +CG_ServerCloseMenus_f +================= +*/ +static void CG_ServerCloseMenus_f( void ) +{ + trap_SendConsoleCommand( "closemenus\n" ); +} - return; +/* +================= +CG_PoisonCloud_f +================= +*/ +static void CG_PoisonCloud_f( void ) +{ + cg.poisonedTime = cg.time; + + if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) + { + cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); + CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity ); + CG_AttachToCent( &cg.poisonCloudPS->attachment ); } +} + +static char registeredCmds[ 8192 ]; // cmd1\0cmd2\0cmdn\0\0 +static size_t gcmdsOffset; +static void CG_GameCmds_f( void ) +{ + int i; + int c = trap_Argc( ); + const char *cmd; + size_t len; - // reply to ptrcverify - if( !strcmp( cmd, "ptrcconfirm" ) ) + for( i = 1; i < c; i++ ) { - trap_SendConsoleCommand( "menu ptrc_popmenu\n" ); + cmd = CG_Argv( i ); + len = strlen( cmd ) + 1; + if( len + gcmdsOffset >= sizeof( registeredCmds ) - 1 ) + { + CG_Printf( "AddCommand: too many commands (%d >= %d)\n", + (int)( len + gcmdsOffset ), (int)( sizeof( registeredCmds ) - 1 ) ); + return; + } + trap_AddCommand( cmd ); + strcpy( registeredCmds + gcmdsOffset, cmd ); + gcmdsOffset += len; + } +} +void CG_UnregisterCommands( void ) +{ + size_t len, offset = 0; + while( registeredCmds[ offset ] ) + { + len = strlen( registeredCmds + offset ); + trap_RemoveCommand( registeredCmds + offset ); + offset += len + 1; + } + memset( registeredCmds, 0, 2 ); + gcmdsOffset = 0; +} + +static consoleCommand_t svcommands[ ] = +{ + { "chat", CG_Chat_f }, + { "cmds", CG_GameCmds_f }, + { "cp", CG_CenterPrint_f }, + { "cs", CG_ConfigStringModified }, + { "map_restart", CG_MapRestart }, + { "poisoncloud", CG_PoisonCloud_f }, + { "print", CG_Print_f }, + { "scores", CG_ParseScores }, + { "serverclosemenus", CG_ServerCloseMenus_f }, + { "servermenu", CG_ServerMenu_f }, + { "tinfo", CG_ParseTeamInfo }, + { "voice", CG_ParseVoice } +}; + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) +{ + const char *cmd; + consoleCommand_t *command; + + cmd = CG_Argv( 0 ); + command = bsearch( cmd, svcommands, ARRAY_LEN( svcommands ), + sizeof( svcommands[ 0 ] ), cmdcmp ); + + if( command ) + { + command->function( ); return; } diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c index 84b419f..3316abb 100644 --- a/src/cgame/cg_snapshot.c +++ b/src/cgame/cg_snapshot.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,14 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_snapshot.c -- things that happen on snapshot transition, // not necessarily every single rendered frame - #include "cg_local.h" /* @@ -41,8 +41,8 @@ static void CG_ResetEntity( centity_t *cent ) cent->trailTime = cg.snap->serverTime; - VectorCopy( cent->currentState.origin, cent->lerpOrigin ); - VectorCopy( cent->currentState.angles, cent->lerpAngles ); + VectorCopy( cent->currentState.pos.trBase, cent->lerpOrigin ); + VectorCopy( cent->currentState.apos.trBase, cent->lerpAngles ); if( cent->currentState.eType == ET_PLAYER ) CG_ResetPlayerEntity( cent ); @@ -142,9 +142,6 @@ static void CG_TransitionSnapshot( void ) // execute any server string commands before transitioning entities CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); - // if we had a map_restart, set everthing with initial - if( !cg.snap ) { } //TA: ? - // clear the currentValid flag for all entities in the existing snapshot for( i = 0; i < cg.snap->numEntities; i++ ) { @@ -311,7 +308,7 @@ static snapshot_t *CG_ReadNextSnapshot( void ) if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { - CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i\n", cg.latestSnapshotNum, cgs.processedSnapshotNum ); } @@ -461,4 +458,3 @@ void CG_ProcessSnapshots( void ) if( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); } - diff --git a/src/cgame/cg_syscalls.asm b/src/cgame/cg_syscalls.asm index 2537c91..3b8ed01 100644 --- a/src/cgame/cg_syscalls.asm +++ b/src/cgame/cg_syscalls.asm @@ -12,7 +12,7 @@ equ trap_Argv -9 equ trap_Args -10 equ trap_FS_FOpenFile -11 equ trap_FS_Read -12 -equ trap_FS_Write -13 +equ trap_FS_Write -13 equ trap_FS_FCloseFile -14 equ trap_SendConsoleCommand -15 equ trap_AddCommand -16 @@ -106,6 +106,7 @@ equ trap_Key_SetOverstrikeMode -102 equ trap_Key_GetOverstrikeMode -103 equ trap_S_SoundDuration -104 +equ trap_Field_CompleteList -105 equ memset -201 equ memcpy -202 @@ -118,4 +119,3 @@ equ floor -208 equ ceil -209 equ testPrintInt -210 equ testPrintFloat -211 - diff --git a/src/cgame/cg_syscalls.c b/src/cgame/cg_syscalls.c index aa42019..6b81d64 100644 --- a/src/cgame/cg_syscalls.c +++ b/src/cgame/cg_syscalls.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,14 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_syscalls.c -- this file is only included when building a dll // cg_syscalls.asm is included instead when building a qvm - #include "cg_local.h" static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; @@ -38,9 +38,9 @@ Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) int PASSFLOAT( float x ) { - float floatTemp; - floatTemp = x; - return *(int *)&floatTemp; + floatint_t fi; + fi.f = x; + return fi.i; } void trap_Print( const char *fmt ) @@ -51,6 +51,8 @@ void trap_Print( const char *fmt ) void trap_Error( const char *fmt ) { syscall( CG_ERROR, fmt ); + // shut up GCC warning about returning functions, because we know better + exit(1); } int trap_Milliseconds( void ) @@ -98,7 +100,7 @@ void trap_LiteralArgs( char *buffer, int bufferLength ) syscall( CG_LITERAL_ARGS, buffer, bufferLength ); } -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ) { return syscall( CG_FS_FOPENFILE, qpath, f, mode ); } @@ -118,7 +120,7 @@ void trap_FS_FCloseFile( fileHandle_t f ) syscall( CG_FS_FCLOSEFILE, f ); } -void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ) +void trap_FS_Seek( fileHandle_t f, long offset, enum FS_Origin origin ) { syscall( CG_FS_SEEK, f, offset, origin ); } @@ -290,6 +292,13 @@ sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) return syscall( CG_S_REGISTERSOUND, sample, compressed ); } +#ifndef MODULE_INTERFACE_11 +int trap_S_SoundDuration( sfxHandle_t handle ) +{ + return syscall( CG_S_SOUNDDURATION, handle ); +} +#endif + void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); @@ -340,6 +349,11 @@ void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t * syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); } +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) +{ + return syscall( CG_R_INPVS, p1, p2 ); +} + void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); @@ -370,6 +384,13 @@ void trap_R_SetColor( const float *rgba ) syscall( CG_R_SETCOLOR, rgba ); } +#ifndef MODULE_INTERFACE_11 +void trap_R_SetClipRegion( const float *region ) +{ + syscall( CG_R_SETCLIPREGION, region ); +} +#endif + void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) { @@ -572,3 +593,16 @@ void trap_Key_SetBinding( int keynum, const char *binding ) { syscall( CG_KEY_SETBINDING, keynum, binding ); } +#ifndef MODULE_INTERFACE_11 +void trap_Key_SetOverstrikeMode( qboolean state ) { + syscall( CG_KEY_SETOVERSTRIKEMODE, state ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) { + return syscall( CG_KEY_GETOVERSTRIKEMODE ); +} + +void trap_Field_CompleteList( char *listJson ) { + syscall( CG_FIELD_COMPLETELIST, listJson ); +} +#endif diff --git a/src/cgame/cg_syscalls_11.asm b/src/cgame/cg_syscalls_11.asm index 0893ebc..13c262c 100644 --- a/src/cgame/cg_syscalls_11.asm +++ b/src/cgame/cg_syscalls_11.asm @@ -12,7 +12,7 @@ equ trap_Argv -9 equ trap_Args -10 equ trap_FS_FOpenFile -11 equ trap_FS_Read -12 -equ trap_FS_Write -13 +equ trap_FS_Write -13 equ trap_FS_FCloseFile -14 equ trap_SendConsoleCommand -15 equ trap_AddCommand -16 @@ -112,4 +112,3 @@ equ floor -208 equ ceil -209 equ testPrintInt -210 equ testPrintFloat -211 - diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c index c8943ed..c7d2662 100644 --- a/src/cgame/cg_trails.c +++ b/src/cgame/cg_trails.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,14 +16,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_trails.c -- the trail system - #include "cg_local.h" static baseTrailSystem_t baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ]; @@ -60,7 +60,7 @@ static void CG_CalculateBeamNodeProperties( trailBeam_t *tb ) if( ts->destroyTime > 0 && btb->fadeOutTime ) { - fadeAlpha -= ( cg.time - ts->destroyTime ) / btb->fadeOutTime; + fadeAlpha -= (float)( cg.time - ts->destroyTime ) / btb->fadeOutTime; if( fadeAlpha < 0.0f ) fadeAlpha = 0.0f; @@ -559,7 +559,7 @@ static void CG_UpdateBeam( trailBeam_t *tb ) tb->lastEvalTime = cg.time; // first make sure this beam has enough nodes - if( ts->destroyTime <= 0 ) + if( ts->destroyTime <= 0 || btb->fadeOutTime > 0 ) { nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1; @@ -898,7 +898,7 @@ static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p ) { if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS ) { - CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token ); + CG_Printf( S_COLOR_RED "ERROR: too many jitters\n" ); break; } @@ -1009,6 +1009,15 @@ static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, cons } else if( !Q_stricmp( token, "thirdPersonOnly" ) ) bts->thirdPersonOnly = qtrue; + else if( !Q_stricmp( token, "lifeTime" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + bts->lifeTime = atoi_neg( token, qfalse ); + continue; + } else if( !Q_stricmp( token, "beam" ) ) //acceptable text continue; else if( !Q_stricmp( token, "}" ) ) @@ -1051,9 +1060,11 @@ static qboolean CG_ParseTrailFile( const char *fileName ) if( len <= 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( S_COLOR_RED "ERROR: trail file %s too long\n", fileName ); + trap_FS_FCloseFile( f ); + CG_Printf( S_COLOR_RED "ERROR: trail file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); return qfalse; } @@ -1251,11 +1262,14 @@ static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb, if( cg_debugTrails.integer >= 1 ) CG_Printf( "TB %s created\n", ts->class->name ); - break; + return tb; } } - return tb; + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "MAX_TRAIL_BEAMS\n" ); + + return NULL; } @@ -1291,18 +1305,22 @@ trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ) ts->valid = qtrue; ts->destroyTime = -1; - + ts->birthTime = cg.time; + for( j = 0; j < bts->numBeams; j++ ) CG_SpawnNewTrailBeam( bts->beams[ j ], ts ); if( cg_debugTrails.integer >= 1 ) CG_Printf( "TS %s created\n", bts->name ); - break; + return ts; } } - return ts; + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "MAX_TRAIL_SYSTEMS\n" ); + + return NULL; } /* @@ -1406,6 +1424,19 @@ static void CG_GarbageCollectTrailSystems( void ) CG_DestroyTrailSystem( &tempTS ); } + // lifetime expired + if( ts->destroyTime <= 0 && ts->class->lifeTime && + ts->birthTime + ts->class->lifeTime < cg.time ) + { + trailSystem_t *tempTS = ts; + + CG_DestroyTrailSystem( &tempTS ); + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TS %s expired (born %d, lives %d, now %d)\n", + ts->class->name, ts->birthTime, ts->class->lifeTime, + cg.time ); + } + if( cg_debugTrails.integer >= 1 && !ts->valid ) CG_Printf( "TS %s garbage collected\n", ts->class->name ); } diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c index c418726..e9dbcd6 100644 --- a/src/cgame/cg_tutorial.c +++ b/src/cgame/cg_tutorial.c @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,8 +16,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -35,7 +36,8 @@ static bind_t bindings[ ] = { { "+button2", "Activate Upgrade", { -1, -1 } }, { "+speed", "Run/Walk", { -1, -1 } }, - { "boost", "Sprint", { -1, -1 } }, + { "+button6", "Dodge", { -1, -1 } }, + { "+button8", "Sprint", { -1, -1 } }, { "+moveup", "Jump", { -1, -1 } }, { "+movedown", "Crouch", { -1, -1 } }, { "+attack", "Primary Attack", { -1, -1 } }, @@ -49,7 +51,7 @@ static bind_t bindings[ ] = { "weapnext", "Next Upgrade", { -1, -1 } } }; -static const int numBindings = sizeof( bindings ) / sizeof( bind_t ); +static const size_t numBindings = ARRAY_LEN( bindings ); /* ================= @@ -122,9 +124,8 @@ static const char *CG_KeyNameForCommand( const char *command ) } else { - Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ), - MAX_STRING_CHARS ); - Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" ); + Com_sprintf( buffer, MAX_STRING_CHARS, "\"%s\" (unbound)", + bindings[ i ].humanName ); } return buffer; @@ -157,12 +158,12 @@ static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFract if( healthFraction ) { - health = es->generic1 & B_HEALTH_MASK; - *healthFraction = (float)health / B_HEALTH_MASK; + health = es->misc; + *healthFraction = (float)health / BG_Buildable( es->modelindex )->health; } if( es->eType == ET_BUILDABLE && - ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) ) + ps->stats[ STAT_TEAM ] == BG_Buildable( es->modelindex )->team ) return es; else return NULL; @@ -183,61 +184,61 @@ static void CG_AlienBuilderText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to place the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to cancel placing the %s\n", CG_KeyNameForCommand( "+button5" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to build a structure\n", CG_KeyNameForCommand( "+attack" ) ) ); + } - if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) { - if( cgs.markDeconstruct ) + if( es->eFlags & EF_B_MARKED ) { - if( es->generic1 & B_MARKED_TOGGLEBIT ) - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to unmark this structure\n", - CG_KeyNameForCommand( "deconstruct" ) ) ); - } - else - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to mark this structure\n", - CG_KeyNameForCommand( "deconstruct" ) ) ); - } + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to unmark this structure for replacement\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to destroy this structure\n", + va( "Press %s to mark this structure for replacement\n", CG_KeyNameForCommand( "deconstruct" ) ) ); } } - } - - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) - { - if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE ) + else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to swipe\n", - CG_KeyNameForCommand( "+button5" ) ) ); + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); } + } + if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_BUILDER0_UPG ) + { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to launch a projectile\n", - CG_KeyNameForCommand( "+button2" ) ) ); + CG_KeyNameForCommand( "+button2" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to walk on walls\n", - CG_KeyNameForCommand( "+movedown" ) ) ); + CG_KeyNameForCommand( "+movedown" ) ) ); } } @@ -249,7 +250,7 @@ CG_AlienLevel0Text static void CG_AlienLevel0Text( char *text, playerState_t *ps ) { Q_strcat( text, MAX_TUTORIAL_TEXT, - "Touch a human to damage it\n" ); + "Touch humans to damage them\n" ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to walk on walls\n", @@ -264,13 +265,13 @@ CG_AlienLevel1Text static void CG_AlienLevel1Text( char *text, playerState_t *ps ) { Q_strcat( text, MAX_TUTORIAL_TEXT, - "Touch a human to grab it\n" ); + "Touch humans to grab them\n" ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to swipe\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG ) + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL1_UPG ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to spray poisonous gas\n", @@ -293,7 +294,7 @@ static void CG_AlienLevel2Text( char *text, playerState_t *ps ) va( "Press %s to bite\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG ) + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL2_UPG ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to invoke an electrical attack\n", @@ -316,7 +317,7 @@ static void CG_AlienLevel3Text( char *text, playerState_t *ps ) va( "Press %s to bite\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG ) + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL3_UPG ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to launch a projectile\n", @@ -340,7 +341,7 @@ static void CG_AlienLevel4Text( char *text, playerState_t *ps ) CG_KeyNameForCommand( "+attack" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Hold down and release %s to charge\n", + va( "Hold down and release %s to trample\n", CG_KeyNameForCommand( "+button5" ) ) ); } @@ -351,36 +352,47 @@ CG_HumanCkitText */ static void CG_HumanCkitText( char *text, playerState_t *ps ) { - buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; - float health; + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + entityState_t *es; if( buildable > BA_NONE ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to place the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to cancel placing the %s\n", CG_KeyNameForCommand( "+button5" ), - BG_FindHumanNameForBuildable( buildable ) ) ); + BG_Buildable( buildable )->humanName ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to build a structure\n", CG_KeyNameForCommand( "+attack" ) ) ); + } - if( CG_BuildableInRange( ps, &health ) ) + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) { - if( health < 1.0f ) + if( es->eFlags & EF_B_MARKED ) { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Hold %s to repair this structure\n", - CG_KeyNameForCommand( "+button5" ) ) ); + va( "Press %s to unmark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); } - + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to mark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + else + { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to destroy this structure\n", CG_KeyNameForCommand( "deconstruct" ) ) ); @@ -396,21 +408,17 @@ CG_HumanText static void CG_HumanText( char *text, playerState_t *ps ) { char *name; - int ammo, clips; upgrade_t upgrade = UP_NONE; - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) name = cg_weapons[ cg.weaponSelect ].humanName; - else if( cg.weaponSelect > 32 ) + else { name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; upgrade = cg.weaponSelect - 32; } - ammo = ps->ammo; - clips = ps->clips; - - if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) ) + if( !ps->ammo && !ps->clips && !BG_Weapon( ps->weapon )->infiniteAmmo ) { //no ammo switch( ps->weapon ) @@ -429,7 +437,7 @@ static void CG_HumanText( char *text, playerState_t *ps ) case WP_MASS_DRIVER: case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Find a Reactor or Repeater and press %s for more ammo\n", + va( "Find an Armoury, Reactor, or Repeater and press %s for more ammo\n", CG_KeyNameForCommand( "buy ammo" ) ) ); break; @@ -451,14 +459,14 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); break; case WP_MASS_DRIVER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to zoom\n", @@ -469,7 +477,7 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to activate the %s\n", CG_KeyNameForCommand( "+attack" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); break; case WP_LUCIFER_CANNON: @@ -480,11 +488,10 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+button5" ), - BG_FindHumanNameForWeapon( ps->weapon ) ) ); + BG_Weapon( ps->weapon )->humanName ) ); break; case WP_HBUILD: - case WP_HBUILD2: CG_HumanCkitText( text, ps ); break; @@ -501,7 +508,7 @@ static void CG_HumanText( char *text, playerState_t *ps ) CG_KeyNameForCommand( "weapnext" ) ) ); if( upgrade == UP_NONE || - ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) ) + ( upgrade > UP_NONE && BG_Upgrade( upgrade )->usable ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use the %s\n", @@ -515,16 +522,52 @@ static void CG_HumanText( char *text, playerState_t *ps ) Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use your %s\n", CG_KeyNameForCommand( "itemact medkit" ), - BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) ); + BG_Upgrade( UP_MEDKIT )->humanName ) ); + } + + if( ps->stats[ STAT_STAMINA ] <= STAMINA_BLACKOUT_LEVEL ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + "You are blacking out. Stop sprinting to recover stamina\n" ); + } + else if( ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Your stamina is low. Stop sprinting to recover\n" ); + } + + switch( cg.nearUsableBuildable ) + { + case BA_H_ARMOURY: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to buy equipment upgrades at the %s. Sell your old weapon first!\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable )->humanName ) ); + break; + case BA_H_REPEATER: + case BA_H_REACTOR: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to refill your energy weapon's ammo at the %s\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable )->humanName ) ); + break; + case BA_NONE: + break; + default: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use the %s\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable )->humanName ) ); + break; } Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to use a structure\n", - CG_KeyNameForCommand( "+button7" ) ) ); + va( "Press %s and any direction to sprint\n", + CG_KeyNameForCommand( "+button8" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to sprint\n", - CG_KeyNameForCommand( "boost" ) ) ); + va( "Press %s and back or strafe to dodge\n", + CG_KeyNameForCommand( "+button6" ) ) ); } /* @@ -534,37 +577,56 @@ CG_SpectatorText */ static void CG_SpectatorText( char *text, playerState_t *ps ) { - if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE ) + if( cgs.clientinfo[ cg.clientNum ].team != TEAM_NONE ) { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to spawn\n", CG_KeyNameForCommand( "+attack" ) ) ); + if( ps->pm_flags & PMF_QUEUED ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to leave spawn queue\n", + CG_KeyNameForCommand( "+attack" ) ) ); + else + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to spawn\n", + CG_KeyNameForCommand( "+attack" ) ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to join a team\n", CG_KeyNameForCommand( "+attack" ) ) ); + va( "Press %s to join a team\n", + CG_KeyNameForCommand( "+attack" ) ) ); } if( ps->pm_flags & PMF_FOLLOW ) { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to stop following\n", - CG_KeyNameForCommand( "+button2" ) ) ); + if( !cg.chaseFollow ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to switch to chase-cam spectator mode\n", + CG_KeyNameForCommand( "+button2" ) ) ); + else if( cgs.clientinfo[ cg.clientNum ].team == TEAM_NONE ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to return to free spectator mode\n", + CG_KeyNameForCommand( "+button2" ) ) ); + else + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to stop following\n", + CG_KeyNameForCommand( "+button2" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s or ", CG_KeyNameForCommand( "weapprev" ) ) ); + va( "Press %s or ", + CG_KeyNameForCommand( "weapprev" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "%s to change player\n", CG_KeyNameForCommand( "weapnext" ) ) ); + va( "%s to change player\n", + CG_KeyNameForCommand( "weapnext" ) ) ); } else { Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to follow a %s\n", CG_KeyNameForCommand( "+button2" ), - ( cgs.clientinfo[ cg.clientNum ].team == PTE_NONE ) - ? "player" : "teammate" ) ); + va( "Press %s to follow a player\n", + CG_KeyNameForCommand( "+button2" ) ) ); } } +#define BINDING_REFRESH_INTERVAL 30 + /* =============== CG_TutorialText @@ -576,22 +638,26 @@ const char *CG_TutorialText( void ) { playerState_t *ps; static char text[ MAX_TUTORIAL_TEXT ]; + static int refreshBindings = 0; + + if( refreshBindings == 0 ) + CG_GetBindings( ); - CG_GetBindings( ); + refreshBindings = ( refreshBindings + 1 ) % BINDING_REFRESH_INTERVAL; text[ 0 ] = '\0'; ps = &cg.snap->ps; if( !cg.intermissionStarted && !cg.demoPlayback ) { - if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR || + if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || ps->pm_flags & PMF_FOLLOW ) { CG_SpectatorText( text, ps ); } else if( ps->stats[ STAT_HEALTH ] > 0 ) { - switch( ps->stats[ STAT_PCLASS ] ) + switch( ps->stats[ STAT_CLASS ] ) { case PCL_ALIEN_BUILDER0: case PCL_ALIEN_BUILDER0_UPG: @@ -622,6 +688,7 @@ const char *CG_TutorialText( void ) break; case PCL_HUMAN: + case PCL_HUMAN_BSUIT: CG_HumanText( text, ps ); break; @@ -629,26 +696,11 @@ const char *CG_TutorialText( void ) break; } - if( ps->stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( ps->stats[ STAT_TEAM ] == TEAM_ALIENS ) { - entityState_t *es = CG_BuildableInRange( ps, NULL ); - - if( ps->stats[ STAT_STATE ] & SS_HOVELING ) - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to exit the hovel\n", - CG_KeyNameForCommand( "+button7" ) ) ); - } - else if( es && es->modelindex == BA_A_HOVEL && - es->generic1 & B_SPAWNED_TOGGLEBIT && - ( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || - ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) - { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to enter the hovel\n", - CG_KeyNameForCommand( "+button7" ) ) ); - } - else if( BG_UpgradeClassAvailable( ps ) ) + if( BG_AlienCanEvolve( ps->stats[ STAT_CLASS ], + ps->persistant[ PERS_CREDIT ], + cgs.alienStage ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to evolve\n", @@ -656,7 +708,23 @@ const char *CG_TutorialText( void ) } } } + } + else if( !cg.demoPlayback ) + { + if( !CG_ClientIsReady( ps->clientNum ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s when ready to continue\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, "Waiting for other players to be ready\n" ); + } + } + if( !cg.demoPlayback ) + { Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" ); } diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c index 428f299..871b095 100644 --- a/src/cgame/cg_view.c +++ b/src/cgame/cg_view.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,18 +17,16 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_view.c -- setup all the parameters (position, angle, etc) // for a 3D rendering - #include "cg_local.h" - /* ============================================================================= @@ -181,7 +180,7 @@ static void CG_AddTestModel( void ) return; } - // if testing a gun, set the origin reletive to the view origin + // if testing a gun, set the origin relative to the view origin if( cg.testGun ) { VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); @@ -229,21 +228,7 @@ static void CG_CalcVrect( void ) if( cg.snap->ps.pm_type == PM_INTERMISSION ) size = 100; else - { - // bound normal viewsize - if( cg_viewsize.integer < 30 ) - { - trap_Cvar_Set( "cg_viewsize", "30" ); - size = 30; - } - else if( cg_viewsize.integer > 100 ) - { - trap_Cvar_Set( "cg_viewsize","100" ); - size = 100; - } - else - size = cg_viewsize.integer; - } + size = cg_viewsize.integer; cg.refdef.width = cgs.glconfig.vidWidth * size / 100; cg.refdef.width &= ~1; @@ -257,119 +242,282 @@ static void CG_CalcVrect( void ) //============================================================================== - /* =============== CG_OffsetThirdPersonView =============== */ -#define FOCUS_DISTANCE 512 -static void CG_OffsetThirdPersonView( void ) +void CG_OffsetThirdPersonView( void ) { + int i; vec3_t forward, right, up; vec3_t view; - vec3_t focusAngles; trace_t trace; static vec3_t mins = { -8, -8, -8 }; static vec3_t maxs = { 8, 8, 8 }; vec3_t focusPoint; - float focusDist; - float forwardScale, sideScale; vec3_t surfNormal; - - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING ) + int cmdNum; + usercmd_t cmd, oldCmd; + float range; + vec3_t mouseInputAngles; + vec3_t rotationAngles; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + float deltaPitch; + static float pitch; + static vec3_t killerPos = { 0, 0, 0 }; + + // If cg_thirdpersonShoulderViewMode == 2, do shoulder view instead + // If cg_thirdpersonShoulderViewMode == 1, do shoulder view when chasing + // a wallwalker because it's really erratic to watch + if( ( cg_thirdPersonShoulderViewMode.integer == 2 ) || + ( ( cg_thirdPersonShoulderViewMode.integer == 1 ) && + ( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) && + ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) ) ) { - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); + CG_OffsetShoulderView( ); + return; } - else - VectorSet( surfNormal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( &cg.predictedPlayerState, surfNormal ); + // Set the view origin to the class's view height VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.viewheight, surfNormal, cg.refdef.vieworg ); - VectorCopy( cg.refdefViewAngles, focusAngles ); + // Set the focus point where the camera will look (at the player's vieworg) + VectorCopy( cg.refdef.vieworg, focusPoint ); - // if dead, look at killer + // If player is dead, we want the player to be between us and the killer + // so pretend that the player was looking at the killer, then place cam behind them. if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) { - focusAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; - cg.refdefViewAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; + int killerEntNum = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; + + // already looking at ourself + if( killerEntNum != cg.snap->ps.clientNum ) + { + vec3_t lookDirection; + if( cg.wasDeadLastFrame == qfalse || !cg_staticDeathCam.integer ) + { + VectorCopy( cg_entities[ killerEntNum ].lerpOrigin, killerPos ); + cg.wasDeadLastFrame = qtrue; + } + VectorSubtract( killerPos, cg.refdef.vieworg, lookDirection ); + vectoangles( lookDirection, cg.refdefViewAngles ); + } } - //if ( focusAngles[PITCH] > 45 ) { - // focusAngles[PITCH] = 45; // don't go too far overhead - //} - AngleVectors( focusAngles, forward, NULL, NULL ); + // get and rangecheck cg_thirdPersonRange + range = cg_thirdPersonRange.value; + if( range > 150.0f ) range = 150.0f; + if( range < 30.0f ) range = 30.0f; + + // Calculate the angle of the camera's position around the player. + // Unless in demo, PLAYING in third person, or in dead-third-person cam, allow the player + // to control camera position offsets using the mouse position. + if( cg.demoPlayback || + ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) && + ( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) ) ) + { + // Collect our input values from the mouse. + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldCmd ); - VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + // Prevent pitch from wrapping and clamp it within a [-75, 90] range. + // Cgame has no access to ps.delta_angles[] here, so we need to reproduce + // it ourselves. + deltaPitch = SHORT2ANGLE( cmd.angles[ PITCH ] - oldCmd.angles[ PITCH ] ); + if( fabs(deltaPitch) < 200.0f ) + { + pitch += deltaPitch; + } - VectorCopy( cg.refdef.vieworg, view ); + mouseInputAngles[ PITCH ] = pitch; + mouseInputAngles[ YAW ] = -1.0f * SHORT2ANGLE( cmd.angles[ YAW ] ); // yaw is inverted + mouseInputAngles[ ROLL ] = 0.0f; + + for( i = 0; i < 3; i++ ) + mouseInputAngles[ i ] = AngleNormalize180( mouseInputAngles[ i ] ); - VectorMA( view, 12, surfNormal, view ); + // Set the rotation angles to be the view angles offset by the mouse input + // Ignore the original pitch though; it's too jerky otherwise + if( !cg_thirdPersonPitchFollow.integer ) + cg.refdefViewAngles[ PITCH ] = 0.0f; - //cg.refdefViewAngles[PITCH] *= 0.5; + for( i = 0; i < 3; i++ ) + { + rotationAngles[ i ] = AngleNormalize180(cg.refdefViewAngles[ i ]) + mouseInputAngles[ i ]; + AngleNormalize180( rotationAngles[ i ] ); + } - AngleVectors( cg.refdefViewAngles, forward, right, up ); + // Don't let pitch go too high/too low or the camera flips around and + // that's really annoying. + // However, when we're not on the floor or ceiling (wallwalk) pitch + // may not be pitch, so just let it go. + if( surfNormal[ 2 ] > 0.5f || surfNormal[ 2 ] < -0.5f ) + { + if( rotationAngles[ PITCH ] > 85.0f ) + rotationAngles[ PITCH ] = 85.0f; + else if( rotationAngles[ PITCH ] < -85.0f ) + rotationAngles[ PITCH ] = -85.0f; + } + + // Perform the rotations specified by rotationAngles. + AnglesToAxis( rotationAngles, axis ); + if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( cg.snap->ps.grapplePoint, axis, rotaxis, qfalse, + cg.snap->ps.eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + // Convert the new axis back to angles. + AxisToAngles( rotaxis, rotationAngles ); + } + else + { + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) + { + // If we're playing the game in third person, the viewangles already + // take care of our mouselook, so just use them. + for( i = 0; i < 3; i++ ) + rotationAngles[ i ] = cg.refdefViewAngles[ i ]; + } + else // dead + { + rotationAngles[ PITCH ] = 20.0f; + rotationAngles[ YAW ] = cg.refdefViewAngles[ YAW ]; + } + } - forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); - sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); - VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); - VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + rotationAngles[ YAW ] -= cg_thirdPersonAngle.value; - // trace a ray from the origin to the viewpoint to make sure the view isn't - // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + // Move the camera range distance back. + AngleVectors( rotationAngles, forward, right, up ); + VectorCopy( cg.refdef.vieworg, view ); + VectorMA( view, -range, forward, view ); + // Ensure that the current camera position isn't out of bounds and that there + // is nothing between the camera and the player. if( !cg_cameraMode.integer ) { + // Trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); - if( trace.fraction != 1.0 ) + if( trace.fraction != 1.0f ) { VectorCopy( trace.endpos, view ); - view[ 2 ] += ( 1.0 - trace.fraction ) * 32; - // try another trace to this position, because a tunnel may have the ceiling - // close enogh that this is poking out + view[ 2 ] += ( 1.0f - trace.fraction ) * 32; + // Try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out. CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); VectorCopy( trace.endpos, view ); } } + // Set the camera position to what we calculated. VectorCopy( view, cg.refdef.vieworg ); - // select pitch to look at focus point from vieword - VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); - focusDist = sqrt( focusPoint[ 0 ] * focusPoint[ 0 ] + focusPoint[ 1 ] * focusPoint[ 1 ] ); - if ( focusDist < 1 ) { - focusDist = 1; // should never happen + // The above checks may have moved the camera such that the existing viewangles + // may not still face the player. Recalculate them to do so. + // but if we're dead, don't bother because we'd rather see what killed us + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) + { + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + vectoangles( focusPoint, cg.refdefViewAngles ); } - cg.refdefViewAngles[ PITCH ] = -180 / M_PI * atan2( focusPoint[ 2 ], focusDist ); - cg.refdefViewAngles[ YAW ] -= cg_thirdPersonAngle.value; } +/* +=============== +CG_OffsetShoulderView + +=============== +*/ +void CG_OffsetShoulderView( void ) +{ + int i; + int cmdNum; + usercmd_t cmd, oldCmd; + vec3_t rotationAngles; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + float deltaMousePitch; + static float mousePitch; + vec3_t forward, right, up; + classConfig_t* classConfig; + + // Ignore following pitch; it's too jerky otherwise. + if( !cg_thirdPersonPitchFollow.integer ) + cg.refdefViewAngles[ PITCH ] = 0.0f; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + classConfig = BG_ClassConfig( cg.snap->ps.stats[ STAT_CLASS ] ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 0 ], forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 1 ], right, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 2 ], up, cg.refdef.vieworg ); + + // If someone is playing like this, the rest is already taken care of + // so just get the firstperson effects and leave. + if( !cg.demoPlayback && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + CG_OffsetFirstPersonView(); + return; + } + + // Get mouse input for camera rotation. + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldCmd ); + + // Prevent pitch from wrapping and clamp it within a [30, -50] range. + // Cgame has no access to ps.delta_angles[] here, so we need to reproduce + // it ourselves here. + deltaMousePitch = SHORT2ANGLE( cmd.angles[ PITCH ] - oldCmd.angles[ PITCH ] ); + if( fabs(deltaMousePitch) < 200.0f ) + mousePitch += deltaMousePitch; + + // Handle pitch. + rotationAngles[ PITCH ] = mousePitch; + + rotationAngles[ PITCH ] = AngleNormalize180( rotationAngles[ PITCH ] + AngleNormalize180( cg.refdefViewAngles[ PITCH ] ) ); + if( rotationAngles [ PITCH ] < -90.0f ) rotationAngles [ PITCH ] = -90.0f; + if( rotationAngles [ PITCH ] > 90.0f ) rotationAngles [ PITCH ] = 90.0f; + + // Yaw and Roll are much easier. + rotationAngles[ YAW ] = SHORT2ANGLE( cmd.angles[ YAW ] ) + cg.refdefViewAngles[ YAW ]; + rotationAngles[ ROLL ] = 0.0f; + + // Perform the rotations. + AnglesToAxis( rotationAngles, axis ); + if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( cg.snap->ps.grapplePoint, axis, rotaxis, qfalse, + cg.snap->ps.eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + AxisToAngles( rotaxis, rotationAngles ); + + // Actually set the viewangles. + for( i = 0; i < 3; i++ ) + cg.refdefViewAngles[ i ] = rotationAngles[ i ]; + + // Now run the first person stuff so we get various effects added. + CG_OffsetFirstPersonView( ); +} + // this causes a compiler bug on mac MrC compiler static void CG_StepOffset( void ) { float steptime; - int timeDelta; + int timeDelta; vec3_t normal; playerState_t *ps = &cg.predictedPlayerState; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( ps, normal ); - steptime = BG_FindSteptimeForClass( ps->stats[ STAT_PCLASS ] ); + steptime = BG_Class( ps->stats[ STAT_CLASS ] )->steptime; // smooth out stair climbing timeDelta = cg.time - cg.stepTime; @@ -378,17 +526,15 @@ static void CG_StepOffset( void ) float stepChange = cg.stepChange * (steptime - timeDelta) / steptime; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg ); - else - cg.refdef.vieworg[ 2 ] -= stepChange; + VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg ); } } -#define PCLOUD_ROLL_AMPLITUDE 25.0f -#define PCLOUD_ROLL_FREQUENCY 0.4f -#define PCLOUD_ZOOM_AMPLITUDE 15 -#define PCLOUD_ZOOM_FREQUENCY 0.7f +#define PCLOUD_ROLL_AMPLITUDE 25.0f +#define PCLOUD_ROLL_FREQUENCY 0.4f +#define PCLOUD_ZOOM_AMPLITUDE 15 +#define PCLOUD_ZOOM_FREQUENCY 0.625f // 2.5s / 4 +#define PCLOUD_DISORIENT_DURATION 2500 /* @@ -397,7 +543,7 @@ CG_OffsetFirstPersonView =============== */ -static void CG_OffsetFirstPersonView( void ) +void CG_OffsetFirstPersonView( void ) { float *origin; float *angles; @@ -409,19 +555,10 @@ static void CG_OffsetFirstPersonView( void ) vec3_t predictedVelocity; int timeDelta; float bob2; - vec3_t normal, baseOrigin; + vec3_t normal; playerState_t *ps = &cg.predictedPlayerState; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); - + BG_GetClientNormal( ps, normal ); if( cg.snap->ps.pm_type == PM_INTERMISSION ) return; @@ -429,8 +566,6 @@ static void CG_OffsetFirstPersonView( void ) origin = cg.refdef.vieworg; angles = cg.refdefViewAngles; - VectorCopy( origin, baseOrigin ); - // if dead, fix the angle and don't add any kick if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) { @@ -441,9 +576,6 @@ static void CG_OffsetFirstPersonView( void ) return; } - // add angles based on weapon kick - VectorAdd( angles, cg.kick_angles, angles ); - // add angles based on damage kick if( cg.damageTime ) { @@ -485,10 +617,10 @@ static void CG_OffsetFirstPersonView( void ) // add angles based on bob // bob amount is class dependant - if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) bob2 = 0.0f; else - bob2 = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); + bob2 = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; #define LEVEL4_FEEDBACK 10.0f @@ -498,7 +630,8 @@ static void CG_OffsetFirstPersonView( void ) { if( ps->stats[ STAT_MISC ] > 0 ) { - float fraction = (float)ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME; + float fraction = (float)ps->stats[ STAT_MISC ] / + LEVEL4_TRAMPLE_CHARGE_MAX; if( fraction > 1.0f ) fraction = 1.0f; @@ -530,26 +663,24 @@ static void CG_OffsetFirstPersonView( void ) #define LEVEL3_FEEDBACK 20.0f //provide some feedback for pouncing - if( cg.predictedPlayerState.weapon == WP_ALEVEL3 || - cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) + if( ( cg.predictedPlayerState.weapon == WP_ALEVEL3 || + cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) && + cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) { - if( cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) - { - float fraction1, fraction2; - vec3_t forward; - - AngleVectors( angles, forward, NULL, NULL ); - VectorNormalize( forward ); + float fraction1, fraction2; + vec3_t forward; - fraction1 = (float)( cg.time - cg.weapon2Time ) / (float)LEVEL3_POUNCE_CHARGE_TIME; + AngleVectors( angles, forward, NULL, NULL ); + VectorNormalize( forward ); - if( fraction1 > 1.0f ) - fraction1 = 1.0f; + fraction1 = (float)cg.predictedPlayerState.stats[ STAT_MISC ] / + LEVEL3_POUNCE_TIME_UPG; + if( fraction1 > 1.0f ) + fraction1 = 1.0f; - fraction2 = -sin( fraction1 * M_PI / 2 ); + fraction2 = -sin( fraction1 * M_PI / 2 ); - VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); - } + VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); } #define STRUGGLE_DIST 5.0f @@ -562,7 +693,6 @@ static void CG_OffsetFirstPersonView( void ) usercmd_t cmd; int cmdNum; float fFraction, rFraction, uFraction; - float fFraction2, rFraction2, uFraction2; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); @@ -580,10 +710,6 @@ static void CG_OffsetFirstPersonView( void ) if( uFraction > 1.0f ) uFraction = 1.0f; - fFraction2 = -sin( fFraction * M_PI / 2 ); - rFraction2 = -sin( rFraction * M_PI / 2 ); - uFraction2 = -sin( uFraction * M_PI / 2 ); - if( cmd.forwardmove > 0 ) VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin ); else if( cmd.forwardmove < 0 ) @@ -606,14 +732,21 @@ static void CG_OffsetFirstPersonView( void ) cg.upMoveTime = cg.time; } - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED && + if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && + ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { - float fraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 2 * PCLOUD_ROLL_FREQUENCY ); - float pitchFraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 5 * PCLOUD_ROLL_FREQUENCY ); - - fraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME ); - pitchFraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME ); + float scale, fraction, pitchFraction; + + scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / + BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); + if( scale < 0.0f ) + scale = 0.0f; + + fraction = sin( ( cg.time - cg.poisonedTime ) / 500.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * + scale; + pitchFraction = sin( ( cg.time - cg.poisonedTime ) / 200.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * + scale; angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE; angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE; @@ -621,17 +754,17 @@ static void CG_OffsetFirstPersonView( void ) } // this *feels* more realisitic for humans - if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && + ( cg.predictedPlayerState.pm_type == PM_NORMAL || + cg.predictedPlayerState.pm_type == PM_JETPACK ) ) { angles[PITCH] += cg.bobfracsin * bob2 * 0.5; // heavy breathing effects //FIXME: sound - if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ) + if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < STAMINA_BREATHING_LEVEL ) { - float deltaBreath = (float)( - cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ? - -cg.predictedPlayerState.stats[ STAT_STAMINA ] : - cg.predictedPlayerState.stats[ STAT_STAMINA ] ) / 200.0; + float deltaBreath = ( cg.predictedPlayerState.stats[ STAT_STAMINA ] - + STAMINA_BREATHING_LEVEL ) / -250.0; float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath; deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5; @@ -643,11 +776,7 @@ static void CG_OffsetFirstPersonView( void ) //=================================== // add view height - // when wall climbing the viewheight is not straight up - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorMA( origin, ps->viewheight, normal, origin ); - else - origin[ 2 ] += cg.predictedPlayerState.viewheight; + VectorMA( origin, ps->viewheight, normal, origin ); // smooth out duck height changes timeDelta = cg.time - cg.duckTime; @@ -663,12 +792,7 @@ static void CG_OffsetFirstPersonView( void ) if( bob > 6 ) bob = 6; - // likewise for bob - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorMA( origin, bob, normal, origin ); - else - origin[ 2 ] += bob; - + VectorMA( origin, bob, normal, origin ); // add fall height delta = cg.time - cg.landTime; @@ -687,10 +811,6 @@ static void CG_OffsetFirstPersonView( void ) // add step offset CG_StepOffset( ); - - // add kick offset - - VectorAdd (origin, cg.kick_origin, origin); } //====================================================================== @@ -702,14 +822,17 @@ CG_CalcFov Fixed fov at intermissions, otherwise account for fov variable and zooms. ==================== */ -#define WAVE_AMPLITUDE 1 -#define WAVE_FREQUENCY 0.4 +#define WAVE_AMPLITUDE 1.0f +#define WAVE_FREQUENCY 0.4f -#define FOVWARPTIME 400.0 +#define FOVWARPTIME 400.0f +#define BASE_FOV_Y 73.739792f // atan2( 3, 4 / tan( 90 ) ) +#define MAX_FOV_Y 120.0f +#define MAX_FOV_WARP_Y 127.5f static int CG_CalcFov( void ) { - float x; + float y; float phase; float v; int contents; @@ -719,95 +842,114 @@ static int CG_CalcFov( void ) int inwater; int attribFov; usercmd_t cmd; + usercmd_t oldcmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber( ); trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldcmd ); + + // switch follow modes if necessary: cycle between free -> follow -> third-person follow + if( cmd.buttons & BUTTON_USE_HOLDABLE && !( oldcmd.buttons & BUTTON_USE_HOLDABLE ) ) + { + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + if( !cg.chaseFollow ) + cg.chaseFollow = qtrue; + else + { + cg.chaseFollow = qfalse; + trap_SendClientCommand( "follow\n" ); + } + } + else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + trap_SendClientCommand( "follow\n" ); + } if( cg.predictedPlayerState.pm_type == PM_INTERMISSION || - ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) ) + ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || + ( cg.renderingThirdPerson ) ) { - // if in intermission, use a fixed value - fov_x = 90; + // if in intermission or third person, use a fixed value + fov_y = BASE_FOV_Y; } else { // don't lock the fov globally - we need to be able to change it - attribFov = BG_FindFovForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); - fov_x = attribFov; + attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov * 0.75f; + fov_y = attribFov; - if ( fov_x < 1 ) - fov_x = 1; - else if ( fov_x > 160 ) - fov_x = 160; + if ( fov_y < 1.0f ) + fov_y = 1.0f; + else if ( fov_y > MAX_FOV_Y ) + fov_y = MAX_FOV_Y; if( cg.spawnTime > ( cg.time - FOVWARPTIME ) && - BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_FOVWARPS ) ) + BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) { - float temp, temp2; - - temp = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; - temp2 = ( 170 - fov_x ) * temp; - - //Com_Printf( "%f %f\n", temp*100, temp2*100 ); + float fraction = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; - fov_x = 170 - temp2; + fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); } // account for zooms - zoomFov = BG_FindZoomFovForWeapon( cg.predictedPlayerState.weapon ); - if ( zoomFov < 1 ) - zoomFov = 1; + zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; + if ( zoomFov < 1.0f ) + zoomFov = 1.0f; else if ( zoomFov > attribFov ) zoomFov = attribFov; // only do all the zoom stuff if the client CAN zoom // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2 - if( BG_WeaponCanZoom( cg.predictedPlayerState.weapon ) ) + if( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) { if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f > 1.0 ) - fov_x = zoomFov; + if ( f > 1.0f ) + fov_y = zoomFov; else - fov_x = fov_x + f * ( zoomFov - fov_x ); + fov_y = fov_y + f * ( zoomFov - fov_y ); // BUTTON_ATTACK2 isn't held so unzoom next time if( !( cmd.buttons & BUTTON_ATTACK2 ) ) { cg.zoomed = qfalse; - cg.zoomTime = cg.time; + cg.zoomTime = MIN( cg.time, + cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } else { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f <= 1.0 ) - fov_x = zoomFov + f * ( fov_x - zoomFov ); + if ( f > 1.0f ) + fov_y = fov_y; + else + fov_y = zoomFov + f * ( fov_y - zoomFov ); // BUTTON_ATTACK2 is held so zoom next time if( cmd.buttons & BUTTON_ATTACK2 ) { cg.zoomed = qtrue; - cg.zoomTime = cg.time; + cg.zoomTime = MIN( cg.time, + cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } } } - x = cg.refdef.width / tan( fov_x / 360 * M_PI ); - fov_y = atan2( cg.refdef.height, x ); - fov_y = fov_y * 360 / M_PI; + y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); + fov_x = atan2( cg.refdef.width, y ); + fov_x = 2.0f * RAD2DEG( fov_x ); // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; @@ -816,13 +958,16 @@ static int CG_CalcFov( void ) else inwater = qfalse; - if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED && + if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && + ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { - phase = cg.time / 1000.0 * PCLOUD_ZOOM_FREQUENCY * M_PI * 2; - v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ); - v *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME ); + float scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / + BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); + + phase = ( cg.time - cg.poisonedTime ) / 1000.0f * PCLOUD_ZOOM_FREQUENCY * M_PI * 2.0f; + v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ) * scale; fov_x += v; fov_y += v; } @@ -833,9 +978,9 @@ static int CG_CalcFov( void ) cg.refdef.fov_y = fov_y; if( !cg.zoomed ) - cg.zoomSensitivity = 1; + cg.zoomSensitivity = 1.0f; else - cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; return inwater; } @@ -941,10 +1086,7 @@ static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t o } //set surfNormal - if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) - VectorCopy( ps->grapplePoint, surfNormal ); - else - VectorCopy( ceilingNormal, surfNormal ); + BG_GetClientNormal( ps, surfNormal ); AnglesToAxis( in, inAxis ); @@ -952,7 +1094,6 @@ static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t o if( !VectorCompare( surfNormal, cg.lastNormal ) ) { //if we moving from the ceiling to the floor special case - //( x product of colinear vectors is undefined) if( VectorCompare( ceilingNormal, cg.lastNormal ) && VectorCompare( refNormal, surfNormal ) ) { @@ -1091,7 +1232,8 @@ static int CG_CalcViewValues( void ) ps = &cg.predictedPlayerState; // intermission view - if( ps->pm_type == PM_INTERMISSION ) + if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_FREEZE || + ps->pm_type == PM_SPECTATOR ) { VectorCopy( ps->origin, cg.refdef.vieworg ); VectorCopy( ps->viewangles, cg.refdefViewAngles ); @@ -1105,18 +1247,22 @@ static int CG_CalcViewValues( void ) cg.xyspeed = sqrt( ps->velocity[ 0 ] * ps->velocity[ 0 ] + ps->velocity[ 1 ] * ps->velocity[ 1 ] ); + // the bob velocity should't get too fast to avoid jerking + if( cg.xyspeed > 300.0f ) + cg.xyspeed = 300.0f; + VectorCopy( ps->origin, cg.refdef.vieworg ); - if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) + if( BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) ) CG_smoothWWTransitions( ps, ps->viewangles, cg.refdefViewAngles ); - else if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + else if( BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) CG_smoothWJTransitions( ps, ps->viewangles, cg.refdefViewAngles ); else VectorCopy( ps->viewangles, cg.refdefViewAngles ); //clumsy logic, but it needs to be this way round because the CS propogation //delay screws things up otherwise - if( !BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + if( !BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) { if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) VectorSet( cg.lastNormal, 0.0f, 0.0f, 1.0f ); @@ -1143,6 +1289,8 @@ static int CG_CalcViewValues( void ) if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) CG_DestroyParticleSystem( &cg.poisonCloudPS ); } + else + cg.wasDeadLastFrame = qfalse; if( cg.renderingThirdPerson ) { @@ -1155,7 +1303,7 @@ static int CG_CalcViewValues( void ) CG_OffsetFirstPersonView( ); } - // position eye reletive to origin + // position eye relative to origin AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); if( cg.hyperspace ) @@ -1260,7 +1408,11 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo CG_PredictPlayerState( ); // decide on third person view - cg.renderingThirdPerson = cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ); + cg.renderingThirdPerson = ( cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) || + ( cg.chaseFollow && cg.snap->ps.pm_flags & PMF_FOLLOW) ); + + // update speedometer + CG_AddSpeed( ); // build cg.refdef inwater = CG_CalcViewValues( ); @@ -1335,4 +1487,3 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo if( cg_stats.integer ) CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); } - diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c index 9aa9f9d..fc88606 100644 --- a/src/cgame/cg_weapons.c +++ b/src/cgame/cg_weapons.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cg_weapons.c -- events and effects dealing with weapons - #include "cg_local.h" /* @@ -38,26 +38,32 @@ void CG_RegisterUpgrade( int upgradeNum ) upgradeInfo_t *upgradeInfo; char *icon; - upgradeInfo = &cg_upgrades[ upgradeNum ]; - - if( upgradeNum == 0 ) + if( upgradeNum <= UP_NONE || upgradeNum >= UP_NUM_UPGRADES ) + { + CG_Error( "CG_RegisterUpgrade: out of range: %d", upgradeNum ); return; + } + + upgradeInfo = &cg_upgrades[ upgradeNum ]; if( upgradeInfo->registered ) + { + CG_Printf( "CG_RegisterUpgrade: already registered: (%d) %s\n", upgradeNum, + BG_Upgrade( upgradeNum )->name ); return; + } - memset( upgradeInfo, 0, sizeof( *upgradeInfo ) ); upgradeInfo->registered = qtrue; - if( !BG_FindNameForUpgrade( upgradeNum ) ) + if( !BG_Upgrade( upgradeNum )->name[ 0 ] ) CG_Error( "Couldn't find upgrade %i", upgradeNum ); - upgradeInfo->humanName = BG_FindHumanNameForUpgrade( upgradeNum ); + upgradeInfo->humanName = BG_Upgrade( upgradeNum )->humanName; //la la la la la, i'm not listening! if( upgradeNum == UP_GRENADE ) upgradeInfo->upgradeIcon = cg_weapons[ WP_GRENADE ].weaponIcon; - else if( ( icon = BG_FindIconForUpgrade( upgradeNum ) ) ) + else if( ( icon = BG_Upgrade( upgradeNum )->icon ) ) upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon ); } @@ -72,13 +78,112 @@ void CG_InitUpgrades( void ) { int i; - memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + Com_Memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) CG_RegisterUpgrade( i ); } +/* +====================== +CG_ParseWeaponAnimationFile + +Read a configuration file containing animation counts and rates +models/weapons/rifle/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseWeaponAnimationFile( const char *filename, weaponInfo_t *weapon ) +{ + char *text_p; + int len; + int i; + char *token; + float fps; + char text[ 20000 ]; + fileHandle_t f; + animation_t *animations; + + animations = weapon->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read information for each frame + for( i = WANIM_NONE + 1; i < MAX_WEAPON_ANIMATIONS; i++ ) + { + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "noDrift" ) ) + { + weapon->noDrift = qtrue; + continue; + } + + animations[ i ].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + animations[ i ].numFrames = atoi( token ); + animations[ i ].reversed = qfalse; + animations[ i ].flipflop = qfalse; + + // if numFrames is negative the animation is reversed + if( animations[ i ].numFrames < 0 ) + { + animations[ i ].numFrames = -animations[ i ].numFrames; + animations[ i ].reversed = qtrue; + } + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + fps = atof( token ); + if( fps == 0 ) + fps = 1; + + animations[ i ].frameLerp = 1000 / fps; + animations[ i ].initialLerp = 1000 / fps; + } + + if( i != MAX_WEAPON_ANIMATIONS ) + { + CG_Printf( "Error parsing animation file: %s\n", filename ); + return qfalse; + } + + return qtrue; +} + + /* =============== CG_ParseWeaponModeSection @@ -141,6 +246,16 @@ static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p continue; } + else if( !Q_stricmp( token, "missileSpriteCharge" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileSpriteCharge = atof( token ); + + continue; + } else if( !Q_stricmp( token, "missileRotates" ) ) { wim->missileRotates = qtrue; @@ -429,12 +544,13 @@ static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi ) // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len <= 0 ) + if( len < 0 ) return qfalse; - if( len >= sizeof( text ) - 1 ) + if( len == 0 || len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -517,8 +633,33 @@ static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi ) strcat( path, "_hand.md3" ); wi->handsModel = trap_R_RegisterModel( path ); - if( !wi->handsModel ) - wi->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); + continue; + } + else if( !Q_stricmp( token, "weaponModel3rdPerson" ) ) + { + char path[ MAX_QPATH ]; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->weaponModel3rdPerson = trap_R_RegisterModel( token ); + + if( !wi->weaponModel3rdPerson ) + { + CG_Printf( S_COLOR_RED "ERROR: 3rd person weapon " + "model not found %s\n", token ); + } + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_flash.md3" ); + wi->flashModel3rdPerson = trap_R_RegisterModel( path ); + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_barrel.md3" ); + wi->barrelModel3rdPerson = trap_R_RegisterModel( path ); continue; } @@ -596,35 +737,42 @@ void CG_RegisterWeapon( int weaponNum ) vec3_t mins, maxs; int i; - weaponInfo = &cg_weapons[ weaponNum ]; - - if( weaponNum == 0 ) + if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS ) + { + CG_Error( "CG_RegisterWeapon: out of range: %d", weaponNum ); return; + } + + weaponInfo = &cg_weapons[ weaponNum ]; if( weaponInfo->registered ) + { + CG_Printf( "CG_RegisterWeapon: already registered: (%d) %s\n", weaponNum, + BG_Weapon( weaponNum )->name ); return; + } - memset( weaponInfo, 0, sizeof( *weaponInfo ) ); weaponInfo->registered = qtrue; - if( !BG_FindNameForWeapon( weaponNum ) ) + if( !BG_Weapon( weaponNum )->name[ 0 ] ) CG_Error( "Couldn't find weapon %i", weaponNum ); - Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_FindNameForWeapon( weaponNum ) ); + Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_Weapon( weaponNum )->name ); - weaponInfo->humanName = BG_FindHumanNameForWeapon( weaponNum ); + weaponInfo->humanName = BG_Weapon( weaponNum )->humanName; if( !CG_ParseWeaponFile( path, weaponInfo ) ) Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path ); + Com_sprintf( path, MAX_QPATH, "models/weapons/%s/animation.cfg", BG_Weapon( weaponNum )->name ); + + if( !CG_ParseWeaponAnimationFile( path, weaponInfo ) ) + Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path ); + // calc midpoint for rotation trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); for( i = 0 ; i < 3 ; i++ ) weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] ); - - //FIXME: - for( i = WPM_NONE + 1; i < WPM_NUM_WEAPONMODES; i++ ) - weaponInfo->wim[ i ].loopFireSound = qfalse; } /* @@ -638,7 +786,7 @@ void CG_InitWeapons( void ) { int i; - memset( cg_weapons, 0, sizeof( cg_weapons ) ); + Com_Memset( cg_weapons, 0, sizeof( cg_weapons ) ); for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) CG_RegisterWeapon( i ); @@ -655,6 +803,53 @@ VIEW WEAPON ======================================================================================== */ +/* +=============== +CG_SetWeaponLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetWeaponLerpFrameAnimation( weapon_t weapon, lerpFrame_t *lf, int newAnimation ) +{ + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if( newAnimation < 0 || newAnimation >= MAX_WEAPON_ANIMATIONS ) + CG_Error( "Bad animation number: %i", newAnimation ); + + anim = &cg_weapons[ weapon ].animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if( cg_debugAnim.integer ) + CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_WeaponAnimation +=============== +*/ +static void CG_WeaponAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + lerpFrame_t *lf = ¢->pe.weapon; + entityState_t *es = ¢->currentState; + + // see if the animation sequence is switching + if( es->weaponAnim != lf->animationNumber || !lf->animation ) + CG_SetWeaponLerpFrameAnimation( es->weapon, lf, es->weaponAnim ); + + CG_RunLerpFrame( lf, 1.0f ); + + *old = lf->oldFrame; + *now = lf->frame; + *backLerp = lf->backlerp; +} + /* ================= CG_MapTorsoToWeaponFrame @@ -690,10 +885,13 @@ CG_CalculateWeaponPosition */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { - float scale; - int delta; - float fracsin; - float bob; + float scale; + int delta; + float fracsin; + float bob; + weaponInfo_t *weapon; + + weapon = &cg_weapons[ cg.predictedPlayerState.weapon ]; VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); @@ -706,7 +904,7 @@ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) // gun angles from bobbing // bob amount is class dependant - bob = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ); + bob = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; if( bob != 0 ) { @@ -716,7 +914,7 @@ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) } // drop the weapon when landing - if( !BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_NOWEAPONDRIFT ) ) + if( !weapon->noDrift ) { delta = cg.time - cg.landTime; if( delta < LAND_DEFLECT_TIME ) @@ -795,6 +993,13 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent weaponNum = cent->currentState.weapon; weaponMode = cent->currentState.generic1; + if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddPlayerWeapon: weapon " + "number %i is out of bounds", weaponNum ); + return; + } + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) weaponMode = WPM_PRIMARY; @@ -805,16 +1010,23 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent else firing = qfalse; - CG_RegisterWeapon( weaponNum ); weapon = &cg_weapons[ weaponNum ]; + if( !weapon->registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddPlayerWeapon: weapon %d (%s) " + "is not registered\n", weaponNum, BG_Weapon( weaponNum )->name ); + return; + } // add the weapon - memset( &gun, 0, sizeof( gun ) ); + Com_Memset( &gun, 0, sizeof( gun ) ); + Com_Memset( &barrel, 0, sizeof( barrel ) ); + Com_Memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); gun.shadowPlane = parent->shadowPlane; gun.renderfx = parent->renderfx; - // set custom shading for railgun refire rate if( ps ) { gun.shaderRGBA[ 0 ] = 255; @@ -842,7 +1054,15 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent } } - gun.hModel = weapon->weaponModel; + if( !ps ) + { + gun.hModel = weapon->weaponModel3rdPerson; + + if( !gun.hModel ) + gun.hModel = weapon->weaponModel; + } + else + gun.hModel = weapon->weaponModel; noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel; @@ -858,27 +1078,46 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } + // Lucifer cannon charge warning beep + if( weaponNum == WP_LUCIFER_CANNON && + ( cent->currentState.eFlags & EF_WARN_CHARGE ) && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, ps ? cgs.media.lCannonWarningSound : + cgs.media.lCannonWarningSound2 ); + } + if( !noGunModel ) { CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" ); + CG_WeaponAnimation( cent, &gun.oldframe, &gun.frame, &gun.backlerp ); trap_R_AddRefEntityToScene( &gun ); + if( !ps ) + { + barrel.hModel = weapon->barrelModel3rdPerson; + + if( !barrel.hModel ) + barrel.hModel = weapon->barrelModel; + } + else + barrel.hModel = weapon->barrelModel; + // add the spinning barrel - if( weapon->barrelModel ) + if( barrel.hModel ) { - memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; - barrel.hModel = weapon->barrelModel; angles[ YAW ] = 0; angles[ PITCH ] = 0; angles[ ROLL ] = CG_MachinegunSpinAngle( cent, firing ); AnglesToAxis( angles, barrel.axis ); - CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); + CG_PositionRotatedEntityOnTag( &barrel, &gun, gun.hModel, "tag_barrel" ); trap_R_AddRefEntityToScene( &barrel ); } @@ -892,7 +1131,7 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if( noGunModel ) CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); else - CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, gun.hModel, "tag_flash" ); } //if the PS is infinite disable it when not firing @@ -908,12 +1147,20 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent return; } - memset( &flash, 0, sizeof( flash ) ); VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); flash.shadowPlane = parent->shadowPlane; flash.renderfx = parent->renderfx; - flash.hModel = weapon->flashModel; + if( !ps ) + { + flash.hModel = weapon->flashModel3rdPerson; + + if( !flash.hModel ) + flash.hModel = weapon->flashModel; + } + else + flash.hModel = weapon->flashModel; + if( flash.hModel ) { angles[ YAW ] = 0; @@ -924,7 +1171,7 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if( noGunModel ) CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" ); else - CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" ); + CG_PositionRotatedEntityOnTag( &flash, &gun, gun.hModel, "tag_flash" ); trap_R_AddRefEntityToScene( &flash ); } @@ -941,7 +1188,7 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent if( noGunModel ) CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); else - CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, gun.hModel, "tag_flash" ); CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); CG_AttachToTag( ¢->muzzlePS->attachment ); @@ -970,6 +1217,9 @@ CG_AddViewWeapon Add the weapon, and flash for the player's view ============== */ + +#define WEAPON_CLICK_REPEAT 500 + void CG_AddViewWeapon( playerState_t *ps ) { refEntity_t hand; @@ -981,20 +1231,23 @@ void CG_AddViewWeapon( playerState_t *ps ) weapon_t weapon = ps->weapon; weaponMode_t weaponMode = ps->generic1; + // no weapon carried - can't draw it + if( weapon == WP_NONE ) + return; + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) weaponMode = WPM_PRIMARY; - CG_RegisterWeapon( weapon ); wi = &cg_weapons[ weapon ]; - cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; - - if( ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) || - ( ps->stats[ STAT_STATE ] & SS_INFESTING ) || - ( ps->stats[ STAT_STATE ] & SS_HOVELING ) ) + if( !wi->registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddViewWeapon: weapon %d (%s) " + "is not registered\n", weapon, BG_Weapon( weapon )->name ); return; + } + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; - // no weapon carried - can't draw it - if( weapon == WP_NONE ) + if( ps->persistant[PERS_SPECSTATE] != SPECTATOR_NOT ) return; if( ps->pm_type == PM_INTERMISSION ) @@ -1004,12 +1257,6 @@ void CG_AddViewWeapon( playerState_t *ps ) if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) CG_GhostBuildable( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ); - if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 ) - { - if( ps->stats[ STAT_MISC ] > ( LCANNON_TOTAL_CHARGE - ( LCANNON_TOTAL_CHARGE / 3 ) ) ) - trap_S_AddLoopingSound( ps->clientNum, ps->origin, vec3_origin, cgs.media.lCannonWarningSound ); - } - // no gun if in third person view if( cg.renderingThirdPerson ) return; @@ -1052,7 +1299,7 @@ void CG_AddViewWeapon( playerState_t *ps ) else fovOffset = 0; - memset( &hand, 0, sizeof( hand ) ); + Com_Memset( &hand, 0, sizeof( hand ) ); // set up gun position CG_CalculateWeaponPosition( hand.origin, angles ); @@ -1061,12 +1308,16 @@ void CG_AddViewWeapon( playerState_t *ps ) VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[ 1 ], hand.origin ); VectorMA( hand.origin, ( cg_gun_z.value + fovOffset ), cg.refdef.viewaxis[ 2 ], hand.origin ); + // Lucifer Cannon vibration effect if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 ) { - float fraction = (float)ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE; + float fraction; - VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], hand.origin ); - VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], hand.origin ); + fraction = (float)ps->stats[ STAT_MISC ] / LCANNON_CHARGE_TIME_MAX; + VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], + hand.origin ); + VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], + hand.origin ); } AnglesToAxis( angles, hand.axis ); @@ -1109,19 +1360,7 @@ CG_WeaponSelectable */ static qboolean CG_WeaponSelectable( weapon_t weapon ) { - //int ammo, clips; - // - //ammo = cg.snap->ps.ammo; - //clips = cg.snap->ps.clips - // - // this is a pain in the ass - //if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) ) - // return qfalse; - - if( !BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ) ) - return qfalse; - - return qtrue; + return BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ); } @@ -1135,7 +1374,7 @@ static qboolean CG_UpgradeSelectable( upgrade_t upgrade ) if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) ) return qfalse; - return BG_FindUsableForUpgrade( upgrade ); + return BG_Upgrade( upgrade )->usable; } @@ -1149,22 +1388,19 @@ CG_DrawItemSelect void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) { int i; - int x = rect->x; - int y = rect->y; - int width = rect->w; - int height = rect->h; - int iconsize; + float x = rect->x; + float y = rect->y; + float width = rect->w; + float height = rect->h; + float iconWidth; + float iconHeight; int items[ 64 ]; + int colinfo[ 64 ]; int numItems = 0, selectedItem = 0; int length; - int selectWindow; qboolean vertical; - centity_t *cent; playerState_t *ps; - - int colinfo[ 64 ]; - cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; // don't display if dead @@ -1174,109 +1410,121 @@ void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { // first make sure that whatever it selected is actually selectable - if( cg.weaponSelect <= 32 && !CG_WeaponSelectable( cg.weaponSelect ) ) - CG_NextWeapon_f( ); - else if( cg.weaponSelect > 32 && !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) - CG_NextWeapon_f( ); + if( cg.weaponSelect < 32 ) + { + if( !CG_WeaponSelectable( cg.weaponSelect ) ) + CG_NextWeapon_f( ); + } + else + { + if( !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) + CG_NextWeapon_f( ); + } } // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; - if( height > width ) - { - vertical = qtrue; - iconsize = width; - length = height / width; - } - else - { - vertical = qfalse; - iconsize = height; - length = width / height; - } - - selectWindow = length / 2; - + // put all weapons in the items list for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) ) continue; - - { - int ammo, clips; - - ammo = cg.snap->ps.ammo; - clips = cg.snap->ps.clips; - - if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) ) - colinfo[ numItems ] = 1; - else - colinfo[ numItems ] = 0; - - } + + if( !ps->ammo && !ps->clips && !BG_Weapon( i )->infiniteAmmo ) + colinfo[ numItems ] = 1; + else + colinfo[ numItems ] = 0; if( i == cg.weaponSelect ) selectedItem = numItems; - CG_RegisterWeapon( i ); + if( !cg_weapons[ i ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: weapon %d (%s) " + "is not registered\n", i, BG_Weapon( i )->name ); + continue; + } items[ numItems ] = i; numItems++; } + // put all upgrades in the weapons list for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) ) continue; colinfo[ numItems ] = 0; - if( !BG_FindUsableForUpgrade ( i ) ) + if( !BG_Upgrade( i )->usable ) colinfo[ numItems ] = 2; - if( i == cg.weaponSelect - 32 ) selectedItem = numItems; - CG_RegisterUpgrade( i ); + if( !cg_upgrades[ i ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: upgrade %d (%s) " + "is not registered\n", i, BG_Upgrade( i )->name ); + continue; + } items[ numItems ] = i + 32; numItems++; } + // compute the length of the display window and determine orientation + vertical = height > width; + if( vertical ) + { + iconWidth = width * cgDC.aspectScale; + iconHeight = width; + length = height / ( width * cgDC.aspectScale ); + } + else + { + iconWidth = height * cgDC.aspectScale; + iconHeight = height; + length = width / ( height * cgDC.aspectScale ); + } + + // render icon ring for( i = 0; i < length; i++ ) { - int displacement = i - selectWindow; - int item = displacement + selectedItem; + int item = i - length / 2 + selectedItem; - if( ( item >= 0 ) && ( item < numItems ) ) + if( item < 0 ) + item += length; + else if( item >= length ) + item -= length; + if( item >= 0 && item < numItems ) { switch( colinfo[ item ] ) { - case 0: - color = colorCyan; - break; - case 1: - color = colorRed; - break; - case 2: - color = colorGray; - break; + case 0: + color = colorCyan; + break; + case 1: + color = colorRed; + break; + case 2: + color = colorMdGrey; + break; } color[3] = 0.5; - trap_R_SetColor( color ); - if( items[ item ] <= 32 ) - CG_DrawPic( x, y, iconsize, iconsize, cg_weapons[ items[ item ] ].weaponIcon ); - else if( items[ item ] > 32 ) - CG_DrawPic( x, y, iconsize, iconsize, cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); - - trap_R_SetColor( NULL ); + if( items[ item ] < 32 ) + CG_DrawPic( x, y, iconWidth, iconHeight, + cg_weapons[ items[ item ] ].weaponIcon ); + else + CG_DrawPic( x, y, iconWidth, iconHeight, + cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); } - if( vertical ) - y += iconsize; + y += iconHeight; else - x += iconsize; + x += iconWidth; } + trap_R_SetColor( NULL ); } @@ -1298,29 +1546,29 @@ void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) trap_R_SetColor( color ); // draw the selected name - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) { if( cg_weapons[ cg.weaponSelect ].registered && BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) ) { if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) ) { - w = CG_Text_Width( name, scale, 0 ); + w = UI_Text_Width( name, scale ); x = rect->x + rect->w / 2; - CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); } } } - else if( cg.weaponSelect > 32 ) + else { if( cg_upgrades[ cg.weaponSelect - 32 ].registered && BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) ) { if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) ) { - w = CG_Text_Width( name, scale, 0 ); + w = UI_Text_Width( name, scale ); x = rect->x + rect->w / 2; - CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); } } } @@ -1357,12 +1605,12 @@ void CG_NextWeapon_f( void ) if( cg.weaponSelect == 64 ) cg.weaponSelect = 0; - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) { if( CG_WeaponSelectable( cg.weaponSelect ) ) break; } - else if( cg.weaponSelect > 32 ) + else { if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) break; @@ -1401,12 +1649,12 @@ void CG_PrevWeapon_f( void ) if( cg.weaponSelect == -1 ) cg.weaponSelect = 63; - if( cg.weaponSelect <= 32 ) + if( cg.weaponSelect < 32 ) { if( CG_WeaponSelectable( cg.weaponSelect ) ) break; } - else if( cg.weaponSelect > 32 ) + else { if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) break; @@ -1521,7 +1769,7 @@ Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ================= */ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientNum, - vec3_t origin, vec3_t dir, impactSound_t soundType ) + vec3_t origin, vec3_t dir, impactSound_t soundType, int charge ) { qhandle_t mark = 0; qhandle_t ps = 0; @@ -1579,6 +1827,7 @@ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientN CG_SetAttachmentPoint( &partSystem->attachment, origin ); CG_SetParticleSystemNormal( partSystem, dir ); CG_AttachToPoint( &partSystem->attachment ); + partSystem->charge = charge; } } @@ -1592,11 +1841,11 @@ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientN /* ================= -CG_MissileHitPlayer +CG_MissileHitEntity ================= */ -void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode, - vec3_t origin, vec3_t dir, int entityNum ) +void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode, + vec3_t origin, vec3_t dir, int entityNum, int charge ) { vec3_t normal; weaponInfo_t *weapon = &cg_weapons[ weaponNum ]; @@ -1610,7 +1859,25 @@ void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode, weaponMode = WPM_PRIMARY; if( weapon->wim[ weaponMode ].alwaysImpact ) - CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, IMPACTSOUND_FLESH ); + { + int sound; + + if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER ) + { + // Players + sound = IMPACTSOUND_FLESH; + } + else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE && + BG_Buildable( cg_entities[ entityNum ].currentState.modelindex )->team == TEAM_ALIENS ) + { + // Alien buildables + sound = IMPACTSOUND_FLESH; + } + else + sound = IMPACTSOUND_DEFAULT; + + CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, sound, charge ); + } } @@ -1772,7 +2039,7 @@ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, if( flesh ) CG_Bleed( end, normal, fleshEntityNum ); else - CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT ); + CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT, 0 ); } /* @@ -1818,12 +2085,13 @@ static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int othe if( !( tr.surfaceFlags & SURF_NOIMPACT ) ) { - if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER ) - CG_MissileHitPlayer( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum ); + if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER || + cg_entities[ tr.entityNum ].currentState.eType == ET_BUILDABLE ) + CG_MissileHitEntity( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum, 0 ); else if( tr.surfaceFlags & SURF_METALSTEPS ) - CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); + CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0 ); else - CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); + CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT, 0 ); } } } @@ -1845,3 +2113,54 @@ void CG_ShotgunFire( entityState_t *es ) CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); } +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ) +{ + team_t team; + qhandle_t bleedPS; + particleSystem_t *ps; + + if( !cg_blood.integer ) + return; + + if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER ) + { + team = cgs.clientinfo[ entityNum ].team; + if( team == TEAM_ALIENS ) + bleedPS = cgs.media.alienBleedPS; + else if( team == TEAM_HUMANS ) + bleedPS = cgs.media.humanBleedPS; + else + return; + } + else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE ) + { + //ew + team = BG_Buildable( cg_entities[ entityNum ].currentState.modelindex )->team; + if( team == TEAM_ALIENS ) + bleedPS = cgs.media.alienBuildableBleedPS; + else if( team == TEAM_HUMANS ) + bleedPS = cgs.media.humanBuildableBleedPS; + else + return; + } + else + return; + + ps = CG_SpawnNewParticleSystem( bleedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] ); + CG_AttachToPoint( &ps->attachment ); + + CG_SetParticleSystemNormal( ps, normal ); + } +} diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt new file mode 100644 index 0000000..c10fd9c --- /dev/null +++ b/src/client/CMakeLists.txt @@ -0,0 +1,209 @@ +# +## .o88b. db d888888b d88888b d8b db d888888b +## d8P Y8 88 `88' 88' 888o 88 `~~88~~' +## 8P 88 88 88ooooo 88V8o 88 88 +## 8b 88 88 88~~~~~ 88 V8o88 88 +## Y8b d8 88booo. .88. 88. 88 V888 88 +## `Y88P' Y88888P Y888888P Y88888P VP V8P YP +# + +find_package(CURL) +find_package(OpenGL) +find_package(OpenAL) +include(${CMAKE_SOURCE_DIR}/cmake/SDL2.cmake) + +add_definitions( + -DUSE_LOCAL_HEADERS + -DUSE_RESTCLIENT + -DUSE_CODEC_OPUS + -DUSE_OPENAL + -DUSE_OPENAL_DLOPEN + -DUSE_INTERNAL_JPEG + -DUSE_RENDERER_DLOPEN + -DNDEBUG + -DPRODUCT_VERSION="1.3.0 alpha" + ${SDL2_DEFINES} + ) + +set(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) +set(EXTERNAL_DIR ${CMAKE_SOURCE_DIR}/external) +if(APPLE) +set(APPLE_SOURCES ${PARENT_DIR}/sys/sys_osx.mm) +endif(APPLE) + +add_executable( + tremulous + # + ${PARENT_DIR}/asm/snapvector.c + # + cl_avi.cpp + cl_cgame.cpp + cl_cin.cpp + cl_console.cpp + cl_curl.cpp + cl_input.cpp + cl_keys.cpp + cl_main.cpp + cl_net_chan.cpp + cl_parse.cpp + cl_rest.cpp + cl_scrn.cpp + cl_ui.cpp + cl_updates.cpp + libmumblelink.cpp + qal.cpp + snd_adpcm.cpp + snd_codec.cpp + snd_codec_ogg.cpp + snd_codec_opus.cpp + snd_codec_wav.cpp + snd_dma.cpp + snd_main.cpp + snd_mem.cpp + snd_mix.cpp + snd_openal.cpp + snd_wavelet.cpp + # + ${PARENT_DIR}/qcommon/cm_load.cpp + ${PARENT_DIR}/qcommon/cm_patch.cpp + ${PARENT_DIR}/qcommon/cm_polylib.cpp + ${PARENT_DIR}/qcommon/cm_test.cpp + ${PARENT_DIR}/qcommon/cm_trace.cpp + ${PARENT_DIR}/qcommon/cmd.cpp + ${PARENT_DIR}/qcommon/common.cpp + ${PARENT_DIR}/qcommon/crypto.cpp + ${PARENT_DIR}/qcommon/cvar.cpp + ${PARENT_DIR}/qcommon/cvar.h + ${PARENT_DIR}/qcommon/files.cpp + ${PARENT_DIR}/qcommon/files.h + ${PARENT_DIR}/qcommon/huffman.cpp + ${PARENT_DIR}/qcommon/huffman.h + ${PARENT_DIR}/qcommon/ioapi.cpp + ${PARENT_DIR}/qcommon/md4.cpp + ${PARENT_DIR}/qcommon/md5.cpp + ${PARENT_DIR}/qcommon/msg.cpp + ${PARENT_DIR}/qcommon/msg.h + ${PARENT_DIR}/qcommon/net_chan.cpp + ${PARENT_DIR}/qcommon/net_ip.cpp + ${PARENT_DIR}/qcommon/net.h + ${PARENT_DIR}/qcommon/parse.cpp + ${PARENT_DIR}/qcommon/puff.cpp + ${PARENT_DIR}/qcommon/q_shared.c + ${PARENT_DIR}/qcommon/q3_lauxlib.cpp + ${PARENT_DIR}/qcommon/q_math.c + ${PARENT_DIR}/qcommon/unzip.cpp + ${PARENT_DIR}/qcommon/vm.cpp + ${PARENT_DIR}/qcommon/vm_interpreted.cpp + ${PARENT_DIR}/qcommon/vm_x86.cpp + # + ${PARENT_DIR}/sdl/sdl_input.cpp + ${PARENT_DIR}/sdl/sdl_snd.cpp + # + ${PARENT_DIR}/server/sv_ccmds.cpp + ${PARENT_DIR}/server/sv_client.cpp + ${PARENT_DIR}/server/sv_game.cpp + ${PARENT_DIR}/server/sv_init.cpp + ${PARENT_DIR}/server/sv_main.cpp + ${PARENT_DIR}/server/sv_net_chan.cpp + ${PARENT_DIR}/server/sv_snapshot.cpp + ${PARENT_DIR}/server/sv_world.cpp + # + ${PARENT_DIR}/sys/con_log.cpp + ${PARENT_DIR}/sys/con_tty.cpp + ${PARENT_DIR}/sys/sys_main.cpp + ${PARENT_DIR}/sys/sys_unix.cpp + ${PARENT_DIR}/sys/sys_shared.h + ${APPLE_SOURCES} + + ${EXTERNAL_DIR}/semver/src/lib/semantic_version_v1.cpp + ${EXTERNAL_DIR}/semver/src/lib/semantic_version_v2.cpp + ) + +if(APPLE) + # FIXME Prefixed with "lua" to prevent cmake from doing "-l-framework Cocoa" + set(FRAMEWORKS "-framework Cocoa -framework Security -framework OpenAL -framework IOKit") +else(APPLE) + if(UNIX) + set(SYSLIBS dl rt) + endif(UNIX) +endif(APPLE) + +if(NOT USE_RENDERER_DLOPEN) + if(USE_OPENGL1) + set(RENDERER_LIBRARY renderergl1) + endif(USE_OPENGL1) + set(RENDERER_LIBRARY renderergl2) +endif(NOT USE_RENDERER_DLOPEN) + +target_link_libraries( + tremulous + # + lua + nettle + zlib + ogg + opus + opusfile + # + script_api + restclient + # + ${FRAMEWORKS} + ${CURL_LIBRARIES} + ${SDL2_LIBRARIES} + ${OPENGL_LIBRARIES} + ${OPENAL_LIBRARY} + ${SYSLIBS} + ) + +include_directories( + ${PARENT_DIR} + ${PARENT_DIR}/qcommon + ${EXTERNAL_DIR}/zlib + ${EXTERNAL_DIR}/restclient + ${EXTERNAL_DIR}/rapidjson + ${EXTERNAL_DIR}/SDL2/include + ${EXTERNAL_DIR}/jpeg-8c + ${EXTERNAL_DIR}/libogg-1.3.2/include + ${EXTERNAL_DIR}/lua-5.3.3/include + ${EXTERNAL_DIR}/sol + ${EXTERNAL_DIR}/nettle-3.3 + ${EXTERNAL_DIR}/opus-1.1.4/include + ${EXTERNAL_DIR}/opusfile-0.8/include + ${EXTERNAL_DIR}/restclient + ${EXTERNAL_DIR}/semver/src/include + ${SDL2_INCLUDE_DIRS} + ${OPENAL_INCLUDE_DIR} + ) + +# TODO: Turn this into a macro +if (USE_INTERNAL_SDL2) +add_custom_command( + TARGET tremulous + POST_BUILD COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_SOURCE_DIR}/external/SDL2/libs/Darwin/libSDL2-2.0.0.dylib ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libSDL2-2.0.0.dylib + ) +endif(USE_INTERNAL_SDL2) + +if (USE_RENDERER_DLOPEN) + add_custom_command( + TARGET tremulous + POST_BUILD COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_BINARY_DIR}/src/renderergl1/librenderergl1${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/renderer_opengl1${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + + add_custom_command( + TARGET tremulous + POST_BUILD COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_BINARY_DIR}/src/renderergl2/librenderergl2${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/renderer_opengl2${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +endif(USE_RENDERER_DLOPEN) + +add_dependencies( + tremulous + ui + game + cgame + renderergl1 + renderergl2 + ) diff --git a/src/client/cl_avi.cpp b/src/client/cl_avi.cpp new file mode 100644 index 0000000..d533e60 --- /dev/null +++ b/src/client/cl_avi.cpp @@ -0,0 +1,664 @@ +/* +=========================================================================== +Copyright (C) 2005-2009 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" +#include "snd_local.h" + +#define INDEX_FILE_EXTENSION ".index.dat" + +#define MAX_RIFF_CHUNKS 16 + +typedef struct audioFormat_s +{ + int rate; + int format; + int channels; + int bits; + + int sampleSize; + int totalBytes; +} audioFormat_t; + +typedef struct aviFileData_s +{ + bool fileOpen; + fileHandle_t f; + char fileName[ MAX_QPATH ]; + int fileSize; + int moviOffset; + int moviSize; + + fileHandle_t idxF; + int numIndices; + + int frameRate; + int framePeriod; + int width, height; + int numVideoFrames; + int maxRecordSize; + bool motionJpeg; + + bool audio; + audioFormat_t a; + int numAudioFrames; + + int chunkStack[ MAX_RIFF_CHUNKS ]; + int chunkStackTop; + + byte *cBuffer, *eBuffer; +} aviFileData_t; + +static aviFileData_t afd; + +#define MAX_AVI_BUFFER 2048 + +static byte buffer[ MAX_AVI_BUFFER ]; +static int bufIndex; + +/* +=============== +SafeFS_Write +=============== +*/ +static ID_INLINE void SafeFS_Write( const void *buffer, int len, fileHandle_t f ) +{ + if( FS_Write( buffer, len, f ) < len ) + Com_Error( ERR_DROP, "Failed to write avi file" ); +} + +/* +=============== +WRITE_STRING +=============== +*/ +static ID_INLINE void WRITE_STRING( const char *s ) +{ + ::memcpy( &buffer[ bufIndex ], s, strlen( s ) ); + bufIndex += strlen( s ); +} + +/* +=============== +WRITE_4BYTES +=============== +*/ +static ID_INLINE void WRITE_4BYTES( int x ) +{ + buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF ); + buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF ); + buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF ); + buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF ); + bufIndex += 4; +} + +/* +=============== +WRITE_2BYTES +=============== +*/ +static ID_INLINE void WRITE_2BYTES( int x ) +{ + buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF ); + buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF ); + bufIndex += 2; +} + +/* +=============== +START_CHUNK +=============== +*/ +static ID_INLINE void START_CHUNK( const char *s ) +{ + if( afd.chunkStackTop == MAX_RIFF_CHUNKS ) + { + Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached" ); + } + + afd.chunkStack[ afd.chunkStackTop ] = bufIndex; + afd.chunkStackTop++; + WRITE_STRING( s ); + WRITE_4BYTES( 0 ); +} + +/* +=============== +END_CHUNK +=============== +*/ +static ID_INLINE void END_CHUNK( void ) +{ + int endIndex = bufIndex; + + if( afd.chunkStackTop <= 0 ) + { + Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached" ); + } + + afd.chunkStackTop--; + bufIndex = afd.chunkStack[ afd.chunkStackTop ]; + bufIndex += 4; + WRITE_4BYTES( endIndex - bufIndex - 4 ); + bufIndex = endIndex; + bufIndex = PAD( bufIndex, 2 ); +} + +/* +=============== +CL_WriteAVIHeader +=============== +*/ +void CL_WriteAVIHeader( void ) +{ + bufIndex = 0; + afd.chunkStackTop = 0; + + START_CHUNK( "RIFF" ); + { + WRITE_STRING( "AVI " ); + { + START_CHUNK( "LIST" ); + { + WRITE_STRING( "hdrl" ); + WRITE_STRING( "avih" ); + WRITE_4BYTES( 56 ); //"avih" "chunk" size + WRITE_4BYTES( afd.framePeriod ); //dwMicroSecPerFrame + WRITE_4BYTES( afd.maxRecordSize * + afd.frameRate ); //dwMaxBytesPerSec + WRITE_4BYTES( 0 ); //dwReserved1 + WRITE_4BYTES( 0x110 ); //dwFlags bits HAS_INDEX and IS_INTERLEAVED + WRITE_4BYTES( afd.numVideoFrames ); //dwTotalFrames + WRITE_4BYTES( 0 ); //dwInitialFrame + + if( afd.audio ) //dwStreams + WRITE_4BYTES( 2 ); + else + WRITE_4BYTES( 1 ); + + WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize + WRITE_4BYTES( afd.width ); //dwWidth + WRITE_4BYTES( afd.height ); //dwHeight + WRITE_4BYTES( 0 ); //dwReserved[ 0 ] + WRITE_4BYTES( 0 ); //dwReserved[ 1 ] + WRITE_4BYTES( 0 ); //dwReserved[ 2 ] + WRITE_4BYTES( 0 ); //dwReserved[ 3 ] + + START_CHUNK( "LIST" ); + { + WRITE_STRING( "strl" ); + WRITE_STRING( "strh" ); + WRITE_4BYTES( 56 ); //"strh" "chunk" size + WRITE_STRING( "vids" ); + + if( afd.motionJpeg ) + WRITE_STRING( "MJPG" ); + else + WRITE_4BYTES( 0 ); // BI_RGB + + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( 0 ); //dwPriority + WRITE_4BYTES( 0 ); //dwInitialFrame + + WRITE_4BYTES( 1 ); //dwTimescale + WRITE_4BYTES( afd.frameRate ); //dwDataRate + WRITE_4BYTES( 0 ); //dwStartTime + WRITE_4BYTES( afd.numVideoFrames ); //dwDataLength + + WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize + WRITE_4BYTES( -1 ); //dwQuality + WRITE_4BYTES( 0 ); //dwSampleSize + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( afd.width ); //rcFrame + WRITE_2BYTES( afd.height ); //rcFrame + + WRITE_STRING( "strf" ); + WRITE_4BYTES( 40 ); //"strf" "chunk" size + WRITE_4BYTES( 40 ); //biSize + WRITE_4BYTES( afd.width ); //biWidth + WRITE_4BYTES( afd.height ); //biHeight + WRITE_2BYTES( 1 ); //biPlanes + WRITE_2BYTES( 24 ); //biBitCount + + if( afd.motionJpeg ) //biCompression + { + WRITE_STRING( "MJPG" ); + WRITE_4BYTES( afd.width * + afd.height ); //biSizeImage + } + else + { + WRITE_4BYTES( 0 ); // BI_RGB + WRITE_4BYTES( afd.width * + afd.height * 3 ); //biSizeImage + } + + WRITE_4BYTES( 0 ); //biXPelsPetMeter + WRITE_4BYTES( 0 ); //biYPelsPetMeter + WRITE_4BYTES( 0 ); //biClrUsed + WRITE_4BYTES( 0 ); //biClrImportant + } + END_CHUNK( ); + + if( afd.audio ) + { + START_CHUNK( "LIST" ); + { + WRITE_STRING( "strl" ); + WRITE_STRING( "strh" ); + WRITE_4BYTES( 56 ); //"strh" "chunk" size + WRITE_STRING( "auds" ); + WRITE_4BYTES( 0 ); //FCC + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( 0 ); //dwPriority + WRITE_4BYTES( 0 ); //dwInitialFrame + + WRITE_4BYTES( afd.a.sampleSize ); //dwTimescale + WRITE_4BYTES( afd.a.sampleSize * + afd.a.rate ); //dwDataRate + WRITE_4BYTES( 0 ); //dwStartTime + WRITE_4BYTES( afd.a.totalBytes / + afd.a.sampleSize ); //dwDataLength + + WRITE_4BYTES( 0 ); //dwSuggestedBufferSize + WRITE_4BYTES( -1 ); //dwQuality + WRITE_4BYTES( afd.a.sampleSize ); //dwSampleSize + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + + WRITE_STRING( "strf" ); + WRITE_4BYTES( 18 ); //"strf" "chunk" size + WRITE_2BYTES( afd.a.format ); //wFormatTag + WRITE_2BYTES( afd.a.channels ); //nChannels + WRITE_4BYTES( afd.a.rate ); //nSamplesPerSec + WRITE_4BYTES( afd.a.sampleSize * + afd.a.rate ); //nAvgBytesPerSec + WRITE_2BYTES( afd.a.sampleSize ); //nBlockAlign + WRITE_2BYTES( afd.a.bits ); //wBitsPerSample + WRITE_2BYTES( 0 ); //cbSize + } + END_CHUNK( ); + } + } + END_CHUNK( ); + + afd.moviOffset = bufIndex; + + START_CHUNK( "LIST" ); + { + WRITE_STRING( "movi" ); + } + } + } +} + +/* +=============== +CL_OpenAVIForWriting + +Creates an AVI file and gets it into a state where +writing the actual data can begin +=============== +*/ +bool CL_OpenAVIForWriting( const char *fileName ) +{ + if( afd.fileOpen ) + return false; + + ::memset( &afd, 0, sizeof( aviFileData_t ) ); + + // Don't start if a framerate has not been chosen + if( cl_aviFrameRate->integer <= 0 ) + { + Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" ); + return false; + } + + if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 ) + return false; + + if( ( afd.idxF = FS_FOpenFileWrite( + va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 ) + { + FS_FCloseFile( afd.f ); + return false; + } + + Q_strncpyz( afd.fileName, fileName, MAX_QPATH ); + + afd.frameRate = cl_aviFrameRate->integer; + afd.framePeriod = (int)( 1000000.0f / afd.frameRate ); + afd.width = cls.glconfig.vidWidth; + afd.height = cls.glconfig.vidHeight; + + if( cl_aviMotionJpeg->integer ) + afd.motionJpeg = true; + else + afd.motionJpeg = false; + + // Buffers only need to store RGB pixels. + // Allocate a bit more space for the capture buffer to account for possible + // padding at the end of pixel lines, and padding for alignment + #define MAX_PACK_LEN 16 + afd.cBuffer = (byte*)Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); + // raw avi files have pixel lines start on 4-byte boundaries + afd.eBuffer = (byte*)Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height); + + afd.a.rate = dma.speed; + afd.a.format = WAV_FORMAT_PCM; + afd.a.channels = dma.channels; + afd.a.bits = dma.samplebits; + afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels; + + if( afd.a.rate % afd.frameRate ) + { + int suggestRate = afd.frameRate; + + while( ( afd.a.rate % suggestRate ) && suggestRate >= 1 ) + suggestRate--; + + Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor " + "of the audio rate, suggest %d\n", suggestRate ); + } + + if( !Cvar_VariableIntegerValue( "s_initsound" ) ) + { + afd.audio = false; + } + else if( Q_stricmp( Cvar_VariableString( "s_backend" ), "OpenAL" ) ) + { + if( afd.a.bits != 16 || afd.a.channels != 2 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: Audio format of %d bit/%d channels not supported", + afd.a.bits, afd.a.channels ); + afd.audio = false; + } + else + afd.audio = true; + } + else + { + afd.audio = false; + Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture is not supported " + "with OpenAL. Set s_useOpenAL to 0 for audio capture\n" ); + } + + // This doesn't write a real header, but allocates the + // correct amount of space at the beginning of the file + CL_WriteAVIHeader( ); + + SafeFS_Write( buffer, bufIndex, afd.f ); + afd.fileSize = bufIndex; + + bufIndex = 0; + START_CHUNK( "idx1" ); + SafeFS_Write( buffer, bufIndex, afd.idxF ); + + afd.moviSize = 4; // For the "movi" + afd.fileOpen = true; + + return true; +} + +/* +=============== +CL_CheckFileSize +=============== +*/ +static bool CL_CheckFileSize( int bytesToAdd ) +{ + unsigned int newFileSize; + + newFileSize = + afd.fileSize + // Current file size + bytesToAdd + // What we want to add + ( afd.numIndices * 16 ) + // The index + 4; // The index size + + // I assume all the operating systems + // we target can handle a 2Gb file + if( newFileSize > INT_MAX ) + { + // Close the current file... + CL_CloseAVI( ); + + // ...And open a new one + CL_OpenAVIForWriting( va( "%s_", afd.fileName ) ); + + return true; + } + + return false; +} + +/* +=============== +CL_WriteAVIVideoFrame +=============== +*/ +void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size ) +{ + int chunkOffset = afd.fileSize - afd.moviOffset - 8; + int chunkSize = 8 + size; + int paddingSize = PADLEN(size, 2); + byte padding[ 4 ] = { 0 }; + + if( !afd.fileOpen ) + return; + + // Chunk header + contents + padding + if( CL_CheckFileSize( 8 + size + 2 ) ) + return; + + bufIndex = 0; + WRITE_STRING( "00dc" ); + WRITE_4BYTES( size ); + + SafeFS_Write( buffer, 8, afd.f ); + SafeFS_Write( imageBuffer, size, afd.f ); + SafeFS_Write( padding, paddingSize, afd.f ); + afd.fileSize += ( chunkSize + paddingSize ); + + afd.numVideoFrames++; + afd.moviSize += ( chunkSize + paddingSize ); + + if( size > afd.maxRecordSize ) + afd.maxRecordSize = size; + + // Index + bufIndex = 0; + WRITE_STRING( "00dc" ); //dwIdentifier + WRITE_4BYTES( 0x00000010 ); //dwFlags (all frames are KeyFrames) + WRITE_4BYTES( chunkOffset ); //dwOffset + WRITE_4BYTES( size ); //dwLength + SafeFS_Write( buffer, 16, afd.idxF ); + + afd.numIndices++; +} + +#define PCM_BUFFER_SIZE 44100 + +/* +=============== +CL_WriteAVIAudioFrame +=============== +*/ +void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size ) +{ + static byte pcmCaptureBuffer[ PCM_BUFFER_SIZE ] = { 0 }; + static int bytesInBuffer = 0; + + if( !afd.audio ) + return; + + if( !afd.fileOpen ) + return; + + // Chunk header + contents + padding + if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) ) + return; + + if( bytesInBuffer + size > PCM_BUFFER_SIZE ) + { + Com_Printf( S_COLOR_YELLOW + "WARNING: Audio capture buffer overflow -- truncating\n" ); + size = PCM_BUFFER_SIZE - bytesInBuffer; + } + + ::memcpy( &pcmCaptureBuffer[ bytesInBuffer ], pcmBuffer, size ); + bytesInBuffer += size; + + // Only write if we have a frame's worth of audio + if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) * + afd.a.sampleSize ) + { + int chunkOffset = afd.fileSize - afd.moviOffset - 8; + int chunkSize = 8 + bytesInBuffer; + int paddingSize = PADLEN(bytesInBuffer, 2); + byte padding[ 4 ] = { 0 }; + + bufIndex = 0; + WRITE_STRING( "01wb" ); + WRITE_4BYTES( bytesInBuffer ); + + SafeFS_Write( buffer, 8, afd.f ); + SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f ); + SafeFS_Write( padding, paddingSize, afd.f ); + afd.fileSize += ( chunkSize + paddingSize ); + + afd.numAudioFrames++; + afd.moviSize += ( chunkSize + paddingSize ); + afd.a.totalBytes += bytesInBuffer; + + // Index + bufIndex = 0; + WRITE_STRING( "01wb" ); //dwIdentifier + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( chunkOffset ); //dwOffset + WRITE_4BYTES( bytesInBuffer ); //dwLength + SafeFS_Write( buffer, 16, afd.idxF ); + + afd.numIndices++; + + bytesInBuffer = 0; + } +} + +/* +=============== +CL_TakeVideoFrame +=============== +*/ +void CL_TakeVideoFrame( void ) +{ + // AVI file isn't open + if( !afd.fileOpen ) + return; + + re.TakeVideoFrame( afd.width, afd.height, afd.cBuffer, afd.eBuffer, afd.motionJpeg ); +} + +/* +=============== +CL_CloseAVI + +Closes the AVI file and writes an index chunk +=============== +*/ +bool CL_CloseAVI( void ) +{ + int indexRemainder; + int indexSize = afd.numIndices * 16; + const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName ); + + // AVI file isn't open + if( !afd.fileOpen ) + return false; + + afd.fileOpen = false; + + FS_Seek( afd.idxF, 4, FS_SEEK_SET ); + bufIndex = 0; + WRITE_4BYTES( indexSize ); + SafeFS_Write( buffer, bufIndex, afd.idxF ); + FS_FCloseFile( afd.idxF ); + + // Write index + + // Open the temp index file + if( ( indexSize = FS_FOpenFileRead( idxFileName, &afd.idxF, true ) ) <= 0 ) + { + FS_FCloseFile( afd.f ); + return false; + } + + indexRemainder = indexSize; + + // Append index to end of avi file + while( indexRemainder > MAX_AVI_BUFFER ) + { + FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF ); + SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f ); + afd.fileSize += MAX_AVI_BUFFER; + indexRemainder -= MAX_AVI_BUFFER; + } + FS_Read( buffer, indexRemainder, afd.idxF ); + SafeFS_Write( buffer, indexRemainder, afd.f ); + afd.fileSize += indexRemainder; + FS_FCloseFile( afd.idxF ); + + // Remove temp index file + FS_HomeRemove( idxFileName ); + + // Write the real header + FS_Seek( afd.f, 0, FS_SEEK_SET ); + CL_WriteAVIHeader( ); + + bufIndex = 4; + WRITE_4BYTES( afd.fileSize - 8 ); // "RIFF" size + + bufIndex = afd.moviOffset + 4; // Skip "LIST" + WRITE_4BYTES( afd.moviSize ); + + SafeFS_Write( buffer, bufIndex, afd.f ); + + Z_Free( afd.cBuffer ); + Z_Free( afd.eBuffer ); + FS_FCloseFile( afd.f ); + + Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName ); + + return true; +} + +/* +=============== +CL_VideoRecording +=============== +*/ +bool CL_VideoRecording( void ) +{ + return afd.fileOpen; +} diff --git a/src/client/cl_cgame.cpp b/src/client/cl_cgame.cpp new file mode 100644 index 0000000..f16dc35 --- /dev/null +++ b/src/client/cl_cgame.cpp @@ -0,0 +1,1172 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// cl_cgame.c -- client system interaction with client game + +#include "client.h" + +#ifdef USE_MUMBLE +#include "libmumblelink.h" +#endif +#include "snd_public.h" + +/* +==================== +CL_GetGameState +==================== +*/ +static void CL_GetGameState( gameState_t *gs ) +{ + *gs = cl.gameState; +} + +/* +==================== +CL_GetGlconfig +==================== +*/ +static void CL_GetGlconfig( glconfig_t *glconfig ) +{ + *glconfig = cls.glconfig; +} + + +/* +==================== +CL_GetUserCmd +==================== +*/ +static bool CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) +{ + // cmds[cmdNumber] is the last properly generated command + + // can't return anything that we haven't created yet + if ( cmdNumber > cl.cmdNumber ) + Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); + + // the usercmd has been overwritten in the wrapping + // buffer because it is too far out of date + if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) + return false; + + *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; + + return true; +} + +static int CL_GetCurrentCmdNumber( void ) +{ + return cl.cmdNumber; +} + +/* +==================== +CL_GetCurrentSnapshotNumber +==================== +*/ +static void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) +{ + *snapshotNumber = cl.snap.messageNum; + *serverTime = cl.snap.serverTime; +} + +/* +==================== +CL_GetSnapshot +==================== +*/ +bool CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) +{ + clSnapshot_t *clSnap; + + if ( snapshotNumber > cl.snap.messageNum ) { + Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); + } + + // if the frame has fallen out of the circular buffer, we can't return it + if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) + return false; + + // if the frame is not valid, we can't return it + clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; + if ( !clSnap->valid ) + return false; + + // if the entities in the frame have fallen out of their circular buffer, + // we can't return it + if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) + return false; + + // write the snapshot + snapshot->snapFlags = clSnap->snapFlags; + snapshot->ping = clSnap->ping; + snapshot->serverTime = clSnap->serverTime; + ::memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); + + int count = clSnap->numEntities; + if ( count > MAX_ENTITIES_IN_SNAPSHOT ) + { + Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); + count = MAX_ENTITIES_IN_SNAPSHOT; + } + + if ( cls.cgInterface == 2 ) + { + alternateSnapshot_t *altSnapshot = (alternateSnapshot_t *)snapshot; + altSnapshot->ps = clSnap->alternatePs; + altSnapshot->serverCommandSequence = clSnap->serverCommandNum; + altSnapshot->numEntities = count; + + for ( int i = 0 ; i < count ; i++ ) + { + entityState_t *es = &cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; + ::memcpy( &altSnapshot->entities[i], es, (size_t)&((entityState_t *)0)->weaponAnim ); + altSnapshot->entities[i].generic1 = es->generic1; + } + } + else + { + snapshot->ps = clSnap->ps; + snapshot->serverCommandSequence = clSnap->serverCommandNum; + snapshot->numEntities = count; + for ( int i = 0 ; i < count ; i++ ) + { + snapshot->entities[i] = + cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; + } + } + + // FIXME: configstring changes and server commands!!! + + return true; +} + +/* +===================== +CL_SetUserCmdValue +===================== +*/ +void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) { + cl.cgameUserCmdValue = userCmdValue; + cl.cgameSensitivity = sensitivityScale; +} + +/* +===================== +CL_AddCgameCommand +===================== +*/ +void CL_AddCgameCommand( const char *cmdName ) { + Cmd_AddCommand( cmdName, NULL ); +} + +/* +===================== +CL_ConfigstringModified +===================== +*/ +void CL_ConfigstringModified( void ) +{ + int idx = atoi( Cmd_Argv(1) ); + if ( idx < 0 || idx >= MAX_CONFIGSTRINGS ) + Com_Error( ERR_DROP, "CL_ConfigstringModified: bad index %i", idx ); + + // get everything after "cs " + const char* s = Cmd_ArgsFrom(2); + const char* old = cl.gameState.stringData + cl.gameState.stringOffsets[ idx ]; + if ( !strcmp(old, s) ) + return; + + // build the new gameState_t + gameState_t oldGs = cl.gameState; + + ::memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + + // leave the first 0 for uninitialized strings + cl.gameState.dataCount = 1; + + const char* dup; + for ( int i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) + { + if ( i == idx ) + dup = s; + else + dup = oldGs.stringData + oldGs.stringOffsets[ i ]; + + if ( !dup[0] ) + continue; // leave with the default empty string + + int len = strlen(dup); + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) + Com_Error(ERR_DROP, "MAX_GAMESTATE_CHARS exceeded"); + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + ::memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); + cl.gameState.dataCount += len + 1; + } + + if ( idx == CS_SYSTEMINFO ) + { + // parse serverId and other cvars + CL_SystemInfoChanged(); + } +} + + +/* +=================== +CL_GetServerCommand + +Set up argc/argv for the given command +=================== +*/ +static bool CL_GetServerCommand( int serverCommandNumber ) +{ + const char *s; + const char *cmd; + static char bigConfigString[BIG_INFO_STRING]; + int argc; + + // if we have irretrievably lost a reliable command, drop the connection + if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) + { + // when a demo record was started after the client got a whole bunch of + // reliable commands then the client never got those first reliable commands + if ( clc.demoplaying ) + return false; + + Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); + return false; + } + + if ( serverCommandNumber > clc.serverCommandSequence ) + { + Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); + return false; + } + + s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + clc.lastExecutedServerCommand = serverCommandNumber; + + Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); + +rescan: + Cmd_TokenizeString( s ); + cmd = Cmd_Argv(0); + argc = Cmd_Argc(); + + if ( !strcmp( cmd, "disconnect" ) ) + { + // allow server to indicate why they were disconnected + if ( argc >= 2 ) + Com_Error( ERR_SERVERDISCONNECT, "Server disconnected - %s", Cmd_Argv( 1 ) ); + else + Com_Error( ERR_SERVERDISCONNECT, "Server disconnected" ); + } + + if ( !strcmp( cmd, "bcs0" ) ) + { + Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); + return false; + } + + if ( !strcmp( cmd, "bcs1" ) ) + { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + + strcat( bigConfigString, s ); + return false; + } + + if ( !strcmp( cmd, "bcs2" ) ) + { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + + strcat( bigConfigString, s ); + strcat( bigConfigString, "\"" ); + s = bigConfigString; + goto rescan; + } + + if ( !strcmp( cmd, "cs" ) ) + { + CL_ConfigstringModified(); + // reparse the string, because CL_ConfigstringModified may have done + // another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + return true; + } + + if ( !strcmp( cmd, "map_restart" ) ) + { + // clear notify lines and outgoing commands before passing + // the restart to the cgame + Con_ClearNotify(); + // reparse the string, because Con_ClearNotify() may have done another + // Cmd_TokenizeString() + Cmd_TokenizeString( s ); + ::memset( cl.cmds, 0, sizeof( cl.cmds ) ); + return true; + } + + // we may want to put a "connect to other server" command here + + // cgame can now act on the command + return true; +} + + +/* +==================== +CL_CM_LoadMap + +Just adds default parameters that cgame doesn't need to know about +==================== +*/ +void CL_CM_LoadMap( const char *mapname ) { + int checksum; + + CM_LoadMap( mapname, true, &checksum ); +} + +char * safe_strncpy(char *dest, const char *src, size_t n) +{ + char *ret = dest; + while (n > 0 && src[0]) + { + *ret++ = *src++; + --n; + } + while (n > 0) + { + *ret++ = '\0'; + --n; + } + return dest; +} + +/* +==================== +CL_ShutdonwCGame + +==================== +*/ +void CL_ShutdownCGame( void ) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME ); + cls.cgameStarted = false; + if ( !cls.cgame ) { + return; + } + VM_Call( cls.cgame, CG_SHUTDOWN ); + VM_Free( cls.cgame ); + cls.cgame = NULL; +} + +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +static bool probingCG = false; + +/* +==================== +CL_CgameSystemCalls + +The cgame module is making a system call +==================== +*/ +intptr_t CL_CgameSystemCalls( intptr_t *args ) +{ + if( cls.cgInterface == 2 && args[0] >= CG_R_SETCLIPREGION && args[0] < CG_MEMSET ) + { + if( args[0] < CG_S_STOPBACKGROUNDTRACK - 1 ) + args[0] += 1; + + else if( args[0] < CG_S_STOPBACKGROUNDTRACK + 4 ) + args[0] += CG_PARSE_ADD_GLOBAL_DEFINE - CG_S_STOPBACKGROUNDTRACK + 1; + + else if( args[0] < CG_PARSE_ADD_GLOBAL_DEFINE + 4 ) + args[0] -= 4; + + else if( args[0] >= CG_PARSE_SOURCE_FILE_AND_LINE && args[0] <= CG_S_SOUNDDURATION ) + args[0] = CG_PARSE_SOURCE_FILE_AND_LINE - 1337 - args[0] ; + } + + switch( args[0] ) + { + case CG_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + case CG_ERROR: + if( probingCG ) + { + cls.cgInterface = 2; // this is a 1.1.0 cgame + return 0; + } + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + case CG_MILLISECONDS: + return Sys_Milliseconds(); + case CG_CVAR_REGISTER: + Cvar_Register( (vmCvar_t*)VMA(1), (const char*)VMA(2), (const char*)VMA(3), args[4] ); + return 0; + case CG_CVAR_UPDATE: + Cvar_Update( (vmCvar_t*)VMA(1) ); + return 0; + case CG_CVAR_SET: + Cvar_SetSafe( (const char*)VMA(1), (const char*)VMA(2) ); + return 0; + case CG_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( (const char*)VMA(1), (char*)VMA(2), args[3] ); + return 0; + case CG_ARGC: + return Cmd_Argc(); + case CG_ARGV: + Cmd_ArgvBuffer( args[1], (char*)VMA(2), args[3] ); + return 0; + case CG_ARGS: + Cmd_ArgsBuffer( (char*)VMA(1), args[2] ); + return 0; + case CG_LITERAL_ARGS: + Cmd_LiteralArgsBuffer( (char*)VMA(1), args[2] ); + return 0; + case CG_FS_FOPENFILE: + return FS_FOpenFileByMode( (const char*)VMA(1), (fileHandle_t*)VMA(2), (FS_Mode)args[3] ); + case CG_FS_READ: + FS_Read( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + case CG_FS_SEEK: + return FS_Seek( (fileHandle_t)args[1], args[2], (FS_Origin)args[3] ); + case CG_FS_GETFILELIST: + return FS_GetFileList( (const char*)VMA(1), (const char*)VMA(2), (char*)VMA(3), args[4] ); + case CG_SENDCONSOLECOMMAND: + Cbuf_AddText( (const char*)VMA(1) ); + return 0; + case CG_ADDCOMMAND: + CL_AddCgameCommand( (const char*)VMA(1) ); + return 0; + case CG_REMOVECOMMAND: + Cmd_RemoveCommandSafe( (const char*)VMA(1) ); + return 0; + case CG_SENDCLIENTCOMMAND: + CL_AddReliableCommand((const char*)VMA(1), false); + return 0; + case CG_UPDATESCREEN: + // this is used during lengthy level loading, so pump message loop + // Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! + // We can't call Com_EventLoop here, a restart will crash and this _does_ happen + // if there is a map change while we are downloading at pk3. + // ZOID + SCR_UpdateScreen(); + return 0; + case CG_CM_LOADMAP: + CL_CM_LoadMap( (const char*)VMA(1) ); + return 0; + case CG_CM_NUMINLINEMODELS: + return CM_NumInlineModels(); + case CG_CM_INLINEMODEL: + return CM_InlineModel( args[1] ); + case CG_CM_TEMPBOXMODEL: + return CM_TempBoxModel( (const float*)VMA(1), (const float*)VMA(2), false ); + case CG_CM_TEMPCAPSULEMODEL: + return CM_TempBoxModel( (const float*)VMA(1), (const float*)VMA(2), true ); + case CG_CM_POINTCONTENTS: + return CM_PointContents( (const float*)VMA(1), args[2] ); + case CG_CM_TRANSFORMEDPOINTCONTENTS: + return CM_TransformedPointContents( (const float*)VMA(1), (clipHandle_t)args[2], + (const float*)VMA(3), (const float*)VMA(4) ); + case CG_CM_BOXTRACE: + CM_BoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), + (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7], TT_AABB ); + return 0; + case CG_CM_CAPSULETRACE: + CM_BoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), + (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7], TT_CAPSULE ); + return 0; + case CG_CM_TRANSFORMEDBOXTRACE: + CM_TransformedBoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), + (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7], + (const float*)VMA(8), (const float*)VMA(9), TT_AABB ); + return 0; + case CG_CM_TRANSFORMEDCAPSULETRACE: + CM_TransformedBoxTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), + (float*)VMA(4), (float*)VMA(5), (clipHandle_t)args[6], args[7], + (const float*)VMA(8), (const float*)VMA(9), TT_CAPSULE ); + return 0; + case CG_CM_BISPHERETRACE: + CM_BiSphereTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), VMF(4), VMF(5), (clipHandle_t)args[6], args[7] ); + return 0; + case CG_CM_TRANSFORMEDBISPHERETRACE: + CM_TransformedBiSphereTrace( (trace_t*)VMA(1), (const float*)VMA(2), (const float*)VMA(3), + VMF(4), VMF(5), (clipHandle_t)args[6], args[7], (const float*)VMA(8) ); + return 0; + case CG_CM_MARKFRAGMENTS: + { + float (&arg2)[3][3] = *reinterpret_cast(VMA(2)); + return re.MarkFragments( args[1], arg2, (const float*)VMA(3), args[4], (float*)VMA(5), args[6], (markFragment_t*)VMA(7) ); + } + case CG_S_STARTSOUND: + S_StartSound( (float*)VMA(1), args[2], args[3], (sfxHandle_t)args[4] ); + return 0; + case CG_S_STARTLOCALSOUND: + S_StartLocalSound( (sfxHandle_t)args[1], args[2] ); + return 0; + case CG_S_CLEARLOOPINGSOUNDS: + S_ClearLoopingSounds( (bool)args[1] ); + return 0; + case CG_S_ADDLOOPINGSOUND: + S_AddLoopingSound( args[1], (const float*)VMA(2), (const float*)VMA(3), (sfxHandle_t)args[4] ); + return 0; + case CG_S_ADDREALLOOPINGSOUND: + S_AddRealLoopingSound( args[1], (const float*)VMA(2), (const float*)VMA(3), (sfxHandle_t)args[4] ); + return 0; + case CG_S_STOPLOOPINGSOUND: + S_StopLoopingSound( args[1] ); + return 0; + case CG_S_UPDATEENTITYPOSITION: + S_UpdateEntityPosition( args[1], (const float*)VMA(2) ); + return 0; + case CG_S_RESPATIALIZE: + { + float (&arg3)[3][3] = *reinterpret_cast(VMA(3)); + S_Respatialize( args[1], (const float*)VMA(2), arg3, args[4] ); + return 0; + } + case CG_S_REGISTERSOUND: + return S_RegisterSound( (const char*)VMA(1), (bool)args[2] ); + case CG_S_SOUNDDURATION: + return S_SoundDuration( args[1] ); + case CG_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( (const char*)VMA(1), (const char*)VMA(2) ); + return 0; + case CG_R_LOADWORLDMAP: + re.LoadWorld( (const char*)VMA(1) ); + return 0; + case CG_R_REGISTERMODEL: + return re.RegisterModel( (const char*)VMA(1) ); + case CG_R_REGISTERSKIN: + return re.RegisterSkin( (const char*)VMA(1) ); + case CG_R_REGISTERSHADER: + return re.RegisterShader( (const char*)VMA(1) ); + case CG_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( (const char*)VMA(1) ); + case CG_R_REGISTERFONT: + re.RegisterFont( (const char*)VMA(1), args[2], (fontInfo_t*)VMA(3)); + return 0; + case CG_R_CLEARSCENE: + re.ClearScene(); + return 0; + case CG_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( (const refEntity_t*)VMA(1) ); + return 0; + case CG_R_ADDPOLYTOSCENE: + re.AddPolyToScene( (qhandle_t)args[1], args[2], (const polyVert_t*)VMA(3), 1 ); + return 0; + case CG_R_ADDPOLYSTOSCENE: + re.AddPolyToScene( (qhandle_t)args[1], args[2], (const polyVert_t*)VMA(3), args[4] ); + return 0; + case CG_R_LIGHTFORPOINT: + return re.LightForPoint( (float*)VMA(1), (float*)VMA(2), (float*)VMA(3), (float*)VMA(4) ); + case CG_R_ADDLIGHTTOSCENE: + re.AddLightToScene( (const float*)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_ADDADDITIVELIGHTTOSCENE: + re.AddAdditiveLightToScene( (const float*)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_RENDERSCENE: + re.RenderScene( (const refdef_t*)VMA(1) ); + return 0; + case CG_R_SETCOLOR: + re.SetColor( (const float*)VMA(1) ); + return 0; + case CG_R_SETCLIPREGION: + re.SetClipRegion( (const float*)VMA(1) ); + return 0; + case CG_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), (qhandle_t)args[9] ); + return 0; + case CG_R_MODELBOUNDS: + re.ModelBounds( (qhandle_t)args[1], (float*)VMA(2), (float*)VMA(3) ); + return 0; + case CG_R_LERPTAG: + return re.LerpTag( (orientation_t*)VMA(1), args[2], args[3], args[4], VMF(5), (const char*)VMA(6) ); + case CG_GETGLCONFIG: + CL_GetGlconfig( (glconfig_t*)VMA(1) ); + return 0; + case CG_GETGAMESTATE: + CL_GetGameState( (gameState_t*)VMA(1) ); + return 0; + case CG_GETCURRENTSNAPSHOTNUMBER: + CL_GetCurrentSnapshotNumber( (int*)VMA(1), (int*)VMA(2) ); + return 0; + case CG_GETSNAPSHOT: + return CL_GetSnapshot( args[1], (snapshot_t*)VMA(2) ); + case CG_GETSERVERCOMMAND: + return CL_GetServerCommand( args[1] ); + case CG_GETCURRENTCMDNUMBER: + return CL_GetCurrentCmdNumber(); + case CG_GETUSERCMD: + return CL_GetUserCmd( args[1], (usercmd_t*)VMA(2) ); + case CG_SETUSERCMDVALUE: + CL_SetUserCmdValue( args[1], VMF(2) ); + return 0; + case CG_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + case CG_KEY_ISDOWN: + return Key_IsDown( args[1] ); + case CG_KEY_GETCATCHER: + return Key_GetCatcher(); + case CG_KEY_SETCATCHER: + // don't allow the cgame module to toggle the console + Key_SetCatcher( ( args[1] & ~KEYCATCH_CONSOLE ) | ( Key_GetCatcher() & KEYCATCH_CONSOLE ) ); + return 0; + case CG_KEY_GETKEY: + return Key_GetKey( (const char*)VMA(1) ); + + case CG_GETDEMOSTATE: + return CL_DemoState( ); + case CG_GETDEMOPOS: + return CL_DemoPos( ); + case CG_GETDEMONAME: + CL_DemoName( (char*)VMA(1), args[2] ); + return 0; + + case CG_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], (char*)VMA(2), args[3] ); + return 0; + case CG_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], (char*)VMA(2), args[3] ); + return 0; + case CG_KEY_SETBINDING: + Key_SetBinding( args[1], (const char*)VMA(2) ); + return 0; + + case CG_PARSE_ADD_GLOBAL_DEFINE: + return Parse_AddGlobalDefine( (char*)VMA(1) ); + case CG_PARSE_LOAD_SOURCE: + return Parse_LoadSourceHandle( (const char*)VMA(1) ); + case CG_PARSE_FREE_SOURCE: + return Parse_FreeSourceHandle( args[1] ); + case CG_PARSE_READ_TOKEN: + return Parse_ReadTokenHandle( args[1], (pc_token_t*)VMA(2) ); + case CG_PARSE_SOURCE_FILE_AND_LINE: + return Parse_SourceFileAndLine( args[1], (char*)VMA(2), (int*)VMA(3) ); + + case CG_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( (bool)args[1] ); + return 0; + + case CG_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode( ); + + case CG_FIELD_COMPLETELIST: + Field_CompleteList( (char*)VMA(1) ); + return 0; + + case CG_MEMSET: + ::memset( VMA(1), args[2], args[3] ); + return 0; + case CG_MEMCPY: + ::memcpy( VMA(1), VMA(2), args[3] ); + return 0; + case CG_STRNCPY: + safe_strncpy( (char*)VMA(1), (const char*)VMA(2), args[3] ); + return args[1]; + case CG_SIN: + return FloatAsInt( sin( VMF(1) ) ); + case CG_COS: + return FloatAsInt( cos( VMF(1) ) ); + case CG_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + case CG_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + case CG_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + case CG_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + case CG_ACOS: + return FloatAsInt( Q_acos( VMF(1) ) ); + + case CG_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + + case CG_REAL_TIME: + return Com_RealTime( (qtime_t*)VMA(1) ); + case CG_SNAPVECTOR: + Q_SnapVector((float*)VMA(1)); + return 0; + + case CG_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic((const char*)VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case CG_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case CG_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case CG_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case CG_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case CG_R_REMAP_SHADER: + re.RemapShader( (const char*)VMA(1), (const char*)VMA(2), (const char*)VMA(3) ); + return 0; + + case CG_GET_ENTITY_TOKEN: + return re.GetEntityToken( (char*)VMA(1), args[2] ); + case CG_R_INPVS: + return re.inPVS( (const float*)VMA(1), (const float*)VMA(2) ); + + default: + assert(0); + Com_Error( ERR_DROP, "Bad cgame system trap: %ld", (long int) args[0] ); + } + return 0; +} + + +/* +==================== +CL_InitCGame + +Should only be called by CL_StartHunkUsers +==================== +*/ +void CL_InitCGame( void ) { + const char *info; + const char *mapname; + int t1, t2; + char backup[ MAX_STRING_CHARS ]; + vmInterpret_t interpret; + + t1 = Sys_Milliseconds(); + + // find the current mapname + info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); + + // load the dll or bytecode + interpret = (vmInterpret_t)Cvar_VariableValue("vm_cgame"); + if(cl_connectedToPureServer) + { + // if sv_pure is set we only allow qvms to be loaded + if(interpret != VMI_COMPILED && interpret != VMI_BYTECODE) + interpret = VMI_COMPILED; + } + + cls.cgame = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); + if ( !cls.cgame ) { + Com_Error( ERR_DROP, "VM_Create on cgame failed" ); + } + clc.state = CA_LOADING; + + Cvar_VariableStringBuffer( "cl_voipSendTarget", backup, sizeof( backup ) ); + Cvar_Set( "cl_voipSendTarget", "" ); + + // Probe 1.1 or gpp cgame + cls.cgInterface = 0; + probingCG = true; + VM_Call( cls.cgame, CG_VOIP_STRING ); + probingCG = false; + + Cvar_Set( "cl_voipSendTarget", backup ); + + if ( ( clc.netchan.alternateProtocol == 2 ) != ( cls.cgInterface == 2 ) ) { + Com_Error( ERR_DROP, "%s protocol %i, but a cgame module using the %s interface was found", + ( clc.demoplaying ? "Demo was recorded using" : "Server uses" ), + ( clc.netchan.alternateProtocol == 0 ? PROTOCOL_VERSION : clc.netchan.alternateProtocol == 1 ? 70 : 69 ), + ( cls.cgInterface == 2 ? "1.1" : "non-1.1" ) ); + } + + // init for this gamestate + // use the lastExecutedServerCommand instead of the serverCommandSequence + // otherwise server commands sent just before a gamestate are dropped + VM_Call( cls.cgame, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); + + // reset any CVAR_CHEAT cvars registered by cgame + if ( !clc.demoplaying && !cl_connectedToCheatServer ) + Cvar_SetCheatState(); + + // we will send a usercmd this frame, which + // will cause the server to send us the first snapshot + clc.state = CA_PRIMED; + + t2 = Sys_Milliseconds(); + + Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); + + // have the renderer touch all its images, so they are present + // on the card even if the driver does deferred loading + re.EndRegistration(); + + // make sure everything is paged in + if (!Sys_LowPhysicalMemory()) { + Com_TouchMemory(); + + CL_ProtocolSpecificCommandsInit(); + } + + // clear anything that got printed + Con_ClearNotify (); +} + + +/* +==================== +CL_GameCommand + +See if the current console command is claimed by the cgame +==================== +*/ +bool CL_GameCommand( void ) +{ + if ( !cls.cgame ) + return false; + + return (bool)VM_Call( cls.cgame, CG_CONSOLE_COMMAND ); +} + +/* +==================== +CL_GameConsoleText +==================== +*/ +void CL_GameConsoleText( void ) +{ + if ( !cls.cgame ) + return; + + VM_Call( cls.cgame, CG_CONSOLE_TEXT ); +} + +/* +===================== +CL_CGameRendering +===================== +*/ +void CL_CGameRendering( stereoFrame_t stereo ) +{ + VM_Call( cls.cgame, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); + VM_Debug( 0 ); +} + + +/* +================= +CL_AdjustTimeDelta + +Adjust the clients view of server time. + +We attempt to have cl.serverTime exactly equal the server's view +of time plus the timeNudge, but with variable latencies over +the internet it will often need to drift a bit to match conditions. + +Our ideal time would be to have the adjusted time approach, but not pass, +the very latest snapshot. + +Adjustments are only made when a new snapshot arrives with a rational +latency, which keeps the adjustment process framerate independent and +prevents massive overadjustment during times of significant packet loss +or bursted delayed packets. +================= +*/ + +#define RESET_TIME 500 + +void CL_AdjustTimeDelta( void ) { + int newDelta; + int deltaDelta; + + cl.newSnapshots = false; + + // the delta never drifts when replaying a demo + if ( clc.demoplaying ) { + return; + } + + newDelta = cl.snap.serverTime - cls.realtime; + deltaDelta = abs( newDelta - cl.serverTimeDelta ); + + if ( deltaDelta > RESET_TIME ) { + cl.serverTimeDelta = newDelta; + cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? + cl.serverTime = cl.snap.serverTime; + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + } else if ( deltaDelta > 100 ) { + // fast adjust, cut the difference in half + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; + } else { + // slow drift adjust, only move 1 or 2 msec + + // if any of the frames between this and the previous snapshot + // had to be extrapolated, nudge our sense of time back a little + // the granularity of +1 / -2 is too high for timescale modified frametimes + if ( com_timescale->value == 0 || com_timescale->value == 1 ) { + if ( cl.extrapolatedSnapshot ) { + cl.extrapolatedSnapshot = false; + cl.serverTimeDelta -= 2; + } else { + // otherwise, move our sense of time forward to minimize total latency + cl.serverTimeDelta++; + } + } + } + + if ( cl_showTimeDelta->integer ) { + Com_Printf( "%i ", cl.serverTimeDelta ); + } +} + + +/* +================== +CL_FirstSnapshot +================== +*/ +void CL_FirstSnapshot( void ) { + // ignore snapshots that don't have entities + if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { + return; + } + clc.state = CA_ACTIVE; + + // set the timedelta so we are exactly on this first frame + cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; + cl.oldServerTime = cl.snap.serverTime; + + clc.timeDemoBaseTime = cl.snap.serverTime; + + // if this is the first frame of active play, + // execute the contents of activeAction now + // this is to allow scripting a timedemo to start right + // after loading + if ( cl_activeAction->string[0] ) { + Cbuf_AddText( cl_activeAction->string ); + Cvar_Set( "activeAction", "" ); + } + +#ifdef USE_MUMBLE + if ((cl_useMumble->integer) && !mumble_islinked()) { + int ret = mumble_link(CLIENT_WINDOW_TITLE); + Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed"); + } +#endif + +#ifdef USE_VOIP + if (!clc.voipCodecInitialized) { + int i; + int error; + + clc.opusEncoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error); + + if ( error ) { + Com_DPrintf("VoIP: Error opus_encoder_create %d\n", error); + return; + } + + for (i = 0; i < MAX_CLIENTS; i++) { + clc.opusDecoder[i] = opus_decoder_create(48000, 1, &error); + if ( error ) { + Com_DPrintf("VoIP: Error opus_decoder_create(%d) %d\n", i, error); + return; + } + clc.voipIgnore[i] = false; + clc.voipGain[i] = 1.0f; + } + clc.voipCodecInitialized = true; + clc.voipMuteAll = false; + Cmd_AddCommand ("voip", CL_Voip_f); + Cvar_Set("cl_voipSendTarget", "spatial"); + ::memset(clc.voipTargets, ~0, sizeof(clc.voipTargets)); + } +#endif +} + +/* +================== +CL_SetCGameTime +================== +*/ +void CL_SetCGameTime( void ) { + // getting a valid frame message ends the connection process + if ( clc.state != CA_ACTIVE ) { + if ( clc.state != CA_PRIMED ) { + return; + } + if ( clc.demoplaying ) { + // we shouldn't get the first snapshot on the same frame + // as the gamestate, because it causes a bad time skip + if ( !clc.firstDemoFrameSkipped ) { + clc.firstDemoFrameSkipped = true; + return; + } + CL_ReadDemoMessage(); + } + if ( cl.newSnapshots ) { + cl.newSnapshots = false; + CL_FirstSnapshot(); + } + if ( clc.state != CA_ACTIVE ) { + return; + } + } + + // if we have gotten to this point, cl.snap is guaranteed to be valid + if ( !cl.snap.valid ) { + Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); + } + + // allow pause in single player + if ( sv_paused->integer && CL_CheckPaused() && com_sv_running->integer ) { + // paused + return; + } + + if ( cl.snap.serverTime < cl.oldFrameServerTime ) { + Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); + } + cl.oldFrameServerTime = cl.snap.serverTime; + + + // get our current view of time + + if ( clc.demoplaying && cl_freezeDemo->integer ) { + // cl_freezeDemo is used to lock a demo in place for single frame advances + + } else { + // cl_timeNudge is a user adjustable cvar that allows more + // or less latency to be added in the interest of better + // smoothness or better responsiveness. + int tn; + + tn = cl_timeNudge->integer; + if (tn<-30) { + tn = -30; + } else if (tn>30) { + tn = 30; + } + + cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; + + // guarantee that time will never flow backwards, even if + // serverTimeDelta made an adjustment or cl_timeNudge was changed + if ( cl.serverTime < cl.oldServerTime ) { + cl.serverTime = cl.oldServerTime; + } + cl.oldServerTime = cl.serverTime; + + // note if we are almost past the latest frame (without timeNudge), + // so we will try and adjust back a bit when the next snapshot arrives + if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { + cl.extrapolatedSnapshot = true; + } + } + + // if we have gotten new snapshots, drift serverTimeDelta + // don't do this every frame, or a period of packet loss would + // make a huge adjustment + if ( cl.newSnapshots ) { + CL_AdjustTimeDelta(); + } + + if ( !clc.demoplaying ) { + return; + } + + // if we are playing a demo back, we can just keep reading + // messages from the demo file until the cgame definately + // has valid snapshots to interpolate between + + // a timedemo will always use a deterministic set of time samples + // no matter what speed machine it is run on, + // while a normal demo may have different time samples + // each time it is played back + if ( cl_timedemo->integer ) { + int now = Sys_Milliseconds( ); + int frameDuration; + + if (!clc.timeDemoStart) { + clc.timeDemoStart = clc.timeDemoLastFrame = now; + clc.timeDemoMinDuration = INT_MAX; + clc.timeDemoMaxDuration = 0; + } + + frameDuration = now - clc.timeDemoLastFrame; + clc.timeDemoLastFrame = now; + + // Ignore the first measurement as it'll always be 0 + if( clc.timeDemoFrames > 0 ) + { + if( frameDuration > clc.timeDemoMaxDuration ) + clc.timeDemoMaxDuration = frameDuration; + + if( frameDuration < clc.timeDemoMinDuration ) + clc.timeDemoMinDuration = frameDuration; + + // 255 ms = about 4fps + if( frameDuration > UCHAR_MAX ) + frameDuration = UCHAR_MAX; + + clc.timeDemoDurations[ ( clc.timeDemoFrames - 1 ) % + MAX_TIMEDEMO_DURATIONS ] = frameDuration; + } + + clc.timeDemoFrames++; + cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; + } + + while ( cl.serverTime >= cl.snap.serverTime ) { + // feed another messag, which should change + // the contents of cl.snap + CL_ReadDemoMessage(); + if ( clc.state != CA_ACTIVE ) { + return; // end of demo + } + } +} diff --git a/src/client/cl_cin.cpp b/src/client/cl_cin.cpp new file mode 100644 index 0000000..6326e89 --- /dev/null +++ b/src/client/cl_cin.cpp @@ -0,0 +1,1937 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +/***************************************************************************** + * name: cl_cin.c + * + * desc: video and cinematic playback + * + * $Archive: /MissionPack/code/client/cl_cin.c $ + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +#include "client.h" +#include "snd_local.h" + +#define MAXSIZE 8 +#define MINSIZE 4 + +#define DEFAULT_CIN_WIDTH 512 +#define DEFAULT_CIN_HEIGHT 512 + +#define ROQ_QUAD 0x1000 +#define ROQ_QUAD_INFO 0x1001 +#define ROQ_CODEBOOK 0x1002 +#define ROQ_QUAD_VQ 0x1011 +#define ROQ_QUAD_JPEG 0x1012 +#define ROQ_QUAD_HANG 0x1013 +#define ROQ_PACKET 0x1030 +#define ZA_SOUND_MONO 0x1020 +#define ZA_SOUND_STEREO 0x1021 + +#define MAX_VIDEO_HANDLES 16 + +static void RoQ_init(void); + +/****************************************************************************** +* +* Class: trFMV +* +* Description: RoQ/RnR manipulation routines +* not entirely complete for first run +* +******************************************************************************/ + +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static unsigned short vq2[256 * 16 * 4]; +static unsigned short vq4[256 * 64 * 4]; +static unsigned short vq8[256 * 256 * 4]; + +struct cinematics_t +{ + byte linbuf[DEFAULT_CIN_WIDTH * DEFAULT_CIN_HEIGHT * 4 * 2]; + byte file[65536]; + short sqrTable[256]; + + int mcomp[256]; + byte *qStatus[2][32768]; + + long oldXOff; + long oldYOff; + long oldysize; + long oldxsize; + + int currentHandle; +}; + +struct cin_cache +{ + char fileName[MAX_OSPATH]; + + int CIN_WIDTH; + int CIN_HEIGHT; + + int xpos; + int ypos; + int width; + int height; + + bool looping; + bool holdAtEnd; + bool dirty; + bool alterGameState; + bool silent; + bool shader; + + fileHandle_t iFile; + e_status status; + + int startTime; + int lastTime; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + + void (*VQ0)(byte *status, void *qdata); + void (*VQ1)(byte *status, void *qdata); + void (*VQNormal)(byte *status, void *qdata); + void (*VQBuffer)(byte *status, void *qdata); + + long samplesPerPixel; // defaults to 2 + byte *gray; + unsigned int xsize, ysize, maxsize, minsize; + + bool half; + bool smootheddouble; + bool inMemory; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + int playonwalls; + byte *buf; + long drawX; + long drawY; +}; + +static cinematics_t cin; +static cin_cache cinTable[MAX_VIDEO_HANDLES]; +static int currentHandle = -1; +static int CL_handle = -1; + +extern int s_soundtime; // sample PAIRS + +void CIN_CloseAllVideos(void) +{ + int i; + + for (i = 0; i < MAX_VIDEO_HANDLES; i++) + { + if (cinTable[i].fileName[0] != 0) + { + CIN_StopCinematic(i); + } + } +} + +static int CIN_HandleForVideo(void) +{ + int i; + + for (i = 0; i < MAX_VIDEO_HANDLES; i++) + { + if (cinTable[i].fileName[0] == 0) + { + return i; + } + } + Com_Error(ERR_DROP, "CIN_HandleForVideo: none free"); + return -1; +} + +//----------------------------------------------------------------------------- +// RllSetupTable +// +// Allocates and initializes the square table. +// +// Parameters: None +// +// Returns: Nothing +//----------------------------------------------------------------------------- +static void RllSetupTable(void) +{ + int z; + + for (z = 0; z < 128; z++) + { + cin.sqrTable[z] = (short)(z * z); + cin.sqrTable[z + 128] = (short)(-cin.sqrTable[z]); + } +} + +#if 0 +//----------------------------------------------------------------------------- +// RllDecodeMonoToMono +// +// Decode mono source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of shorts of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToMono(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z = 0; z < size; z++) + { + prev = to[z] = (short)(prev + cin.sqrTable[from[z]]); + } + return size; //*sizeof(short)); +} +#endif + +//----------------------------------------------------------------------------- +// RllDecodeMonoToStereo +// +// Decode mono source data into a stereo buffer. Output is 4 times the number +// of bytes in the input. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/4 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +static long RllDecodeMonoToStereo(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z = 0; z < size; z++) + { + prev = (short)(prev + cin.sqrTable[from[z]]); + to[z * 2 + 0] = to[z * 2 + 1] = (short)(prev); + } + + return size; // * 2 * sizeof(short)); +} + +//----------------------------------------------------------------------------- +// RllDecodeStereoToStereo +// +// Decode stereo source data into a stereo buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/2 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +static long RllDecodeStereoToStereo(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag) +{ + unsigned int z; + unsigned char *zz = from; + int prevL, prevR; + + if (signedOutput) + { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) - 0x8000; + } + else + { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z = 0; z < size; z += 2) + { + prevL = (short)(prevL + cin.sqrTable[*zz++]); + prevR = (short)(prevR + cin.sqrTable[*zz++]); + to[z + 0] = (short)(prevL); + to[z + 1] = (short)(prevR); + } + + return (size >> 1); //*sizeof(short)); +} + +#if 0 +//----------------------------------------------------------------------------- +// RllDecodeStereoToMono +// +// Decode stereo source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToMono(unsigned char *from, short *to, unsigned int size, char signedOutput, unsigned short flag) +{ + unsigned int z; + int prevL, prevR; + + if (signedOutput) + { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) - 0x8000; + } + else + { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z = 0; z < size; z += 1) + { + prevL = prevL + cin.sqrTable[from[z * 2]]; + prevR = prevR + cin.sqrTable[from[z * 2 + 1]]; + to[z] = (short)((prevL + prevR) / 2); + } + + return size; +} +#endif + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void move8_32(byte *src, byte *dst, int spl) +{ + int i; + + for (i = 0; i < 8; ++i) + { + memcpy(dst, src, 32); + src += spl; + dst += spl; + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void move4_32(byte *src, byte *dst, int spl) +{ + int i; + + for (i = 0; i < 4; ++i) + { + memcpy(dst, src, 16); + src += spl; + dst += spl; + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit8_32(byte *src, byte *dst, int spl) +{ + int i; + + for (i = 0; i < 8; ++i) + { + memcpy(dst, src, 32); + src += 32; + dst += spl; + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +static void blit4_32(byte *src, byte *dst, int spl) +{ + int i; + + for (i = 0; i < 4; ++i) + { + memmove(dst, src, 16); + src += 16; + dst += spl; + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit2_32(byte *src, byte *dst, int spl) +{ + memcpy(dst, src, 8); + memcpy(dst + spl, src + 8, 8); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blitVQQuad32fs(byte **status, unsigned char *data) +{ + unsigned short newd, celdata, code; + unsigned int index, i; + int spl; + + newd = 0; + celdata = 0; + index = 0; + + spl = cinTable[currentHandle].samplesPerLine; + + do + { + if (!newd) + { + newd = 7; + celdata = data[0] + data[1] * 256; + data += 2; + } + else + { + newd--; + } + + code = (unsigned short)(celdata & 0xc000); + celdata <<= 2; + + switch (code) + { + case 0x8000: // vq code + blit8_32((byte *)&vq8[(*data) * 128], status[index], spl); + data++; + index += 5; + break; + case 0xc000: // drop + index++; // skip 8x8 + for (i = 0; i < 4; i++) + { + if (!newd) + { + newd = 7; + celdata = data[0] + data[1] * 256; + data += 2; + } + else + { + newd--; + } + + code = (unsigned short)(celdata & 0xc000); + celdata <<= 2; + + switch (code) + { // code in top two bits of code + case 0x8000: // 4x4 vq code + blit4_32((byte *)&vq4[(*data) * 32], status[index], spl); + data++; + break; + case 0xc000: // 2x2 vq code + blit2_32((byte *)&vq2[(*data) * 8], status[index], spl); + data++; + blit2_32((byte *)&vq2[(*data) * 8], status[index] + 8, spl); + data++; + blit2_32((byte *)&vq2[(*data) * 8], status[index] + spl * 2, spl); + data++; + blit2_32((byte *)&vq2[(*data) * 8], status[index] + spl * 2 + 8, spl); + data++; + break; + case 0x4000: // motion compensation + move4_32(status[index] + cin.mcomp[(*data)], status[index], spl); + data++; + break; + } + index++; + } + break; + case 0x4000: // motion compensation + move8_32(status[index] + cin.mcomp[(*data)], status[index], spl); + data++; + index += 5; + break; + case 0x0000: + index += 5; + break; + } + } while (status[index] != NULL); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void ROQ_GenYUVTables(void) +{ + float t_ub, t_vr, t_ug, t_vg; + long i; + + t_ub = (1.77200f / 2.0f) * (float)(1 << 6) + 0.5f; + t_vr = (1.40200f / 2.0f) * (float)(1 << 6) + 0.5f; + t_ug = (0.34414f / 2.0f) * (float)(1 << 6) + 0.5f; + t_vg = (0.71414f / 2.0f) * (float)(1 << 6) + 0.5f; + for (i = 0; i < 256; i++) + { + float x = (float)(2 * i - 255); + + ROQ_UB_tab[i] = (long)((t_ub * x) + (1 << 5)); + ROQ_VR_tab[i] = (long)((t_vr * x) + (1 << 5)); + ROQ_UG_tab[i] = (long)((-t_ug * x)); + ROQ_VG_tab[i] = (long)((-t_vg * x) + (1 << 5)); + ROQ_YY_tab[i] = (long)((i << 6) | (i >> 2)); + } +} + +#define VQ2TO4(a, b, c, d) \ + { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; \ + b += 2; \ + } + +#define VQ2TO2(a, b, c, d) \ + { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; \ + b++; \ + } + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static unsigned short yuv_to_rgb(long y, long u, long v) +{ + long r, g, b, YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 9; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; + b = (YY + ROQ_UB_tab[u]) >> 9; + + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + if (r > 31) r = 31; + if (g > 63) g = 63; + if (b > 31) b = 31; + + return (unsigned short)((r << 11) + (g << 5) + (b)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +static unsigned int yuv_to_rgb24(long y, long u, long v) +{ + long r, g, b, YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + return LittleLong((r) | (g << 8) | (b << 16) | (255 << 24)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void decodeCodeBook(byte *input, unsigned short roq_flags) +{ + long i, j, two, four; + unsigned short *aptr, *bptr, *cptr, *dptr; + long y0, y1, y2, y3, cr, cb; + byte *bbptr, *baptr, *bcptr, *bdptr; + union { + unsigned int *i; + unsigned short *s; + } iaptr, ibptr, icptr, idptr; + + if (!roq_flags) + { + two = four = 256; + } + else + { + two = roq_flags >> 8; + if (!two) two = 256; + four = roq_flags & 0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + if (!cinTable[currentHandle].half) + { + if (!cinTable[currentHandle].smootheddouble) + { + // + // normal height + // + if (cinTable[currentHandle].samplesPerPixel == 2) + { + for (i = 0; i < two; i++) + { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *bptr++ = yuv_to_rgb(y0, cr, cb); + *bptr++ = yuv_to_rgb(y1, cr, cb); + *bptr++ = yuv_to_rgb(y2, cr, cb); + *bptr++ = yuv_to_rgb(y3, cr, cb); + } + + cptr = (unsigned short *)vq4; + dptr = (unsigned short *)vq8; + + for (i = 0; i < four; i++) + { + aptr = (unsigned short *)vq2 + (*input++) * 4; + bptr = (unsigned short *)vq2 + (*input++) * 4; + for (j = 0; j < 2; j++) VQ2TO4(aptr, bptr, cptr, dptr); + } + } + else if (cinTable[currentHandle].samplesPerPixel == 4) + { + ibptr.s = bptr; + for (i = 0; i < two; i++) + { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *ibptr.i++ = yuv_to_rgb24(y0, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y1, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y2, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y3, cr, cb); + } + + icptr.s = vq4; + idptr.s = vq8; + + for (i = 0; i < four; i++) + { + iaptr.s = vq2; + iaptr.i += (*input++) * 4; + ibptr.s = vq2; + ibptr.i += (*input++) * 4; + for (j = 0; j < 2; j++) VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i); + } + } + else if (cinTable[currentHandle].samplesPerPixel == 1) + { + bbptr = (byte *)bptr; + for (i = 0; i < two; i++) + { + *bbptr++ = cinTable[currentHandle].gray[*input++]; + *bbptr++ = cinTable[currentHandle].gray[*input++]; + *bbptr++ = cinTable[currentHandle].gray[*input++]; + *bbptr++ = cinTable[currentHandle].gray[*input]; + input += 3; + } + + bcptr = (byte *)vq4; + bdptr = (byte *)vq8; + + for (i = 0; i < four; i++) + { + baptr = (byte *)vq2 + (*input++) * 4; + bbptr = (byte *)vq2 + (*input++) * 4; + for (j = 0; j < 2; j++) VQ2TO4(baptr, bbptr, bcptr, bdptr); + } + } + } + else + { + // + // double height, smoothed + // + if (cinTable[currentHandle].samplesPerPixel == 2) + { + for (i = 0; i < two; i++) + { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *bptr++ = yuv_to_rgb(y0, cr, cb); + *bptr++ = yuv_to_rgb(y1, cr, cb); + *bptr++ = yuv_to_rgb(((y0 * 3) + y2) / 4, cr, cb); + *bptr++ = yuv_to_rgb(((y1 * 3) + y3) / 4, cr, cb); + *bptr++ = yuv_to_rgb((y0 + (y2 * 3)) / 4, cr, cb); + *bptr++ = yuv_to_rgb((y1 + (y3 * 3)) / 4, cr, cb); + *bptr++ = yuv_to_rgb(y2, cr, cb); + *bptr++ = yuv_to_rgb(y3, cr, cb); + } + + cptr = (unsigned short *)vq4; + dptr = (unsigned short *)vq8; + + for (i = 0; i < four; i++) + { + aptr = (unsigned short *)vq2 + (*input++) * 8; + bptr = (unsigned short *)vq2 + (*input++) * 8; + for (j = 0; j < 2; j++) + { + VQ2TO4(aptr, bptr, cptr, dptr); + VQ2TO4(aptr, bptr, cptr, dptr); + } + } + } + else if (cinTable[currentHandle].samplesPerPixel == 4) + { + ibptr.s = bptr; + for (i = 0; i < two; i++) + { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *ibptr.i++ = yuv_to_rgb24(y0, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y1, cr, cb); + *ibptr.i++ = yuv_to_rgb24(((y0 * 3) + y2) / 4, cr, cb); + *ibptr.i++ = yuv_to_rgb24(((y1 * 3) + y3) / 4, cr, cb); + *ibptr.i++ = yuv_to_rgb24((y0 + (y2 * 3)) / 4, cr, cb); + *ibptr.i++ = yuv_to_rgb24((y1 + (y3 * 3)) / 4, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y2, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y3, cr, cb); + } + + icptr.s = vq4; + idptr.s = vq8; + + for (i = 0; i < four; i++) + { + iaptr.s = vq2; + iaptr.i += (*input++) * 8; + ibptr.s = vq2; + ibptr.i += (*input++) * 8; + for (j = 0; j < 2; j++) + { + VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i); + VQ2TO4(iaptr.i, ibptr.i, icptr.i, idptr.i); + } + } + } + else if (cinTable[currentHandle].samplesPerPixel == 1) + { + bbptr = (byte *)bptr; + for (i = 0; i < two; i++) + { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input; + input += 3; + *bbptr++ = cinTable[currentHandle].gray[y0]; + *bbptr++ = cinTable[currentHandle].gray[y1]; + *bbptr++ = cinTable[currentHandle].gray[((y0 * 3) + y2) / 4]; + *bbptr++ = cinTable[currentHandle].gray[((y1 * 3) + y3) / 4]; + *bbptr++ = cinTable[currentHandle].gray[(y0 + (y2 * 3)) / 4]; + *bbptr++ = cinTable[currentHandle].gray[(y1 + (y3 * 3)) / 4]; + *bbptr++ = cinTable[currentHandle].gray[y2]; + *bbptr++ = cinTable[currentHandle].gray[y3]; + } + + bcptr = (byte *)vq4; + bdptr = (byte *)vq8; + + for (i = 0; i < four; i++) + { + baptr = (byte *)vq2 + (*input++) * 8; + bbptr = (byte *)vq2 + (*input++) * 8; + for (j = 0; j < 2; j++) + { + VQ2TO4(baptr, bbptr, bcptr, bdptr); + VQ2TO4(baptr, bbptr, bcptr, bdptr); + } + } + } + } + } + else + { + // + // 1/4 screen + // + if (cinTable[currentHandle].samplesPerPixel == 2) + { + for (i = 0; i < two; i++) + { + y0 = (long)*input; + input += 2; + y2 = (long)*input; + input += 2; + cr = (long)*input++; + cb = (long)*input++; + *bptr++ = yuv_to_rgb(y0, cr, cb); + *bptr++ = yuv_to_rgb(y2, cr, cb); + } + + cptr = (unsigned short *)vq4; + dptr = (unsigned short *)vq8; + + for (i = 0; i < four; i++) + { + aptr = (unsigned short *)vq2 + (*input++) * 2; + bptr = (unsigned short *)vq2 + (*input++) * 2; + for (j = 0; j < 2; j++) + { + VQ2TO2(aptr, bptr, cptr, dptr); + } + } + } + else if (cinTable[currentHandle].samplesPerPixel == 1) + { + bbptr = (byte *)bptr; + + for (i = 0; i < two; i++) + { + *bbptr++ = cinTable[currentHandle].gray[*input]; + input += 2; + *bbptr++ = cinTable[currentHandle].gray[*input]; + input += 4; + } + + bcptr = (byte *)vq4; + bdptr = (byte *)vq8; + + for (i = 0; i < four; i++) + { + baptr = (byte *)vq2 + (*input++) * 2; + bbptr = (byte *)vq2 + (*input++) * 2; + for (j = 0; j < 2; j++) + { + VQ2TO2(baptr, bbptr, bcptr, bdptr); + } + } + } + else if (cinTable[currentHandle].samplesPerPixel == 4) + { + ibptr.s = bptr; + for (i = 0; i < two; i++) + { + y0 = (long)*input; + input += 2; + y2 = (long)*input; + input += 2; + cr = (long)*input++; + cb = (long)*input++; + *ibptr.i++ = yuv_to_rgb24(y0, cr, cb); + *ibptr.i++ = yuv_to_rgb24(y2, cr, cb); + } + + icptr.s = vq4; + idptr.s = vq8; + + for (i = 0; i < four; i++) + { + iaptr.s = vq2; + iaptr.i += (*input++) * 2; + ibptr.s = vq2 + (*input++) * 2; + ibptr.i += (*input++) * 2; + for (j = 0; j < 2; j++) + { + VQ2TO2(iaptr.i, ibptr.i, icptr.i, idptr.i); + } + } + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void recurseQuad(long startX, long startY, long quadSize, long xOff, long yOff) +{ + byte *scroff; + long bigx, bigy, lowx, lowy, useY; + long offset; + + offset = cinTable[currentHandle].screenDelta; + + lowx = lowy = 0; + bigx = cinTable[currentHandle].xsize; + bigy = cinTable[currentHandle].ysize; + + if (bigx > cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; + if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; + + if ((startX >= lowx) && (startX + quadSize) <= (bigx) && (startY + quadSize) <= (bigy) && (startY >= lowy) && + quadSize <= MAXSIZE) + { + useY = startY; + scroff = cin.linbuf + + (useY + ((cinTable[currentHandle].CIN_HEIGHT - bigy) >> 1) + yOff) * + (cinTable[currentHandle].samplesPerLine) + + (((startX + xOff)) * cinTable[currentHandle].samplesPerPixel); + + cin.qStatus[0][cinTable[currentHandle].onQuad] = scroff; + cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff + offset; + } + + if (quadSize != MINSIZE) + { + quadSize >>= 1; + recurseQuad(startX, startY, quadSize, xOff, yOff); + recurseQuad(startX + quadSize, startY, quadSize, xOff, yOff); + recurseQuad(startX, startY + quadSize, quadSize, xOff, yOff); + recurseQuad(startX + quadSize, startY + quadSize, quadSize, xOff, yOff); + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void setupQuad(long xOff, long yOff) +{ + long numQuadCels, i, x, y; + byte *temp; + + if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && + cinTable[currentHandle].xsize == cin.oldxsize) + { + return; + } + + cin.oldXOff = xOff; + cin.oldYOff = yOff; + cin.oldysize = cinTable[currentHandle].ysize; + cin.oldxsize = cinTable[currentHandle].xsize; + + numQuadCels = (cinTable[currentHandle].xsize * cinTable[currentHandle].ysize) / (16); + numQuadCels += numQuadCels / 4; + numQuadCels += 64; // for overflow + + cinTable[currentHandle].onQuad = 0; + + for (y = 0; y < (long)cinTable[currentHandle].ysize; y += 16) + for (x = 0; x < (long)cinTable[currentHandle].xsize; x += 16) recurseQuad(x, y, 16, xOff, yOff); + + temp = NULL; + + for (i = (numQuadCels - 64); i < numQuadCels; i++) + { + cin.qStatus[0][i] = temp; // eoq + cin.qStatus[1][i] = temp; // eoq + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void readQuadInfo(byte *qData) +{ + if (currentHandle < 0) return; + + cinTable[currentHandle].xsize = qData[0] + qData[1] * 256; + cinTable[currentHandle].ysize = qData[2] + qData[3] * 256; + cinTable[currentHandle].maxsize = qData[4] + qData[5] * 256; + cinTable[currentHandle].minsize = qData[6] + qData[7] * 256; + + cinTable[currentHandle].CIN_HEIGHT = cinTable[currentHandle].ysize; + cinTable[currentHandle].CIN_WIDTH = cinTable[currentHandle].xsize; + + cinTable[currentHandle].samplesPerLine = + cinTable[currentHandle].CIN_WIDTH * cinTable[currentHandle].samplesPerPixel; + cinTable[currentHandle].screenDelta = cinTable[currentHandle].CIN_HEIGHT * cinTable[currentHandle].samplesPerLine; + + cinTable[currentHandle].half = false; + cinTable[currentHandle].smootheddouble = false; + + cinTable[currentHandle].VQ0 = cinTable[currentHandle].VQNormal; + cinTable[currentHandle].VQ1 = cinTable[currentHandle].VQBuffer; + + cinTable[currentHandle].t[0] = cinTable[currentHandle].screenDelta; + cinTable[currentHandle].t[1] = -cinTable[currentHandle].screenDelta; + + cinTable[currentHandle].drawX = cinTable[currentHandle].CIN_WIDTH; + cinTable[currentHandle].drawY = cinTable[currentHandle].CIN_HEIGHT; + + // rage pro is very slow at 512 wide textures, voodoo can't do it at all + if (cls.glconfig.hardwareType == GLHW_RAGEPRO || cls.glconfig.maxTextureSize <= 256) + { + if (cinTable[currentHandle].drawX > 256) + { + cinTable[currentHandle].drawX = 256; + } + if (cinTable[currentHandle].drawY > 256) + { + cinTable[currentHandle].drawY = 256; + } + if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) + { + Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQPrepMcomp(long xoff, long yoff) +{ + long i, j, x, y, temp, temp2; + + i = cinTable[currentHandle].samplesPerLine; + j = cinTable[currentHandle].samplesPerPixel; + if (cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize * 4) && !cinTable[currentHandle].half) + { + j = j + j; + i = i + i; + } + + for (y = 0; y < 16; y++) + { + temp2 = (y + yoff - 8) * i; + for (x = 0; x < 16; x++) + { + temp = (x + xoff - 8) * j; + cin.mcomp[(x * 16) + y] = cinTable[currentHandle].normalBuffer0 - (temp2 + temp); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void initRoQ(void) +{ + if (currentHandle < 0) return; + + cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].samplesPerPixel = 4; + ROQ_GenYUVTables(); + RllSetupTable(); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +/* +static byte* RoQFetchInterlaced( byte *source ) { + int x, *src, *dst; + + if (currentHandle < 0) return NULL; + + src = (int *)source; + dst = (int *)cinTable[currentHandle].buf2; + + for(x=0;x<256*256;x++) { + *dst = *src; + dst++; src += 2; + } + return cinTable[currentHandle].buf2; +} +*/ +static void RoQReset(void) +{ + if (currentHandle < 0) return; + + FS_FCloseFile(cinTable[currentHandle].iFile); + FS_FOpenFileRead(cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, true); + // let the background thread start reading ahead + FS_Read(cin.file, 16, cinTable[currentHandle].iFile); + RoQ_init(); + cinTable[currentHandle].status = FMV_LOOPED; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQInterrupt(void) +{ + byte *framedata; + short sbuf[32768]; + int ssize; + + if (currentHandle < 0) return; + + FS_Read(cin.file, cinTable[currentHandle].RoQFrameSize + 8, cinTable[currentHandle].iFile); + if (cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize) + { + if (cinTable[currentHandle].holdAtEnd == false) + { + if (cinTable[currentHandle].looping) + { + RoQReset(); + } + else + { + cinTable[currentHandle].status = FMV_EOF; + } + } + else + { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata = cin.file; +// +// new frame is ready +// +redump: + switch (cinTable[currentHandle].roq_id) + { + case ROQ_QUAD_VQ: + if ((cinTable[currentHandle].numQuads & 1)) + { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; + RoQPrepMcomp(cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1); + cinTable[currentHandle].VQ1((byte *)cin.qStatus[1], framedata); + cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; + } + else + { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; + RoQPrepMcomp(cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1); + cinTable[currentHandle].VQ0((byte *)cin.qStatus[0], framedata); + cinTable[currentHandle].buf = cin.linbuf; + } + if (cinTable[currentHandle].numQuads == 0) + { // first frame + ::memcpy(cin.linbuf + cinTable[currentHandle].screenDelta, cin.linbuf, + cinTable[currentHandle].samplesPerLine * cinTable[currentHandle].ysize); + } + cinTable[currentHandle].numQuads++; + cinTable[currentHandle].dirty = true; + break; + case ROQ_CODEBOOK: + decodeCodeBook(framedata, (unsigned short)cinTable[currentHandle].roq_flags); + break; + case ZA_SOUND_MONO: + if (!cinTable[currentHandle].silent) + { + ssize = RllDecodeMonoToStereo(framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, + (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples(0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f, -1); + } + break; + case ZA_SOUND_STEREO: + if (!cinTable[currentHandle].silent) + { + if (cinTable[currentHandle].numQuads == -1) + { + S_Update(); + s_rawend[0] = s_soundtime; + } + ssize = RllDecodeStereoToStereo(framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, + (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples(0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f, -1); + } + break; + case ROQ_QUAD_INFO: + if (cinTable[currentHandle].numQuads == -1) + { + readQuadInfo(framedata); + setupQuad(0, 0); + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds(); + } + if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; + break; + case ROQ_PACKET: + cinTable[currentHandle].inMemory = (bool)cinTable[currentHandle].roq_flags; + cinTable[currentHandle].RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + cinTable[currentHandle].RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + break; + default: + cinTable[currentHandle].status = FMV_EOF; + break; + } + // + // read in next frame data + // + if (cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize) + { + if (cinTable[currentHandle].holdAtEnd == false) + { + if (cinTable[currentHandle].looping) + { + RoQReset(); + } + else + { + cinTable[currentHandle].status = FMV_EOF; + } + } + else + { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata += cinTable[currentHandle].RoQFrameSize; + cinTable[currentHandle].roq_id = framedata[0] + framedata[1] * 256; + cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3] * 256 + framedata[4] * 65536; + cinTable[currentHandle].roq_flags = framedata[6] + framedata[7] * 256; + cinTable[currentHandle].roqF0 = (signed char)framedata[7]; + cinTable[currentHandle].roqF1 = (signed char)framedata[6]; + + if (cinTable[currentHandle].RoQFrameSize > 65536 || cinTable[currentHandle].roq_id == 0x1084) + { + Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); + cinTable[currentHandle].status = FMV_EOF; + if (cinTable[currentHandle].looping) + { + RoQReset(); + } + return; + } + if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) + { + cinTable[currentHandle].inMemory = false; + framedata += 8; + goto redump; + } + // + // one more frame hits the dust + // + // assert(cinTable[currentHandle].RoQFrameSize <= 65536); + // r = FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile ); + cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize + 8; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQ_init(void) +{ + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds(); + + cinTable[currentHandle].RoQPlayed = 24; + + /* get frame rate */ + cinTable[currentHandle].roqFPS = cin.file[6] + cin.file[7] * 256; + + if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; + + cinTable[currentHandle].numQuads = -1; + + cinTable[currentHandle].roq_id = cin.file[8] + cin.file[9] * 256; + cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11] * 256 + cin.file[12] * 65536; + cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15] * 256; + + if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) + { + return; + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQShutdown(void) +{ + const char *s; + + if (!cinTable[currentHandle].buf) + { + return; + } + + if (cinTable[currentHandle].status == FMV_IDLE) + { + return; + } + Com_DPrintf("finished cinematic\n"); + cinTable[currentHandle].status = FMV_IDLE; + + if (cinTable[currentHandle].iFile) + { + FS_FCloseFile(cinTable[currentHandle].iFile); + cinTable[currentHandle].iFile = 0; + } + + if (cinTable[currentHandle].alterGameState) + { + clc.state = CA_DISCONNECTED; + // we can't just do a vstr nextmap, because + // if we are aborting the intro cinematic with + // a devmap command, nextmap would be valid by + // the time it was referenced + s = Cvar_VariableString("nextmap"); + if (s[0]) + { + Cbuf_ExecuteText(EXEC_APPEND, va("%s\n", s)); + Cvar_Set("nextmap", ""); + } + CL_handle = -1; + + CL_ProtocolSpecificCommandsInit(); + } + cinTable[currentHandle].fileName[0] = 0; + currentHandle = -1; +} + +/* +================== +CIN_StopCinematic +================== +*/ +e_status CIN_StopCinematic(int handle) +{ + if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + currentHandle = handle; + + Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); + + if (!cinTable[currentHandle].buf) + { + return FMV_EOF; + } + + if (cinTable[currentHandle].alterGameState) + { + if (clc.state != CA_CINEMATIC) + { + return cinTable[currentHandle].status; + } + } + cinTable[currentHandle].status = FMV_EOF; + RoQShutdown(); + + return FMV_EOF; +} + +/* +================== +CIN_RunCinematic + +Fetch and decompress the pending frame +================== +*/ + +e_status CIN_RunCinematic(int handle) +{ + int start = 0; + int thisTime = 0; + + if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + + if (cin.currentHandle != handle) + { + currentHandle = handle; + cin.currentHandle = currentHandle; + cinTable[currentHandle].status = FMV_EOF; + RoQReset(); + } + + if (cinTable[handle].playonwalls < -1) + { + return cinTable[handle].status; + } + + currentHandle = handle; + + if (cinTable[currentHandle].alterGameState) + { + if (clc.state != CA_CINEMATIC) + { + return cinTable[currentHandle].status; + } + } + + if (cinTable[currentHandle].status == FMV_IDLE) + { + return cinTable[currentHandle].status; + } + + thisTime = CL_ScaledMilliseconds(); + if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime)) > 100) + { + cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; + } + cinTable[currentHandle].tfps = (((CL_ScaledMilliseconds() - cinTable[currentHandle].startTime) * 3) / 100); + + start = cinTable[currentHandle].startTime; + while ((cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) && + (cinTable[currentHandle].status == FMV_PLAY)) + { + RoQInterrupt(); + if (start != cinTable[currentHandle].startTime) + { + cinTable[currentHandle].tfps = (((CL_ScaledMilliseconds() - cinTable[currentHandle].startTime) * 3) / 100); + start = cinTable[currentHandle].startTime; + } + } + + cinTable[currentHandle].lastTime = thisTime; + + if (cinTable[currentHandle].status == FMV_LOOPED) + { + cinTable[currentHandle].status = FMV_PLAY; + } + + if (cinTable[currentHandle].status == FMV_EOF) + { + if (cinTable[currentHandle].looping) + { + RoQReset(); + } + else + { + RoQShutdown(); + return FMV_EOF; + } + } + + return cinTable[currentHandle].status; +} + +void CIN_SetExtents(int handle, int x, int y, int w, int h) +{ + if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].xpos = x; + cinTable[handle].ypos = y; + cinTable[handle].width = w; + cinTable[handle].height = h; + cinTable[handle].dirty = true; +} + +static void CIN_SetLooping(int handle, bool loop) +{ + if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].looping = loop; +} + +/* +================== +CIN_PlayCinematic +================== +*/ +int CIN_PlayCinematic(const char *arg, int x, int y, int w, int h, int systemBits) +{ + unsigned short RoQID; + char name[MAX_OSPATH]; + int i; + + if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) + { + Com_sprintf(name, sizeof(name), "video/%s", arg); + } + else + { + Com_sprintf(name, sizeof(name), "%s", arg); + } + + if (!(systemBits & CIN_system)) + { + for (i = 0; i < MAX_VIDEO_HANDLES; i++) + { + if (!strcmp(cinTable[i].fileName, name)) + { + return i; + } + } + } + + Com_DPrintf("CIN_PlayCinematic( %s )\n", arg); + + ::memset(&cin, 0, sizeof(cinematics_t)); + currentHandle = CIN_HandleForVideo(); + + cin.currentHandle = currentHandle; + + strcpy(cinTable[currentHandle].fileName, name); + + cinTable[currentHandle].ROQSize = 0; + cinTable[currentHandle].ROQSize = + FS_FOpenFileRead(cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, true); + + if (cinTable[currentHandle].ROQSize <= 0) + { + Com_DPrintf("play(%s), ROQSize<=0\n", arg); + cinTable[currentHandle].fileName[0] = 0; + return -1; + } + + CIN_SetExtents(currentHandle, x, y, w, h); + CIN_SetLooping(currentHandle, ((systemBits & CIN_loop) != 0)); + + cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; + cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0; + cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0; + cinTable[currentHandle].playonwalls = 1; + cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0; + cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0; + + if (cinTable[currentHandle].alterGameState) + { + // close the menu + if (cls.ui) + { + VM_Call(cls.ui, UI_SET_ACTIVE_MENU - cls.uiInterface == 2 ? 2 : 0, UIMENU_NONE); + } + } + else + { + cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; + } + + initRoQ(); + + FS_Read(cin.file, 16, cinTable[currentHandle].iFile); + + RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1]) * 256; + if (RoQID == 0x1084) + { + RoQ_init(); + // FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); + + cinTable[currentHandle].status = FMV_PLAY; + Com_DPrintf("trFMV::play(), playing %s\n", arg); + + if (cinTable[currentHandle].alterGameState) + { + clc.state = CA_CINEMATIC; + } + + if (!cinTable[currentHandle].silent) + { + s_rawend[0] = s_soundtime; + } + + return currentHandle; + } + Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); + + RoQShutdown(); + return -1; +} + +/* +================== +CIN_ResampleCinematic + +Resample cinematic to 256x256 and store in buf2 +================== +*/ +static void CIN_ResampleCinematic(int handle, int *buf2) +{ + int ix, iy, *buf3, xm, ym, ll; + byte *buf; + + buf = cinTable[handle].buf; + + xm = cinTable[handle].CIN_WIDTH / 256; + ym = cinTable[handle].CIN_HEIGHT / 256; + ll = 8; + if (cinTable[handle].CIN_WIDTH == 512) + { + ll = 9; + } + + buf3 = (int *)buf; + if (xm == 2 && ym == 2) + { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy < 256; iy++) + { + iiy = iy << 12; + for (ix = 0; ix < 2048; ix += 8) + { + for (ic = ix; ic < (ix + 4); ic++) + { + *bc2 = (bc3[iiy + ic] + bc3[iiy + 4 + ic] + bc3[iiy + 2048 + ic] + bc3[iiy + 2048 + 4 + ic]) >> 2; + bc2++; + } + } + } + } + else if (xm == 2 && ym == 1) + { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy < 256; iy++) + { + iiy = iy << 11; + for (ix = 0; ix < 2048; ix += 8) + { + for (ic = ix; ic < (ix + 4); ic++) + { + *bc2 = (bc3[iiy + ic] + bc3[iiy + 4 + ic]) >> 1; + bc2++; + } + } + } + } + else + { + for (iy = 0; iy < 256; iy++) + { + for (ix = 0; ix < 256; ix++) + { + buf2[(iy << 8) + ix] = buf3[((iy * ym) << ll) + (ix * xm)]; + } + } + } +} + +/* +================== +CIN_DrawCinematic +================== +*/ +void CIN_DrawCinematic(int handle) +{ + float x, y, w, h; + byte *buf; + + if (handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + + if (!cinTable[handle].buf) + { + return; + } + + x = cinTable[handle].xpos; + y = cinTable[handle].ypos; + w = cinTable[handle].width; + h = cinTable[handle].height; + buf = cinTable[handle].buf; + SCR_AdjustFrom640(&x, &y, &w, &h); + + if (cinTable[handle].dirty && + (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX + || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) + { + int *buf2 = (int *)Hunk_AllocateTempMemory(256 * 256 * 4); + + CIN_ResampleCinematic(handle, buf2); + + re.DrawStretchRaw(x, y, w, h, 256, 256, (byte *)buf2, handle, true); + cinTable[handle].dirty = false; + Hunk_FreeTempMemory(buf2); + return; + } + + re.DrawStretchRaw(x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY, + buf, handle, cinTable[handle].dirty); + + cinTable[handle].dirty = false; +} + +void CL_PlayCinematic_f(void) +{ + int bits = CIN_system; + + Com_DPrintf("CL_PlayCinematic_f\n"); + if (clc.state == CA_CINEMATIC) + { + SCR_StopCinematic(); + } + + const char *arg = Cmd_Argv(1); + const char *s = Cmd_Argv(2); + + if ((s && s[0] == '1') || Q_stricmp(arg, "demoend.roq") == 0 || Q_stricmp(arg, "end.roq") == 0) + { + bits |= CIN_hold; + } + if (s && s[0] == '2') + { + bits |= CIN_loop; + } + + S_StopAllSounds(); + + CL_handle = CIN_PlayCinematic(arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits); + if (CL_handle >= 0) + { + do + { + SCR_RunCinematic(); + } while (cinTable[currentHandle].buf == NULL && + cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) + } +} + +void SCR_DrawCinematic(void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) + { + CIN_DrawCinematic(CL_handle); + } +} + +void SCR_RunCinematic(void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) + { + CIN_RunCinematic(CL_handle); + } +} + +void SCR_StopCinematic(void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) + { + CIN_StopCinematic(CL_handle); + S_StopAllSounds(); + CL_handle = -1; + } +} + +void CIN_UploadCinematic(int handle) +{ + if (handle >= 0 && handle < MAX_VIDEO_HANDLES) + { + if (!cinTable[handle].buf) + { + return; + } + if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) + { + if (cinTable[handle].playonwalls == 0) + { + cinTable[handle].playonwalls = -1; + } + else + { + if (cinTable[handle].playonwalls == -1) + { + cinTable[handle].playonwalls = -2; + } + else + { + cinTable[handle].dirty = false; + } + } + } + + // Resample the video if needed + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || + cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) + { + int *buf2 = (int *)Hunk_AllocateTempMemory(256 * 256 * 4); + + CIN_ResampleCinematic(handle, buf2); + + re.UploadCinematic( + cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, 256, 256, (byte *)buf2, handle, true); + cinTable[handle].dirty = false; + Hunk_FreeTempMemory(buf2); + } + else + { + // Upload video at normal resolution + re.UploadCinematic(cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, cinTable[handle].drawX, + cinTable[handle].drawY, cinTable[handle].buf, handle, cinTable[handle].dirty); + cinTable[handle].dirty = false; + } + + if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) + { + cinTable[handle].playonwalls--; + } + else if (cl_inGameVideo->integer != 0 && cinTable[handle].playonwalls != 1) + { + cinTable[handle].playonwalls = 1; + } + } +} diff --git a/src/client/cl_console.cpp b/src/client/cl_console.cpp new file mode 100644 index 0000000..e3e928c --- /dev/null +++ b/src/client/cl_console.cpp @@ -0,0 +1,877 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// console.c + +#include "client.h" + +#include "qcommon/cdefs.h" + +int g_console_field_width = 78; + + +#define NUM_CON_TIMES 4 + +#define CON_TEXTSIZE 163840 +typedef struct { + bool initialized; + + short 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 linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float xadjust; // for wide aspect screens + + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + + int vislines; // in scanlines + + vec4_t color; +} console_t; + +console_t con; + +cvar_t *con_conspeed; +cvar_t *con_height; +cvar_t *con_useShader; +cvar_t *con_colorRed; +cvar_t *con_colorGreen; +cvar_t *con_colorBlue; +cvar_t *con_colorAlpha; +cvar_t *con_versionStr; + +#define DEFAULT_CONSOLE_WIDTH 78 + + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) { + // Can't toggle the console when it's the only thing available + if ( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) { + return; + } + + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE ); +} + +/* +=================== +Con_ToggleMenu_f +=================== +*/ +void Con_ToggleMenu_f( void ) { + CL_KeyEvent( K_ESCAPE, true, Sys_Milliseconds() ); + CL_KeyEvent( K_ESCAPE, false, Sys_Milliseconds() ); +} + +/* +=================== +Con_MessageMode_f +=================== +-*/ +void Con_MessageMode_f (void) { + chat_playerNum = -1; + chat_team = false; + chat_admins = false; + chat_clans = false; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +==================== +Con_MessageMode2_f +==================== +*/ +void Con_MessageMode2_f (void) { + chat_playerNum = -1; + chat_team = true; + chat_admins = false; + chat_clans = false; + Field_Clear( &chatField ); + chatField.widthInChars = 25; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +=================== +Con_MessageMode3_f +=================== +*/ +void Con_MessageMode3_f (void) { + chat_playerNum = VM_Call( cls.cgame, CG_CROSSHAIR_PLAYER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = false; + chat_admins = false; + chat_clans = false; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +===================== +Con_MessageMode4_f +===================== +*/ +void Con_MessageMode4_f (void) { + chat_playerNum = VM_Call( cls.cgame, CG_LAST_ATTACKER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = false; + chat_admins = false; + chat_clans = false; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_MessageMode5_f +================ +*/ +void Con_MessageMode5_f (void) { + int i; + chat_playerNum = -1; + chat_team = false; + chat_admins = true; + chat_clans = false; + Field_Clear( &chatField ); + chatField.widthInChars = 25; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_MessageMode6_f +================ +*/ +void Con_MessageMode6_f (void) { + int i; + chat_playerNum = -1; + chat_team = false; + chat_admins = false; + chat_clans = true; + Field_Clear( &chatField ); + chatField.widthInChars = 25; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f (void) { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; + } + + Con_Bottom(); // go to end +} + + +/* +================ +Con_Dump_f + +Save the console contents out to a file +================ +*/ +void Con_Dump_f (void) +{ + int l, x, i; + short *line; + fileHandle_t f; + int bufferlen; + char *buffer; + char filename[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Com_Printf ("usage: condump \n"); + return; + } + + Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".txt" ); + + if (!COM_CompareExtension(filename, ".txt")) + { + Com_Printf("Con_Dump_f: Only the \".txt\" extension is supported by this command!\n"); + return; + } + + f = FS_FOpenFileWrite( filename ); + if (!f) + { + Com_Printf("ERROR: couldn't open %s.\n", filename); + return; + } + + Com_Printf("Dumped console text to %s.\n", filename ); + + // 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; + } +#ifdef _WIN32 + Q_strcat(buffer, bufferlen, "\r\n"); +#else + Q_strcat(buffer, bufferlen, "\n"); +#endif + FS_Write(buffer, strlen(buffer), f); + } + + Hunk_FreeTempMemory( buffer ); + FS_FCloseFile( f ); +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) { + Cmd_TokenizeString( NULL ); + CL_GameConsoleText( ); +} + + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + short tbuf[CON_TEXTSIZE]; + + width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + + if (width == con.linewidth) + return; + + if (width < 1) // video hasn't been initialized yet + { + width = DEFAULT_CONSOLE_WIDTH; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + for(i=0; iinteger ) + return; + + if (!con.initialized) { + con.color[0] = + con.color[1] = + con.color[2] = + con.color[3] = 1.0f; + con.linewidth = -1; + Con_CheckResize (); + con.initialized = true; + } + + if( !skipnotify && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE) ) + { + Cmd_SaveCmdContext( ); + + // feed the text to cgame + Cmd_TokenizeString( txt ); + CL_GameConsoleText( ); + + Cmd_RestoreCmdContext( ); + } + + color = ColorIndex(COLOR_WHITE); + + while ( (c = *((unsigned char *)txt)) != 0 ) + { + if ( Q_IsColorString( txt ) ) + { + color = ColorIndex( *(txt+1) ); + txt += 2; + continue; + } + + // count word length + for (l=0 ; l< con.linewidth ; l++) + { + if ( txt[l] <= ' ') + break; + } + + // word wrap + if (l != con.linewidth && (con.x + l >= con.linewidth) ) + Con_Linefeed(); + + txt++; + + switch (c) + { + case INDENT_MARKER: + break; + case '\n': + Con_Linefeed(); + break; + case '\r': + con.x = 0; + break; + default: // display character and advance + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = (color << 8) | c; + con.x++; + if(con.x >= con.linewidth) + Con_Linefeed(); + break; + } + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +Draw the editline after a ] prompt +================ +*/ +static void Con_DrawInput (void) +{ + int y; + + if ( clc.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) { + return; + } + + y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); + + re.SetColor( con.color ); + + SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); + + Field_Draw( &g_consoleField, + con.xadjust + 2 * SMALLCHAR_WIDTH, + y, + SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, + true, true ); +} + +/* +================ +Con_DrawSolidConsole + +Draws the console with the solid background +================ +*/ +static void Con_DrawSolidConsole( float frac ) +{ + int i, x, y; + int rows; + short *text; + int row; + int lines; + int currentColor; + vec4_t color; + + lines = cls.glconfig.vidHeight * frac; + if (lines <= 0) + return; + + if (lines > cls.glconfig.vidHeight ) + lines = cls.glconfig.vidHeight; + + // on wide screens, we will center the text + con.xadjust = 0; + SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); + + // draw the background + y = frac * SCREEN_HEIGHT; + if ( y < 1 ) + { + y = 0; + } + else if (con_useShader->integer) + { + SCR_DrawPic(0, 0, SCREEN_WIDTH, y, cls.consoleShader); + } + else + { + color[0] = con_colorRed->value; + color[1] = con_colorGreen->value; + color[2] = con_colorBlue->value; + color[3] = con_colorAlpha->value; + SCR_FillRect(0, 0, SCREEN_WIDTH, y, color); + } + + color[0] = 1; + color[1] = 0; + color[2] = 0; + color[3] = 1; + SCR_FillRect(0, y, SCREEN_WIDTH, 2, color); + + + // draw the version number + + re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + + i = strlen( Q3_VERSION ); + + for (x=0 ; x= con.totallines) { + // past scrollback wrap point + continue; + } + + text = con.text + (row % con.totallines)*con.linewidth; + + for (x=0 ; x>8 ) != currentColor ) { + currentColor = ColorIndexForNumber( text[x]>>8 ); + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); + } + } + + // draw the input prompt, user text, and cursor if desired + Con_DrawInput (); + + re.SetColor( NULL ); +} + + + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) { + // check for console width changes from a vid mode change + Con_CheckResize (); + + // if disconnected, render console full screen + if ( clc.state == CA_DISCONNECTED ) { + if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { + Con_DrawSolidConsole( 1.0 ); + return; + } + } + + // draw the chat line + if( clc.netchan.alternateProtocol == 2 && + ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) + { + int skip; + + if( chatField.buffer[0] == '/' || + chatField.buffer[0] == '\\' ) + { + SCR_DrawBigString( 8, 232, "Command:", 1.0f, qfalse ); + skip = 10; + } + else if( chat_team ) + { + SCR_DrawBigString( 8, 232, "Team Say:", 1.0f, qfalse ); + skip = 11; + } + else if( chat_admins ) + { + SCR_DrawBigString( 8, 232, "Admin Say:", 1.0f, qfalse ); + skip = 11; + } + else if( chat_clans ) + { + SCR_DrawBigString( 8, 232, "Clan Say:", 1.0f, qfalse ); + skip = 11; + } + else + { + SCR_DrawBigString( 8, 232, "Say:", 1.0f, qfalse ); + skip = 5; + } + + Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, 232, + SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue ); + } + + if ( con.displayFrac ) { + Con_DrawSolidConsole( con.displayFrac ); + } + + if( Key_GetCatcher( ) & ( KEYCATCH_UI | KEYCATCH_CGAME ) ) + return; +} +//================================================================ + +/* +================== +Con_RunConsole + +Scroll it up or down +================== +*/ +void Con_RunConsole (void) { + // decide on the destination height of the console + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + con.finalFrac = MAX(0.10, 0.01 * con_height->integer); + else + con.finalFrac = 0; // none visible + + // scroll towards the destination height + if (con.finalFrac < con.displayFrac) + { + con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; + if (con.finalFrac > con.displayFrac) + con.displayFrac = con.finalFrac; + + } + else if (con.finalFrac > con.displayFrac) + { + con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; + if (con.finalFrac < con.displayFrac) + con.displayFrac = con.finalFrac; + } + +} + + +void Con_PageUp( void ) { + con.display -= 2; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_PageDown( void ) { + con.display += 2; + if (con.display > con.current) { + con.display = con.current; + } +} + +void Con_Top( void ) { + con.display = con.totallines; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_Bottom( void ) { + con.display = con.current; +} + + +void Con_Close( void ) { + if ( !com_cl_running->integer ) { + return; + } + Field_Clear( &g_consoleField ); + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE ); + con.finalFrac = 0; // none visible + con.displayFrac = 0; +} diff --git a/src/client/cl_curl.cpp b/src/client/cl_curl.cpp new file mode 100644 index 0000000..0c81985 --- /dev/null +++ b/src/client/cl_curl.cpp @@ -0,0 +1,364 @@ +/* +=========================================================================== +Copyright (C) 2006 Tony J. White (tjw@tjw.org) +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" + +#ifdef USE_CURL_DLOPEN +#include "sys/sys_loadlib.h" + +cvar_t *cl_cURLLib; + +char* (*qcurl_version)(void); + +CURL* (*qcurl_easy_init)(void); +CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +CURLcode (*qcurl_easy_perform)(CURL *curl); +void (*qcurl_easy_cleanup)(CURL *curl); +CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +CURL* (*qcurl_easy_duphandle)(CURL *curl); +void (*qcurl_easy_reset)(CURL *curl); +const char *(*qcurl_easy_strerror)(CURLcode); + +CURLM* (*qcurl_multi_init)(void); +CURLMcode (*qcurl_multi_add_handle)(CURLM*, CURL*curl_handle); +CURLMcode (*qcurl_multi_remove_handle)(CURLM*, CURL*); +CURLMcode (*qcurl_multi_fdset)(CURLM*, fd_set* read_set, fd_set* write_set, fd_set* exc_set, int*); +CURLMcode (*qcurl_multi_perform)(CURLM*, int*); +CURLMcode (*qcurl_multi_cleanup)(CURLM*); +CURLMsg *(*qcurl_multi_info_read)(CURLM*, int*); +const char *(*qcurl_multi_strerror)(CURLMcode); + +struct curl_slist* (*qcurl_slist_append)(struct curl_slist*, const char*); +void (*qcurl_slist_free_all)(struct curl_slist*); + +CURLcode (*qcurl_global_init)(long); +void (*qcurl_global_cleanup)(void); + + +static void *cURLLib = NULL; + +/* +================= +GPA +================= +*/ +static void *GPA(const char *str) +{ + void* rv = Sys_LoadFunction(cURLLib, str); + if(!rv) + { + Com_Printf("Can't load symbol %s\n", str); + clc.cURLEnabled = false; + return NULL; + } + else + { + Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv); + return rv; + } +} +#endif /* USE_CURL_DLOPEN */ + +/* +================= +CL_cURL_Init +================= +*/ +bool CL_cURL_Init() +{ +#ifdef USE_CURL_DLOPEN + cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE | CVAR_PROTECTED); + + if(cURLLib) + return true; + + Com_Printf("Loading \"%s\"...", cl_cURLLib->string); + if( !(cURLLib = Sys_LoadDll(cl_cURLLib->string, true)) ) + { +#ifdef ALTERNATE_CURL_LIB + // On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too. + if( !(cURLLib = Sys_LoadDll(ALTERNATE_CURL_LIB, true)) ) +#endif + return false; + } + + clc.cURLEnabled = true; + + qcurl_version = (decltype(qcurl_version)) GPA("curl_version"); + + qcurl_easy_init = (decltype(qcurl_easy_init)) GPA("curl_easy_init"); + qcurl_easy_setopt = (decltype(qcurl_easy_setopt)) GPA("curl_easy_setopt"); + qcurl_easy_perform = (decltype(qcurl_easy_perform)) GPA("curl_easy_perform"); + qcurl_easy_cleanup = (decltype(qcurl_easy_cleanup)) GPA("curl_easy_cleanup"); + qcurl_easy_getinfo = (decltype(qcurl_easy_getinfo)) GPA("curl_easy_getinfo"); + qcurl_easy_duphandle = (decltype(qcurl_easy_duphandle)) GPA("curl_easy_duphandle"); + qcurl_easy_reset = (decltype(qcurl_easy_reset)) GPA("curl_easy_reset"); + qcurl_easy_strerror = (decltype(qcurl_easy_strerror)) GPA("curl_easy_strerror"); + + qcurl_multi_init = (decltype(qcurl_multi_init)) GPA("curl_multi_init"); + qcurl_multi_add_handle = (decltype(qcurl_multi_add_handle)) GPA("curl_multi_add_handle"); + qcurl_multi_remove_handle = (decltype(qcurl_multi_remove_handle)) GPA("curl_multi_remove_handle"); + qcurl_multi_fdset = (decltype(qcurl_multi_fdset)) GPA("curl_multi_fdset"); + qcurl_multi_perform = (decltype(qcurl_multi_perform)) GPA("curl_multi_perform"); + qcurl_multi_cleanup = (decltype(qcurl_multi_cleanup)) GPA("curl_multi_cleanup"); + qcurl_multi_info_read = (decltype(qcurl_multi_info_read)) GPA("curl_multi_info_read"); + qcurl_multi_strerror = (decltype(qcurl_multi_strerror)) GPA("curl_multi_strerror"); + qcurl_slist_append = (decltype(qcurl_slist_append)) GPA("curl_slist_append"); + qcurl_slist_free_all = (decltype(qcurl_slist_free_all)) GPA("curl_slist_free_all"); + qcurl_global_init = (decltype(qcurl_global_init)) GPA("curl_global_init"); + qcurl_global_cleanup = (decltype(qcurl_global_cleanup)) GPA("curl_global_cleanup"); + + if(!clc.cURLEnabled) + { + CL_cURL_Shutdown(); + Com_Printf("FAIL One or more symbols not found\n"); + return false; + } + Com_Printf("OK\n"); + + return true; +#else + clc.cURLEnabled = true; + return true; +#endif /* USE_CURL_DLOPEN */ +} + +/* +================= +CL_cURL_Shutdown +================= +*/ +void CL_cURL_Shutdown( void ) +{ + CL_cURL_Cleanup(); +#ifdef USE_CURL_DLOPEN + if(cURLLib) + { + Sys_UnloadLibrary(cURLLib); + cURLLib = NULL; + } + qcurl_easy_init = NULL; + qcurl_easy_setopt = NULL; + qcurl_easy_perform = NULL; + qcurl_easy_cleanup = NULL; + qcurl_easy_getinfo = NULL; + qcurl_easy_duphandle = NULL; + qcurl_easy_reset = NULL; + + qcurl_multi_init = NULL; + qcurl_multi_add_handle = NULL; + qcurl_multi_remove_handle = NULL; + qcurl_multi_fdset = NULL; + qcurl_multi_perform = NULL; + qcurl_multi_cleanup = NULL; + qcurl_multi_info_read = NULL; + qcurl_multi_strerror = NULL; +#endif /* USE_CURL_DLOPEN */ +} + +void CL_cURL_Cleanup(void) +{ + if(clc.downloadCURLM) { + CURLMcode result; + + if(clc.downloadCURL) { + result = qcurl_multi_remove_handle(clc.downloadCURLM, + clc.downloadCURL); + if(result != CURLM_OK) { + Com_DPrintf("qcurl_multi_remove_handle failed: %s\n", qcurl_multi_strerror(result)); + } + qcurl_easy_cleanup(clc.downloadCURL); + } + result = qcurl_multi_cleanup(clc.downloadCURLM); + if(result != CURLM_OK) { + Com_DPrintf("CL_cURL_Cleanup: qcurl_multi_cleanup failed: %s\n", qcurl_multi_strerror(result)); + } + clc.downloadCURLM = NULL; + clc.downloadCURL = NULL; + } + else if(clc.downloadCURL) { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + } +} + +static int CL_cURL_CallbackProgress( void *dummy, double dltotal, double dlnow, + double ultotal, double ulnow ) +{ + clc.downloadSize = (int)dltotal; + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + clc.downloadCount = (int)dlnow; + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + return 0; +} + +static size_t CL_cURL_CallbackWrite(void *buffer, size_t size, size_t nmemb, + void *stream) +{ + FS_Write( buffer, size*nmemb, ((fileHandle_t*)stream)[0] ); + return size*nmemb; +} + +CURLcode qcurl_easy_setopt_warn(CURL *curl, CURLoption option, ...) +{ + CURLcode result; + + va_list argp; + va_start(argp, option); + + if(option < CURLOPTTYPE_OBJECTPOINT) { + long longValue = va_arg(argp, long); + result = qcurl_easy_setopt(curl, option, longValue); + } else if(option < CURLOPTTYPE_OFF_T) { + void *pointerValue = va_arg(argp, void *); + result = qcurl_easy_setopt(curl, option, pointerValue); + } else { + curl_off_t offsetValue = va_arg(argp, curl_off_t); + result = qcurl_easy_setopt(curl, option, offsetValue); + } + + if(result != CURLE_OK) { + Com_DPrintf("qcurl_easy_setopt failed: %s\n", qcurl_easy_strerror(result)); + } + va_end(argp); + + return result; +} + +void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ) +{ + CURLMcode result; + + clc.cURLUsed = true; + Com_Printf("URL: %s\n", remoteURL); + Com_DPrintf("***** CL_cURL_BeginDownload *****\n" + "Localname: %s\n" + "RemoteURL: %s\n" + "****************************\n", localName, remoteURL); + CL_cURL_Cleanup(); + Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL)); + Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName)); + Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName), + "%s.tmp", localName); + + // Set so UI gets access to it + Cvar_Set("cl_downloadName", localName); + Cvar_Set("cl_downloadSize", "0"); + Cvar_Set("cl_downloadCount", "0"); + Cvar_SetValue("cl_downloadTime", cls.realtime); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + clc.downloadCURL = qcurl_easy_init(); + if(!clc.downloadCURL) { + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_easy_init() " + "failed"); + return; + } + clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName); + if(!clc.download) { + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open " + "%s for writing", clc.downloadTempName); + return; + } + + if(com_developer->integer) + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_VERBOSE, 1); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_URL, clc.downloadURL); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_REFERER, va("Tremulous://%s", NET_AdrToString(clc.serverAddress))); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s", Q3_VERSION, qcurl_version())); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_WRITEFUNCTION, CL_cURL_CallbackWrite); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_NOPROGRESS, 0); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION, CL_cURL_CallbackProgress); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_FAILONERROR, 1); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_MAXREDIRS, 5); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS ); + clc.downloadCURLM = qcurl_multi_init(); + if(!clc.downloadCURLM) + { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_multi_init() " + "failed"); + return; + } + + result = qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL); + if(result != CURLM_OK) + { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + Com_Error(ERR_DROP,"CL_cURL_BeginDownload: qcurl_multi_add_handle() failed: %s", qcurl_multi_strerror(result)); + return; + } + + if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) && !clc.cURLDisconnected) + { + CL_AddReliableCommand("disconnect", true); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + clc.cURLDisconnected = true; + } +} + +void CL_cURL_PerformDownload(void) +{ + CURLMcode res; + CURLMsg *msg; + int c; + int i = 0; + + res = qcurl_multi_perform(clc.downloadCURLM, &c); + while(res == CURLM_CALL_MULTI_PERFORM && i < 100) { + res = qcurl_multi_perform(clc.downloadCURLM, &c); + i++; + } + if(res == CURLM_CALL_MULTI_PERFORM) + return; + msg = qcurl_multi_info_read(clc.downloadCURLM, &c); + if(msg == NULL) { + return; + } + FS_FCloseFile(clc.download); + if(msg->msg == CURLMSG_DONE && msg->data.result == CURLE_OK) { + FS_SV_Rename(clc.downloadTempName, clc.downloadName, false); + clc.downloadRestart = true; + } + else { + long code; + + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code); + Com_Error(ERR_DROP, "Download Error: %s Code: %ld URL: %s", + qcurl_easy_strerror(msg->data.result), + code, clc.downloadURL); + } + + CL_NextDownload(); +} diff --git a/src/client/cl_curl.h b/src/client/cl_curl.h new file mode 100644 index 0000000..2e3b4e3 --- /dev/null +++ b/src/client/cl_curl.h @@ -0,0 +1,101 @@ +/* +=========================================================================== +Copyright (C) 2006 Tony J. White (tjw@tjw.org) +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __QCURL_H__ +#define __QCURL_H__ + +#ifdef USE_LOCAL_HEADERS +#include "curl/curl.h" +#else +#include +#endif + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +#ifdef USE_CURL_DLOPEN +#ifdef _WIN32 + #define DEFAULT_CURL_LIB "libcurl-4.dll" + #define ALTERNATE_CURL_LIB "libcurl-3.dll" +#elif defined(__APPLE__) + #define DEFAULT_CURL_LIB "libcurl.dylib" +#else + #define DEFAULT_CURL_LIB "libcurl.so.4" + #define ALTERNATE_CURL_LIB "libcurl.so.3" +#endif + +extern cvar_t *cl_cURLLib; + +extern char* (*qcurl_version)(void); +extern CURL* (*qcurl_easy_init)(void); +extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +extern CURLcode (*qcurl_easy_perform)(CURL *curl); +extern void (*qcurl_easy_cleanup)(CURL *curl); +extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +extern void (*qcurl_easy_reset)(CURL *curl); +extern const char* (*qcurl_easy_strerror)(CURLcode); +extern CURLM* (*qcurl_multi_init)(void); +extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle); +extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle); +extern CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd); +extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles); +extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +extern CURLMsg* (*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue); +extern const char* (*qcurl_multi_strerror)(CURLMcode); +extern struct curl_slist* (*qcurl_slist_append)(struct curl_slist *, const char *); +extern void (*qcurl_slist_free_all)(struct curl_slist *); +extern CURLcode (*qcurl_global_init)(long flags); +extern void (*qcurl_global_cleanup)(void); + +#else +#define qcurl_version curl_version +#define qcurl_easy_init curl_easy_init +#define qcurl_easy_setopt curl_easy_setopt +#define qcurl_easy_perform curl_easy_perform +#define qcurl_easy_cleanup curl_easy_cleanup +#define qcurl_easy_getinfo curl_easy_getinfo +#define qcurl_easy_duphandle curl_easy_duphandle +#define qcurl_easy_reset curl_easy_reset +#define qcurl_easy_strerror curl_easy_strerror +#define qcurl_multi_init curl_multi_init +#define qcurl_multi_add_handle curl_multi_add_handle +#define qcurl_multi_remove_handle curl_multi_remove_handle +#define qcurl_multi_fdset curl_multi_fdset +#define qcurl_multi_perform curl_multi_perform +#define qcurl_multi_cleanup curl_multi_cleanup +#define qcurl_multi_info_read curl_multi_info_read +#define qcurl_multi_strerror curl_multi_strerror +#define qcurl_slist_append curl_slist_append +#define qcurl_slist_free_all curl_slist_free_all +#define qcurl_global_init curl_global_init +#define qcurl_global_cleanup curl_global_cleanup +#endif + +bool CL_cURL_Init( void ); +void CL_cURL_Shutdown( void ); +void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ); +void CL_cURL_PerformDownload( void ); +void CL_cURL_Cleanup( void ); + +#endif // __QCURL_H__ diff --git a/src/client/cl_input.cpp b/src/client/cl_input.cpp new file mode 100644 index 0000000..e134646 --- /dev/null +++ b/src/client/cl_input.cpp @@ -0,0 +1,1194 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// cl.input.c -- builds an intended movement command to send to the server + +#include "client.h" + +unsigned frame_msec; +int old_com_frameTime; + +/* +=============================================================================== + +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 argv(1) so it can be matched up with the release. + +argv(2) will be set to the time the event happened, which allows exact +control even at low framerates when the down and up events may both get qued +at the same time. + +=============================================================================== +*/ + +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; +kbutton_t in_up, in_down; + +#ifdef USE_VOIP +kbutton_t in_voiprecord; +#endif + +kbutton_t in_buttons[16]; + +bool in_mlooking; + +static void IN_CenterView(void) +{ + cl.viewangles[PITCH] = -SHORT2ANGLE( (clc.netchan.alternateProtocol == 2 + ? cl.snap.alternatePs.delta_angles + : cl.snap.ps.delta_angles)[PITCH] ); +} + +static void IN_MLookDown(void) +{ + in_mlooking = true; +} + +static void IN_MLookUp(void) +{ + in_mlooking = false; + if (!cl_freelook->integer) + { + IN_CenterView(); + } +} + +static void IN_KeyDown(kbutton_t *b) +{ + int k; + + const char *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->active) + { + return; // still down + } + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + b->downtime = atoi(c); + + b->active = true; + b->wasPressed = true; +} + +static void IN_KeyUp(kbutton_t *b) +{ + int k; + unsigned uptime; + + const char *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->active = false; + 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 + } + + b->active = false; + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + uptime = atoi(c); + if (uptime) + { + b->msec += uptime - b->downtime; + } + else + { + b->msec += frame_msec / 2; + } + + b->active = false; +} + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +static float CL_KeyState(kbutton_t *key) +{ + float val; + int msec; + + msec = key->msec; + key->msec = 0; + + if (key->active) + { + // still down + if (!key->downtime) + { + msec = com_frameTime; + } + else + { + msec += com_frameTime - key->downtime; + } + key->downtime = com_frameTime; + } + +#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; +} + +static void IN_UpDown(void) { IN_KeyDown(&in_up); } +static void IN_UpUp(void) { IN_KeyUp(&in_up); } +static void IN_DownDown(void) { IN_KeyDown(&in_down); } +static void IN_DownUp(void) { IN_KeyUp(&in_down); } +static void IN_LeftDown(void) { IN_KeyDown(&in_left); } +static void IN_LeftUp(void) { IN_KeyUp(&in_left); } +static void IN_RightDown(void) { IN_KeyDown(&in_right); } +static void IN_RightUp(void) { IN_KeyUp(&in_right); } +static void IN_ForwardDown(void) { IN_KeyDown(&in_forward); } +static void IN_ForwardUp(void) { IN_KeyUp(&in_forward); } +static void IN_BackDown(void) { IN_KeyDown(&in_back); } +static void IN_BackUp(void) { IN_KeyUp(&in_back); } +static void IN_LookupDown(void) { IN_KeyDown(&in_lookup); } +static void IN_LookupUp(void) { IN_KeyUp(&in_lookup); } +static void IN_LookdownDown(void) { IN_KeyDown(&in_lookdown); } +static void IN_LookdownUp(void) { IN_KeyUp(&in_lookdown); } +static void IN_MoveleftDown(void) { IN_KeyDown(&in_moveleft); } +static void IN_MoveleftUp(void) { IN_KeyUp(&in_moveleft); } +static void IN_MoverightDown(void) { IN_KeyDown(&in_moveright); } +static void IN_MoverightUp(void) { IN_KeyUp(&in_moveright); } +static void IN_SpeedDown(void) { IN_KeyDown(&in_speed); } +static void IN_SpeedUp(void) { IN_KeyUp(&in_speed); } +static void IN_StrafeDown(void) { IN_KeyDown(&in_strafe); } +static void IN_StrafeUp(void) { IN_KeyUp(&in_strafe); } + +#ifdef USE_VOIP +static void IN_VoipRecordDown(void) +{ + IN_KeyDown(&in_voiprecord); + Cvar_Set("cl_voipSend", "1"); +} + +static void IN_VoipRecordUp(void) +{ + IN_KeyUp(&in_voiprecord); + Cvar_Set("cl_voipSend", "0"); +} +#endif + +static void IN_Button0Down(void) { IN_KeyDown(&in_buttons[0]); } +static void IN_Button0Up(void) { IN_KeyUp(&in_buttons[0]); } +static void IN_Button1Down(void) { IN_KeyDown(&in_buttons[1]); } +static void IN_Button1Up(void) { IN_KeyUp(&in_buttons[1]); } +static void IN_Button2Down(void) { IN_KeyDown(&in_buttons[2]); } +static void IN_Button2Up(void) { IN_KeyUp(&in_buttons[2]); } +static void IN_Button3Down(void) { IN_KeyDown(&in_buttons[3]); } +static void IN_Button3Up(void) { IN_KeyUp(&in_buttons[3]); } +static void IN_Button4Down(void) { IN_KeyDown(&in_buttons[4]); } +static void IN_Button4Up(void) { IN_KeyUp(&in_buttons[4]); } +static void IN_Button5Down(void) { IN_KeyDown(&in_buttons[5]); } +static void IN_Button5Up(void) { IN_KeyUp(&in_buttons[5]); } +static void IN_Button6Down(void) { IN_KeyDown(&in_buttons[6]); } +static void IN_Button6Up(void) { IN_KeyUp(&in_buttons[6]); } +static void IN_Button7Down(void) { IN_KeyDown(&in_buttons[7]); } +static void IN_Button7Up(void) { IN_KeyUp(&in_buttons[7]); } +static void IN_Button8Down(void) { IN_KeyDown(&in_buttons[8]); } +static void IN_Button8Up(void) { IN_KeyUp(&in_buttons[8]); } +static void IN_Button9Down(void) { IN_KeyDown(&in_buttons[9]); } +static void IN_Button9Up(void) { IN_KeyUp(&in_buttons[9]); } +static void IN_Button10Down(void) { IN_KeyDown(&in_buttons[10]); } +static void IN_Button10Up(void) { IN_KeyUp(&in_buttons[10]); } +static void IN_Button11Down(void) { IN_KeyDown(&in_buttons[11]); } +static void IN_Button11Up(void) { IN_KeyUp(&in_buttons[11]); } +static void IN_Button12Down(void) { IN_KeyDown(&in_buttons[12]); } +static void IN_Button12Up(void) { IN_KeyUp(&in_buttons[12]); } +static void IN_Button13Down(void) { IN_KeyDown(&in_buttons[13]); } +static void IN_Button13Up(void) { IN_KeyUp(&in_buttons[13]); } +static void IN_Button14Down(void) { IN_KeyDown(&in_buttons[14]); } +static void IN_Button14Up(void) { IN_KeyUp(&in_buttons[14]); } +static void IN_Button15Down(void) { IN_KeyDown(&in_buttons[15]); } +static void IN_Button15Up(void) { IN_KeyUp(&in_buttons[15]); } + +//========================================================================== + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +static void CL_AdjustAngles(void) +{ + float speed; + + if (in_speed.active) + { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } + else + { + speed = 0.001 * cls.frametime; + } + + if (!in_strafe.active) + { + cl.viewangles[YAW] -= speed * cl_yawspeed->value * CL_KeyState(&in_right); + cl.viewangles[YAW] += speed * cl_yawspeed->value * CL_KeyState(&in_left); + } + + cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * CL_KeyState(&in_lookup); + cl.viewangles[PITCH] += speed * cl_pitchspeed->value * CL_KeyState(&in_lookdown); +} + +/* +================ +CL_KeyMove + +Sets the usercmd_t based on key states +================ +*/ +static void CL_KeyMove(usercmd_t *cmd) +{ + int movespeed; + int forward, side, up; + + // + // adjust for speed key / running + // the walking flag is to keep animations consistant + // even during acceleration and develeration + // + if (in_speed.active ^ cl_run->integer) + { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } + else + { + cmd->buttons |= BUTTON_WALKING; + movespeed = 64; + } + + forward = 0; + side = 0; + up = 0; + if (in_strafe.active) + { + side += movespeed * CL_KeyState(&in_right); + side -= movespeed * CL_KeyState(&in_left); + } + + side += movespeed * CL_KeyState(&in_moveright); + side -= movespeed * CL_KeyState(&in_moveleft); + + up += movespeed * CL_KeyState(&in_up); + up -= movespeed * CL_KeyState(&in_down); + + forward += movespeed * CL_KeyState(&in_forward); + forward -= movespeed * CL_KeyState(&in_back); + + cmd->forwardmove = ClampChar(forward); + cmd->rightmove = ClampChar(side); + cmd->upmove = ClampChar(up); +} + +/* +================= +CL_MouseEvent +================= +*/ +void CL_MouseEvent(int dx, int dy, int time) +{ + if (Key_GetCatcher() & KEYCATCH_UI) + { + VM_Call(cls.ui, UI_MOUSE_EVENT, dx, dy); + } + else if (Key_GetCatcher() & KEYCATCH_CGAME) + { + VM_Call(cls.cgame, CG_MOUSE_EVENT, dx, dy); + } + else + { + cl.mouseDx[cl.mouseIndex] += dx; + cl.mouseDy[cl.mouseIndex] += dy; + } +} + +/* +================= +CL_JoystickEvent + +Joystick values stay set until changed +================= +*/ +void CL_JoystickEvent(int axis, int value, int time) +{ + if (axis < 0 || axis >= MAX_JOYSTICK_AXIS) + { + Com_Error(ERR_DROP, "CL_JoystickEvent: bad axis %i", axis); + } + cl.joystickAxis[axis] = value; +} + +/* +================= +CL_JoystickMove +================= +*/ +static void CL_JoystickMove(usercmd_t *cmd) +{ + float anglespeed; + + float yaw = j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]; + float right = j_side->value * cl.joystickAxis[j_side_axis->integer]; + float forward = j_forward->value * cl.joystickAxis[j_forward_axis->integer]; + float pitch = j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]; + float up = j_up->value * cl.joystickAxis[j_up_axis->integer]; + + if (!(in_speed.active ^ cl_run->integer)) + { + cmd->buttons |= BUTTON_WALKING; + } + + if (in_speed.active) + { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } + else + { + anglespeed = 0.001 * cls.frametime; + } + + if (!in_strafe.active) + { + cl.viewangles[YAW] += anglespeed * yaw; + cmd->rightmove = ClampChar(cmd->rightmove + (int)right); + } + else + { + cl.viewangles[YAW] += anglespeed * right; + cmd->rightmove = ClampChar(cmd->rightmove + (int)yaw); + } + + if (in_mlooking) + { + cl.viewangles[PITCH] += anglespeed * forward; + cmd->forwardmove = ClampChar(cmd->forwardmove + (int)pitch); + } + else + { + cl.viewangles[PITCH] += anglespeed * pitch; + cmd->forwardmove = ClampChar(cmd->forwardmove + (int)forward); + } + + cmd->upmove = ClampChar(cmd->upmove + (int)up); +} + +/* +================= +CL_MouseMove +================= +*/ + +static void CL_MouseMove(usercmd_t *cmd) +{ + float mx, my; + + // allow mouse smoothing + if (m_filter->integer) + { + mx = (cl.mouseDx[0] + cl.mouseDx[1]) * 0.5f; + my = (cl.mouseDy[0] + cl.mouseDy[1]) * 0.5f; + } + else + { + mx = cl.mouseDx[cl.mouseIndex]; + my = cl.mouseDy[cl.mouseIndex]; + } + + cl.mouseIndex ^= 1; + cl.mouseDx[cl.mouseIndex] = 0; + cl.mouseDy[cl.mouseIndex] = 0; + + if (mx == 0.0f && my == 0.0f) return; + + if (cl_mouseAccel->value != 0.0f) + { + if (cl_mouseAccelStyle->integer == 0) + { + float accelSensitivity; + float rate; + + rate = sqrt(mx * mx + my * my) / (float)frame_msec; + + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; + mx *= accelSensitivity; + my *= accelSensitivity; + + if (cl_showMouseRate->integer) Com_Printf("rate: %f, accelSensitivity: %f\n", rate, accelSensitivity); + } + else + { + float rate[2]; + float power[2]; + + // sensitivity remains pretty much unchanged at low speeds + // cl_mouseAccel is a power value to how the acceleration is shaped + // cl_mouseAccelOffset is the rate for which the acceleration will have doubled the non accelerated + // amplification + // NOTE: decouple the config cvars for independent acceleration setup along X and Y? + + rate[0] = fabs(mx) / (float)frame_msec; + rate[1] = fabs(my) / (float)frame_msec; + power[0] = powf(rate[0] / cl_mouseAccelOffset->value, cl_mouseAccel->value); + power[1] = powf(rate[1] / cl_mouseAccelOffset->value, cl_mouseAccel->value); + + mx = cl_sensitivity->value * (mx + ((mx < 0) ? -power[0] : power[0]) * cl_mouseAccelOffset->value); + my = cl_sensitivity->value * (my + ((my < 0) ? -power[1] : power[1]) * cl_mouseAccelOffset->value); + + if (cl_showMouseRate->integer) + Com_Printf("ratex: %f, ratey: %f, powx: %f, powy: %f\n", rate[0], rate[1], power[0], power[1]); + } + } + else + { + mx *= cl_sensitivity->value; + my *= cl_sensitivity->value; + } + + // ingame FOV + mx *= cl.cgameSensitivity; + my *= cl.cgameSensitivity; + + // add mouse X/Y movement to cmd + if (in_strafe.active) + cmd->rightmove = ClampChar(cmd->rightmove + m_side->value * mx); + else + cl.viewangles[YAW] -= m_yaw->value * mx; + + if ((in_mlooking || cl_freelook->integer) && !in_strafe.active) + cl.viewangles[PITCH] += m_pitch->value * my; + else + cmd->forwardmove = ClampChar(cmd->forwardmove - m_forward->value * my); +} + +/* +============== +CL_CmdButtons +============== +*/ +static void CL_CmdButtons(usercmd_t *cmd) +{ + int i; + + // + // figure button bits + // send a button bit even if the key was pressed and released in + // less than a frame + // + for (i = 0; i < 15; i++) + { + if (in_buttons[i].active || in_buttons[i].wasPressed) + { + cmd->buttons |= 1 << i; + } + in_buttons[i].wasPressed = false; + } + + if (Key_GetCatcher()) + { + cmd->buttons |= BUTTON_TALK; + } + + // allow the game to know if any key at all is + // currently pressed, even if it isn't bound to anything + if (anykeydown && Key_GetCatcher() == 0) + { + cmd->buttons |= BUTTON_ANY; + } +} + +/* +============== +CL_FinishMove +============== +*/ +static void CL_FinishMove(usercmd_t *cmd) +{ + int i; + + // copy the state that the cgame is currently sending + cmd->weapon = cl.cgameUserCmdValue; + + // send the current server time so the amount of movement + // can be determined without allowing cheating + cmd->serverTime = cl.serverTime; + + for (i = 0; i < 3; i++) + { + cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); + } +} + +/* +================= +CL_CreateCmd +================= +*/ +static usercmd_t CL_CreateCmd(void) +{ + usercmd_t cmd; + vec3_t oldAngles; + + VectorCopy(cl.viewangles, oldAngles); + + // keyboard angle adjustment + CL_AdjustAngles(); + + ::memset(&cmd, 0, sizeof(cmd)); + + CL_CmdButtons(&cmd); + + // get basic movement from keyboard + CL_KeyMove(&cmd); + + // get basic movement from mouse + CL_MouseMove(&cmd); + + // get basic movement from joystick + CL_JoystickMove(&cmd); + + // check to make sure the angles haven't wrapped + if (cl.viewangles[PITCH] - oldAngles[PITCH] > 90) + { + cl.viewangles[PITCH] = oldAngles[PITCH] + 90; + } + else if (oldAngles[PITCH] - cl.viewangles[PITCH] > 90) + { + cl.viewangles[PITCH] = oldAngles[PITCH] - 90; + } + + // store out the final values + CL_FinishMove(&cmd); + + // draw debug graphs of turning for mouse testing + if (cl_debugMove->integer) + { + if (cl_debugMove->integer == 1) + { + SCR_DebugGraph(fabs(cl.viewangles[YAW] - oldAngles[YAW])); + } + if (cl_debugMove->integer == 2) + { + SCR_DebugGraph(fabs(cl.viewangles[PITCH] - oldAngles[PITCH])); + } + } + + return cmd; +} + +/* +================= +CL_CreateNewCommands + +Create a new usercmd_t structure for this frame +================= +*/ +static void CL_CreateNewCommands(void) +{ + int cmdNum; + + // no need to create usercmds until we have a gamestate + if (clc.state < CA_PRIMED) + { + return; + } + + frame_msec = com_frameTime - old_com_frameTime; + + // if running over 1000fps, act as if each frame is 1ms + // prevents divisions by zero + if (frame_msec < 1) + { + frame_msec = 1; + } + + // if running less than 5fps, truncate the extra time to prevent + // unexpected moves after a hitch + if (frame_msec > 200) + { + frame_msec = 200; + } + old_com_frameTime = com_frameTime; + + // generate a command for this frame + cl.cmdNumber++; + cmdNum = cl.cmdNumber & CMD_MASK; + cl.cmds[cmdNum] = CL_CreateCmd(); +} + +/* +================= +CL_ReadyToSendPacket + +Returns false if we are over the maxpackets limit +and should choke back the bandwidth a bit by not sending +a packet this frame. All the commands will still get +delivered in the next packet, but saving a header and +getting more delta compression will reduce total bandwidth. +================= +*/ +static bool CL_ReadyToSendPacket(void) +{ + int oldPacketNum; + int delta; + + // don't send anything if playing back a demo + if (clc.demoplaying || clc.state == CA_CINEMATIC) + { + return false; + } + + // If we are downloading, we send no less than 50ms between packets + if (*clc.downloadTempName && cls.realtime - clc.lastPacketSentTime < 50) + { + return false; + } + + // if we don't have a valid gamestate yet, only send + // one packet a second + if (clc.state != CA_ACTIVE && clc.state != CA_PRIMED && !*clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 1000) + { + return false; + } + + // send every frame for loopbacks + if (clc.netchan.remoteAddress.type == NA_LOOPBACK) + { + return true; + } + + // send every frame for LAN + if (cl_lanForcePackets->integer && Sys_IsLANAddress(clc.netchan.remoteAddress)) + { + return true; + } + + // check for exceeding cl_maxpackets + if (cl_maxpackets->integer < 15) + { + Cvar_Set("cl_maxpackets", "15"); + } + else if (cl_maxpackets->integer > 125) + { + Cvar_Set("cl_maxpackets", "125"); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK; + delta = cls.realtime - cl.outPackets[oldPacketNum].p_realtime; + if (delta < 1000 / cl_maxpackets->integer) + { + // the accumulated commands will go out in the next packet + return false; + } + + return true; +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds + +During normal gameplay, a client packet will contain something like: + +4 sequence number +2 qport +4 serverid +4 acknowledged sequence number +4 clc.serverCommandSequence + +1 clc_move or clc_moveNoDelta +1 command count + + +=================== +*/ +void CL_WritePacket(void) +{ + msg_t buf; + byte data[MAX_MSGLEN]; + int i, j; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int packetNum; + int oldPacketNum; + int count, key; + + // don't send anything if playing back a demo + if (clc.demoplaying || clc.state == CA_CINEMATIC) + { + return; + } + + ::memset(&nullcmd, 0, sizeof(nullcmd)); + oldcmd = &nullcmd; + + MSG_Init(&buf, data, sizeof(data)); + + MSG_Bitstream(&buf); + // write the current serverId so the server + // can tell if this is from the current gameState + MSG_WriteLong(&buf, cl.serverId); + + // write the last message we received, which can + // be used for delta compression, and is also used + // to tell if we dropped a gamestate + MSG_WriteLong(&buf, clc.serverMessageSequence); + + // write the last reliable message we received + MSG_WriteLong(&buf, clc.serverCommandSequence); + + // write any unacknowledged clientCommands + for (i = clc.reliableAcknowledge + 1; i <= clc.reliableSequence; i++) + { + MSG_WriteByte(&buf, clc_clientCommand); + MSG_WriteLong(&buf, i); + MSG_WriteString(&buf, clc.reliableCommands[i & (MAX_RELIABLE_COMMANDS - 1)]); + } + + // we want to send all the usercmds that were generated in the last + // few packet, so even if a couple packets are dropped in a row, + // all the cmds will make it to the server + if (cl_packetdup->integer < 0) + { + Cvar_Set("cl_packetdup", "0"); + } + else if (cl_packetdup->integer > 5) + { + Cvar_Set("cl_packetdup", "5"); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; + count = cl.cmdNumber - cl.outPackets[oldPacketNum].p_cmdNumber; + if (count > MAX_PACKET_USERCMDS) + { + count = MAX_PACKET_USERCMDS; + Com_Printf("MAX_PACKET_USERCMDS\n"); + } + +#ifdef USE_VOIP + if (clc.voipOutgoingDataSize > 0) + { + if ((clc.voipFlags & VOIP_SPATIAL) || Com_IsVoipTarget(clc.voipTargets, sizeof(clc.voipTargets), -1)) + { + if (clc.netchan.alternateProtocol != 0) + { + MSG_WriteByte(&buf, clc_EOF); + } + MSG_WriteByte(&buf, clc_voipSpeex); + if (clc.netchan.alternateProtocol != 0) + { + MSG_WriteByte(&buf, clc_voipSpeex + 1); + } + MSG_WriteByte(&buf, clc.voipOutgoingGeneration); + MSG_WriteLong(&buf, clc.voipOutgoingSequence); + MSG_WriteByte(&buf, clc.voipOutgoingDataFrames); + if (clc.netchan.alternateProtocol == 0) + { + MSG_WriteData(&buf, clc.voipTargets, sizeof(clc.voipTargets)); + MSG_WriteByte(&buf, clc.voipFlags); + } + else + { + MSG_WriteLong(&buf, clc.voipTargets[0] | (clc.voipTargets[1] << 8) | (clc.voipTargets[2] << 16) | + ((clc.voipTargets[3] & 0x7F) << 24)); + MSG_WriteLong(&buf, (clc.voipTargets[3] >> 7) | (clc.voipTargets[4] << 1) | (clc.voipTargets[5] << 9) | + (clc.voipTargets[6] << 17) | ((clc.voipTargets[7] & 0x3F) << 25)); + MSG_WriteLong(&buf, clc.voipTargets[7] >> 6); + } + MSG_WriteShort(&buf, clc.voipOutgoingDataSize); + MSG_WriteData(&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); + + // If we're recording a demo, we have to fake a server packet with + // this VoIP data so it gets to disk; the server doesn't send it + // back to us, and we might as well eliminate concerns about dropped + // and misordered packets here. + if (clc.demorecording && !clc.demowaiting) + { + const int voipSize = clc.voipOutgoingDataSize; + msg_t fakemsg; + byte fakedata[MAX_MSGLEN]; + MSG_Init(&fakemsg, fakedata, sizeof(fakedata)); + MSG_Bitstream(&fakemsg); + MSG_WriteLong(&fakemsg, clc.reliableAcknowledge); + MSG_WriteByte(&fakemsg, svc_voipOpus); + MSG_WriteShort(&fakemsg, clc.clientNum); + MSG_WriteByte(&fakemsg, clc.voipOutgoingGeneration); + MSG_WriteLong(&fakemsg, clc.voipOutgoingSequence); + MSG_WriteByte(&fakemsg, clc.voipOutgoingDataFrames); + MSG_WriteShort(&fakemsg, clc.voipOutgoingDataSize); + if (clc.netchan.alternateProtocol == 0) + { + MSG_WriteBits(&fakemsg, clc.voipFlags, VOIP_FLAGCNT); + } + MSG_WriteData(&fakemsg, clc.voipOutgoingData, voipSize); + MSG_WriteByte(&fakemsg, svc_EOF); + CL_WriteDemoMessage(&fakemsg, 0); + } + + clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; + } + else + { + // We have data, but no targets. Silently discard all data + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; + } + } +#endif + + if (count >= 1) + { + if (cl_showSend->integer) + { + Com_Printf("(%i)", count); + } + + // begin a client move command + if (cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum) + { + MSG_WriteByte(&buf, clc_moveNoDelta); + } + else + { + MSG_WriteByte(&buf, clc_move); + } + + // write the command count + MSG_WriteByte(&buf, count); + + // use the checksum feed in the key + key = clc.checksumFeed; + // also use the message acknowledge + key ^= clc.serverMessageSequence; + // also use the last acknowledged server command in the key + key ^= MSG_HashKey(clc.netchan.alternateProtocol, + clc.serverCommands[clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS - 1)], 32); + + // write all the commands, including the predicted command + for (i = 0; i < count; i++) + { + j = (cl.cmdNumber - count + i + 1) & CMD_MASK; + cmd = &cl.cmds[j]; + MSG_WriteDeltaUsercmdKey(&buf, key, oldcmd, cmd); + oldcmd = cmd; + } + } + + // + // deliver the message + // + packetNum = clc.netchan.outgoingSequence & PACKET_MASK; + cl.outPackets[packetNum].p_realtime = cls.realtime; + cl.outPackets[packetNum].p_serverTime = oldcmd->serverTime; + cl.outPackets[packetNum].p_cmdNumber = cl.cmdNumber; + clc.lastPacketSentTime = cls.realtime; + + if (cl_showSend->integer) + { + Com_Printf("%i ", buf.cursize); + } + + CL_Netchan_Transmit(&clc.netchan, &buf); +} + +/* +================= +CL_SendCmd + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCmd(void) +{ + // don't send any message if not connected + if (clc.state < CA_CONNECTED) + { + return; + } + + // don't send commands if paused + if (com_sv_running->integer && sv_paused->integer && cl_paused->integer) + { + return; + } + + // we create commands even if a demo is playing, + CL_CreateNewCommands(); + + // don't send a packet if the last packet was sent too recently + if (!CL_ReadyToSendPacket()) + { + if (cl_showSend->integer) + { + Com_Printf(". "); + } + return; + } + + CL_WritePacket(); +} + +/* +============ +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_Button0Down); + Cmd_AddCommand("-attack", IN_Button0Up); + Cmd_AddCommand("+button0", IN_Button0Down); + Cmd_AddCommand("-button0", IN_Button0Up); + Cmd_AddCommand("+button1", IN_Button1Down); + Cmd_AddCommand("-button1", IN_Button1Up); + Cmd_AddCommand("+button2", IN_Button2Down); + Cmd_AddCommand("-button2", IN_Button2Up); + Cmd_AddCommand("+button3", IN_Button3Down); + Cmd_AddCommand("-button3", IN_Button3Up); + Cmd_AddCommand("+button4", IN_Button4Down); + Cmd_AddCommand("-button4", IN_Button4Up); + Cmd_AddCommand("+button5", IN_Button5Down); + Cmd_AddCommand("-button5", IN_Button5Up); + Cmd_AddCommand("+button6", IN_Button6Down); + Cmd_AddCommand("-button6", IN_Button6Up); + Cmd_AddCommand("+button7", IN_Button7Down); + Cmd_AddCommand("-button7", IN_Button7Up); + Cmd_AddCommand("+button8", IN_Button8Down); + Cmd_AddCommand("-button8", IN_Button8Up); + Cmd_AddCommand("+button9", IN_Button9Down); + Cmd_AddCommand("-button9", IN_Button9Up); + Cmd_AddCommand("+button10", IN_Button10Down); + Cmd_AddCommand("-button10", IN_Button10Up); + Cmd_AddCommand("+button11", IN_Button11Down); + Cmd_AddCommand("-button11", IN_Button11Up); + Cmd_AddCommand("+button12", IN_Button12Down); + Cmd_AddCommand("-button12", IN_Button12Up); + Cmd_AddCommand("+button13", IN_Button13Down); + Cmd_AddCommand("-button13", IN_Button13Up); + Cmd_AddCommand("+button14", IN_Button14Down); + Cmd_AddCommand("-button14", IN_Button14Up); + Cmd_AddCommand("+mlook", IN_MLookDown); + Cmd_AddCommand("-mlook", IN_MLookUp); + +#ifdef USE_VOIP + Cmd_AddCommand("+voiprecord", IN_VoipRecordDown); + Cmd_AddCommand("-voiprecord", IN_VoipRecordUp); +#endif + + cl_nodelta = Cvar_Get("cl_nodelta", "0", 0); + cl_debugMove = Cvar_Get("cl_debugMove", "0", 0); +} + +/* +============ +CL_ShutdownInput +============ +*/ +void CL_ShutdownInput(void) +{ + Cmd_RemoveCommand("centerview"); + + Cmd_RemoveCommand("+moveup"); + Cmd_RemoveCommand("-moveup"); + Cmd_RemoveCommand("+movedown"); + Cmd_RemoveCommand("-movedown"); + Cmd_RemoveCommand("+left"); + Cmd_RemoveCommand("-left"); + Cmd_RemoveCommand("+right"); + Cmd_RemoveCommand("-right"); + Cmd_RemoveCommand("+forward"); + Cmd_RemoveCommand("-forward"); + Cmd_RemoveCommand("+back"); + Cmd_RemoveCommand("-back"); + Cmd_RemoveCommand("+lookup"); + Cmd_RemoveCommand("-lookup"); + Cmd_RemoveCommand("+lookdown"); + Cmd_RemoveCommand("-lookdown"); + Cmd_RemoveCommand("+strafe"); + Cmd_RemoveCommand("-strafe"); + Cmd_RemoveCommand("+moveleft"); + Cmd_RemoveCommand("-moveleft"); + Cmd_RemoveCommand("+moveright"); + Cmd_RemoveCommand("-moveright"); + Cmd_RemoveCommand("+speed"); + Cmd_RemoveCommand("-speed"); + Cmd_RemoveCommand("+attack"); + Cmd_RemoveCommand("-attack"); + Cmd_RemoveCommand("+button0"); + Cmd_RemoveCommand("-button0"); + Cmd_RemoveCommand("+button1"); + Cmd_RemoveCommand("-button1"); + Cmd_RemoveCommand("+button2"); + Cmd_RemoveCommand("-button2"); + Cmd_RemoveCommand("+button3"); + Cmd_RemoveCommand("-button3"); + Cmd_RemoveCommand("+button4"); + Cmd_RemoveCommand("-button4"); + Cmd_RemoveCommand("+button5"); + Cmd_RemoveCommand("-button5"); + Cmd_RemoveCommand("+button6"); + Cmd_RemoveCommand("-button6"); + Cmd_RemoveCommand("+button7"); + Cmd_RemoveCommand("-button7"); + Cmd_RemoveCommand("+button8"); + Cmd_RemoveCommand("-button8"); + Cmd_RemoveCommand("+button9"); + Cmd_RemoveCommand("-button9"); + Cmd_RemoveCommand("+button10"); + Cmd_RemoveCommand("-button10"); + Cmd_RemoveCommand("+button11"); + Cmd_RemoveCommand("-button11"); + Cmd_RemoveCommand("+button12"); + Cmd_RemoveCommand("-button12"); + Cmd_RemoveCommand("+button13"); + Cmd_RemoveCommand("-button13"); + Cmd_RemoveCommand("+button14"); + Cmd_RemoveCommand("-button14"); + Cmd_RemoveCommand("+mlook"); + Cmd_RemoveCommand("-mlook"); + +#ifdef USE_VOIP + Cmd_RemoveCommand("+voiprecord"); + Cmd_RemoveCommand("-voiprecord"); +#endif +} diff --git a/src/client/cl_keys.cpp b/src/client/cl_keys.cpp new file mode 100644 index 0000000..82f5666 --- /dev/null +++ b/src/client/cl_keys.cpp @@ -0,0 +1,1665 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" + +/* + +key up events are sent even if in console mode + +*/ + +field_t historyEditLines[COMMAND_HISTORY]; + +int nextHistoryLine; // the last line in the history buffer, not masked +int historyLine; // the line being displayed from history buffer will be <= nextHistoryLine + +field_t g_consoleField; + +field_t chatField; +bool chat_team; +bool chat_admins; +bool chat_clans; +int chat_playerNum; + +bool key_overstrikeMode; + +int anykeydown; +qkey_t keys[MAX_KEYS]; + +struct keyname_t { + const char* name; + int keynum; +}; + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +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}, + + {"COMMAND", K_COMMAND}, + + {"CAPSLOCK", K_CAPSLOCK}, + + {"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}, + {"F13", K_F13}, + {"F14", K_F14}, + {"F15", K_F15}, + + {"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}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"MWHEELUP", K_MWHEELUP }, + {"MWHEELDOWN", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"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}, + + {"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 }, + {"KP_NUMLOCK", K_KP_NUMLOCK }, + {"KP_STAR", K_KP_STAR }, + {"KP_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {"WORLD_0", K_WORLD_0}, + {"WORLD_1", K_WORLD_1}, + {"WORLD_2", K_WORLD_2}, + {"WORLD_3", K_WORLD_3}, + {"WORLD_4", K_WORLD_4}, + {"WORLD_5", K_WORLD_5}, + {"WORLD_6", K_WORLD_6}, + {"WORLD_7", K_WORLD_7}, + {"WORLD_8", K_WORLD_8}, + {"WORLD_9", K_WORLD_9}, + {"WORLD_10", K_WORLD_10}, + {"WORLD_11", K_WORLD_11}, + {"WORLD_12", K_WORLD_12}, + {"WORLD_13", K_WORLD_13}, + {"WORLD_14", K_WORLD_14}, + {"WORLD_15", K_WORLD_15}, + {"WORLD_16", K_WORLD_16}, + {"WORLD_17", K_WORLD_17}, + {"WORLD_18", K_WORLD_18}, + {"WORLD_19", K_WORLD_19}, + {"WORLD_20", K_WORLD_20}, + {"WORLD_21", K_WORLD_21}, + {"WORLD_22", K_WORLD_22}, + {"WORLD_23", K_WORLD_23}, + {"WORLD_24", K_WORLD_24}, + {"WORLD_25", K_WORLD_25}, + {"WORLD_26", K_WORLD_26}, + {"WORLD_27", K_WORLD_27}, + {"WORLD_28", K_WORLD_28}, + {"WORLD_29", K_WORLD_29}, + {"WORLD_30", K_WORLD_30}, + {"WORLD_31", K_WORLD_31}, + {"WORLD_32", K_WORLD_32}, + {"WORLD_33", K_WORLD_33}, + {"WORLD_34", K_WORLD_34}, + {"WORLD_35", K_WORLD_35}, + {"WORLD_36", K_WORLD_36}, + {"WORLD_37", K_WORLD_37}, + {"WORLD_38", K_WORLD_38}, + {"WORLD_39", K_WORLD_39}, + {"WORLD_40", K_WORLD_40}, + {"WORLD_41", K_WORLD_41}, + {"WORLD_42", K_WORLD_42}, + {"WORLD_43", K_WORLD_43}, + {"WORLD_44", K_WORLD_44}, + {"WORLD_45", K_WORLD_45}, + {"WORLD_46", K_WORLD_46}, + {"WORLD_47", K_WORLD_47}, + {"WORLD_48", K_WORLD_48}, + {"WORLD_49", K_WORLD_49}, + {"WORLD_50", K_WORLD_50}, + {"WORLD_51", K_WORLD_51}, + {"WORLD_52", K_WORLD_52}, + {"WORLD_53", K_WORLD_53}, + {"WORLD_54", K_WORLD_54}, + {"WORLD_55", K_WORLD_55}, + {"WORLD_56", K_WORLD_56}, + {"WORLD_57", K_WORLD_57}, + {"WORLD_58", K_WORLD_58}, + {"WORLD_59", K_WORLD_59}, + {"WORLD_60", K_WORLD_60}, + {"WORLD_61", K_WORLD_61}, + {"WORLD_62", K_WORLD_62}, + {"WORLD_63", K_WORLD_63}, + {"WORLD_64", K_WORLD_64}, + {"WORLD_65", K_WORLD_65}, + {"WORLD_66", K_WORLD_66}, + {"WORLD_67", K_WORLD_67}, + {"WORLD_68", K_WORLD_68}, + {"WORLD_69", K_WORLD_69}, + {"WORLD_70", K_WORLD_70}, + {"WORLD_71", K_WORLD_71}, + {"WORLD_72", K_WORLD_72}, + {"WORLD_73", K_WORLD_73}, + {"WORLD_74", K_WORLD_74}, + {"WORLD_75", K_WORLD_75}, + {"WORLD_76", K_WORLD_76}, + {"WORLD_77", K_WORLD_77}, + {"WORLD_78", K_WORLD_78}, + {"WORLD_79", K_WORLD_79}, + {"WORLD_80", K_WORLD_80}, + {"WORLD_81", K_WORLD_81}, + {"WORLD_82", K_WORLD_82}, + {"WORLD_83", K_WORLD_83}, + {"WORLD_84", K_WORLD_84}, + {"WORLD_85", K_WORLD_85}, + {"WORLD_86", K_WORLD_86}, + {"WORLD_87", K_WORLD_87}, + {"WORLD_88", K_WORLD_88}, + {"WORLD_89", K_WORLD_89}, + {"WORLD_90", K_WORLD_90}, + {"WORLD_91", K_WORLD_91}, + {"WORLD_92", K_WORLD_92}, + {"WORLD_93", K_WORLD_93}, + {"WORLD_94", K_WORLD_94}, + {"WORLD_95", K_WORLD_95}, + + {"WINDOWS", K_SUPER}, + {"COMPOSE", K_COMPOSE}, + {"MODE", K_MODE}, + {"HELP", K_HELP}, + {"PRINT", K_PRINT}, + {"SYSREQ", K_SYSREQ}, + {"SCROLLOCK", K_SCROLLOCK }, + {"BREAK", K_BREAK}, + {"MENU", K_MENU}, + {"POWER", K_POWER}, + {"EURO", K_EURO}, + {"UNDO", K_UNDO}, + + {"PAD0_A", K_PAD0_A }, + {"PAD0_B", K_PAD0_B }, + {"PAD0_X", K_PAD0_X }, + {"PAD0_Y", K_PAD0_Y }, + {"PAD0_BACK", K_PAD0_BACK }, + {"PAD0_GUIDE", K_PAD0_GUIDE }, + {"PAD0_START", K_PAD0_START }, + {"PAD0_LEFTSTICK_CLICK", K_PAD0_LEFTSTICK_CLICK }, + {"PAD0_RIGHTSTICK_CLICK", K_PAD0_RIGHTSTICK_CLICK }, + {"PAD0_LEFTSHOULDER", K_PAD0_LEFTSHOULDER }, + {"PAD0_RIGHTSHOULDER", K_PAD0_RIGHTSHOULDER }, + {"PAD0_DPAD_UP", K_PAD0_DPAD_UP }, + {"PAD0_DPAD_DOWN", K_PAD0_DPAD_DOWN }, + {"PAD0_DPAD_LEFT", K_PAD0_DPAD_LEFT }, + {"PAD0_DPAD_RIGHT", K_PAD0_DPAD_RIGHT }, + + {"PAD0_LEFTSTICK_LEFT", K_PAD0_LEFTSTICK_LEFT }, + {"PAD0_LEFTSTICK_RIGHT", K_PAD0_LEFTSTICK_RIGHT }, + {"PAD0_LEFTSTICK_UP", K_PAD0_LEFTSTICK_UP }, + {"PAD0_LEFTSTICK_DOWN", K_PAD0_LEFTSTICK_DOWN }, + {"PAD0_RIGHTSTICK_LEFT", K_PAD0_RIGHTSTICK_LEFT }, + {"PAD0_RIGHTSTICK_RIGHT", K_PAD0_RIGHTSTICK_RIGHT }, + {"PAD0_RIGHTSTICK_UP", K_PAD0_RIGHTSTICK_UP }, + {"PAD0_RIGHTSTICK_DOWN", K_PAD0_RIGHTSTICK_DOWN }, + {"PAD0_LEFTTRIGGER", K_PAD0_LEFTTRIGGER }, + {"PAD0_RIGHTTRIGGER", K_PAD0_RIGHTTRIGGER }, + + {NULL,0} +}; + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ + + +/* +=================== +Field_Draw + +Handles horizontal scrolling and cursor blinking +x, y, and width are in pixels +=================== +*/ +static void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, + bool showCursor, bool noColorEscape ) +{ + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_STRING_CHARS]; + int i; + + drawLen = edit->widthInChars - 1; // - 1 so there is always a space for the cursor + len = strlen( edit->buffer ); + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( edit->scroll + drawLen > len ) { + edit->scroll = len - drawLen; + if ( edit->scroll < 0 ) { + edit->scroll = 0; + } + } + prestep = edit->scroll; + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_STRING_CHARS ) { + Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); + } + + ::memcpy( str, edit->buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + if ( size == SMALLCHAR_WIDTH ) { + float color[4]; + + color[0] = color[1] = color[2] = color[3] = 1.0; + SCR_DrawSmallStringExt( x, y, str, color, false, noColorEscape ); + } else { + // draw big string with drop shadow + SCR_DrawBigString( x, y, str, 1.0, noColorEscape ); + } + + // draw the cursor + if ( showCursor ) { + if ( (int)( cls.realtime >> 8 ) & 1 ) { + return; // off blink + } + + if ( key_overstrikeMode ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + i = drawLen - strlen( str ); + + if ( size == SMALLCHAR_WIDTH ) { + SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); + } else { + str[0] = cursorChar; + str[1] = 0; + SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0, false ); + + } + } +} + +void Field_Draw( field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape ) +{ + Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape ); +} + +void Field_BigDraw( field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape ) +{ + Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape ); +} + +/* +================ +Field_Paste +================ +*/ +static void Field_Paste( field_t *edit ) +{ + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0 ; i < pasteLen ; i++ ) { + Field_CharEvent( edit, cbd[i] ); + } + + Z_Free( cbd ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) +{ + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) + { + Field_Paste( edit ); + return; + } + + key = tolower( key ); + len = strlen( edit->buffer ); + + switch ( key ) + { + case K_DEL: + if ( edit->cursor < len ) + { + memmove( edit->buffer + edit->cursor, + edit->buffer + edit->cursor + 1, + len - edit->cursor ); + } + break; + + case K_RIGHTARROW: + if ( edit->cursor < len ) { + edit->cursor++; + } + break; + + case K_LEFTARROW: + if ( edit->cursor > 0 ) { + edit->cursor--; + } + break; + + case K_HOME: + edit->cursor = 0; + break; + + case K_END: + edit->cursor = len; + break; + + case K_INS: + key_overstrikeMode = !key_overstrikeMode; + break; + + default: + break; + } + + // Change scroll if cursor is no longer visible + if ( edit->cursor < edit->scroll ) + { + edit->scroll = edit->cursor; + } + else if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) + { + edit->scroll = edit->cursor - edit->widthInChars + 1; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) +{ + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Field_Clear( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( edit->cursor > 0 ) { + memmove( edit->buffer + edit->cursor - 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->cursor--; + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( key_overstrikeMode ) { + // - 2 to leave room for the leading slash and trailing \0 + if ( edit->cursor == MAX_EDIT_LINE - 2 ) + return; + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } else { // insert mode + // - 2 to leave room for the leading slash and trailing \0 + if ( len == MAX_EDIT_LINE - 2 ) { + return; // all full + } + memmove( edit->buffer + edit->cursor + 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + + if ( edit->cursor >= edit->widthInChars ) { + edit->scroll++; + } + + if ( edit->cursor == len + 1) { + edit->buffer[edit->cursor] = 0; + } +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +/* +==================== +Console_Key + +Handles history and console scrollback +==================== +*/ +static void Console_Key(int key) +{ + // ctrl-L clears screen + if ( key == 'l' && keys[K_CTRL].down ) + { + Cbuf_AddText ("clear\n"); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) + { + // if not in the game explicitly prepend a slash if needed + if ( clc.state != CA_ACTIVE + && g_consoleField.buffer[0] + && g_consoleField.buffer[0] != '\\' + && g_consoleField.buffer[0] != '/' ) + { + char temp[MAX_EDIT_LINE-1]; + + Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); + Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); + g_consoleField.cursor++; + } + + Com_Printf ( "]%s\n", g_consoleField.buffer ); + + // leading slash is an explicit command + if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) + { + Cbuf_AddText( g_consoleField.buffer+1 ); // valid command + Cbuf_AddText ("\n"); + } + else + { + // other text will be chat messages + if ( !g_consoleField.buffer[0] ) + { + return; // empty lines just scroll the console without adding to history + } + else + { + Cbuf_AddText ("cmd say "); + Cbuf_AddText( g_consoleField.buffer ); + Cbuf_AddText ("\n"); + } + } + + // copy line to history buffer + historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; + nextHistoryLine++; + historyLine = nextHistoryLine; + + Field_Clear( &g_consoleField ); + + g_consoleField.widthInChars = g_console_field_width; + + CL_SaveConsoleHistory( ); + + if ( clc.state == CA_DISCONNECTED ) { + SCR_UpdateScreen (); // force an update, because the command + } // may take some time + return; + } + + // command completion + + if (key == K_TAB) { + Field_AutoComplete(&g_consoleField); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || + ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) { + if ( nextHistoryLine - historyLine < COMMAND_HISTORY + && historyLine > 0 ) { + historyLine--; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || + ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) { + historyLine++; + if (historyLine >= nextHistoryLine) { + historyLine = nextHistoryLine; + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + return; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == K_PGUP ) { + Con_PageUp(); + return; + } + + if ( key == K_PGDN) { + Con_PageDown(); + return; + } + + if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console + Con_PageUp(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageUp(); + Con_PageUp(); + } + return; + } + + if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console + Con_PageDown(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageDown(); + Con_PageDown(); + } + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && keys[K_CTRL].down ) { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && keys[K_CTRL].down ) { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &g_consoleField, key ); +} +//============================================================================ + + + +/* +================= +Message_Key + +In game talk message +================= +*/ +static void Message_Key( int key ) { + + char buffer[MAX_STRING_CHARS]; + + if (key == K_ESCAPE) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE ); + Field_Clear( &chatField); + return; + } + + if ( key == K_ENTER || key == K_KP_ENTER ) { + if ( chatField.buffer[0] && clc.state == CA_ACTIVE ){ + + if( chatField.buffer[0] == '/' || + chatField.buffer[0] == '\\' ) + { + Com_sprintf( buffer, sizeof( buffer ), "%s\n", &chatField.buffer[1] ); + } + + else if (chat_playerNum != -1 ) { + Com_sprintf( buffer, sizeof( buffer ), + "tell %i \"%s\"\n", + chat_playerNum, + chatField.buffer ); + } + else if (chat_team) { + Com_sprintf( buffer, sizeof( buffer ), + "say_team \"%s\"\n", + chatField.buffer ); + } + else if (chat_admins) { + Com_sprintf( buffer, sizeof( buffer ), + "a \"%s\"\n", + chatField.buffer ); + } + else if (chat_clans) { + char clantagDecolored[ 32 ]; + + Q_strncpyz( clantagDecolored, cl_clantag->string, + sizeof(clantagDecolored) ); + Q_CleanStr( clantagDecolored ); + + if( strlen(clantagDecolored) > 2 && strlen(clantagDecolored) < 11 ) { + Com_sprintf( buffer, sizeof( buffer ), + "m \"%s\" \"%s\"\n", clantagDecolored, + chatField.buffer ); + } else { + //string isnt long enough + Com_Printf ( + "^3Error:your cl_clantag has to be Between 3 and 10 chars long. current value is:^7 %s^7\n", + clantagDecolored ); + return; + } + } + else { + Com_sprintf( buffer, sizeof( buffer ), + "say \"%s\"\n", chatField.buffer ); + } + + CL_AddReliableCommand( buffer, false ); + } + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE ); + Field_Clear( &chatField ); + return; + } + + Field_KeyDownEvent( &chatField, key); +} + +//============================================================================ + + +bool Key_GetOverstrikeMode( void ) +{ + return key_overstrikeMode; +} + + +void Key_SetOverstrikeMode( bool state ) +{ + key_overstrikeMode = state; +} + + +/* +=================== +Key_IsDown +=================== +*/ +bool Key_IsDown( int keynum ) +{ + if ( keynum < 0 || keynum >= MAX_KEYS ) + return false; + + return keys[keynum].down; +} + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers + +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( const char *str ) { + keyname_t *kn; + + if ( !str || !str[0] ) { + return -1; + } + if ( !str[1] ) { + return tolower( str[0] ); + } + + // check for hex code + if ( strlen( str ) == 4 ) { + int n = Com_HexStrToInt( str ); + + if ( n >= 0 ) { + return n; + } + } + + // scan for a text match + for ( kn=keynames ; kn->name ; kn++ ) { + if ( !Q_stricmp( str,kn->name ) ) + return kn->keynum; + } + + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +const char *Key_KeynumToString( int keynum ) { + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) { + return ""; + } + + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return ""; + } + + // check for printable ascii (don't use quote) + if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // check for a key string + for ( kn=keynames ; kn->name ; kn++ ) { + if (keynum == kn->keynum) { + return kn->name; + } + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return; + } + + // free old bindings + if ( keys[ keynum ].binding ) { + Z_Free( keys[ keynum ].binding ); + } + + // allocate memory for new binding + keys[keynum].binding = CopyString( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; +} + + +/* +=================== +Key_GetBinding +=================== +*/ +const char *Key_GetBinding( int keynum ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +Key_GetKey +=================== +*/ + +int Key_GetKey(const char *binding) { + int i; + + if (binding) { + for (i=0 ; i < MAX_KEYS ; i++) { + if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) { + return i; + } + } + } + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf ("unbind : remove commands from a key\n"); + return; + } + + int 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, ""); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f (void) +{ + int i; + + for (i=0 ; i < MAX_KEYS; i++) + if (keys[i].binding) + 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 (keys[b].binding && keys[b].binding[0]) + Com_Printf ("\"%s\" = \"%s\"\n", Key_KeynumToString(b), keys[b].binding ); + else + Com_Printf ("\"%s\" is not bound\n", Key_KeynumToString(b) ); + 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( fileHandle_t f ) { + int i; + + FS_Printf (f, "unbindall\n" ); + + for (i=0 ; i args ) + Field_CompleteKeyname( ); + } +} + +/* +==================== +Key_CompleteBind +==================== +*/ +static void Key_CompleteBind( char *args, int argNum ) +{ + char *p; + + if( argNum == 2 ) + { + // Skip "bind " + p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteKeyname( ); + } + else if( argNum >= 3 ) + { + // Skip "bind " + p = Com_SkipTokens( args, 2, " " ); + + if( p > args ) + Field_CompleteCommand( p, true, true ); + } +} + +/* +=================== +CL_InitKeyCommands +=================== +*/ +void CL_InitKeyCommands( void ) +{ + // register our functions + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_SetCommandCompletionFunc( "bind", Key_CompleteBind ); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind ); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); + Cmd_AddCommand ("bindlist",Key_Bindlist_f); +} + +/* +=================== +CL_BindUICommand + +Returns true if bind command should be executed while user interface is shown +=================== +*/ +static bool CL_BindUICommand( const char *cmd ) +{ + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + return false; + + if ( !Q_stricmp( cmd, "toggleconsole" ) ) + return true; + if ( !Q_stricmp( cmd, "togglemenu" ) ) + return true; + + return false; +} + +/* +=================== +CL_ParseBinding + +Execute the commands in the bind string +=================== +*/ +static void CL_ParseBinding( int key, bool down, unsigned time ) +{ + char buf[ MAX_STRING_CHARS ], *p = buf, *end; + bool allCommands, allowUpCmds; + + if( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == 0 ) + return; + if( !keys[key].binding || !keys[key].binding[0] ) + return; + Q_strncpyz( buf, keys[key].binding, sizeof( buf ) ); + + // run all bind commands if console, ui, etc aren't reading keys + allCommands = ( Key_GetCatcher( ) == 0 ); + + // allow button up commands if in game even if key catcher is set + allowUpCmds = ( clc.state != CA_DISCONNECTED ); + + while( 1 ) + { + while( isspace( *p ) ) + p++; + end = strchr( p, ';' ); + if( end ) + *end = '\0'; + if( *p == '+' ) + { + // button commands add keynum and time as parameters + // so that multiple sources can be discriminated and + // subframe corrected + if ( allCommands || ( allowUpCmds && !down ) ) { + char cmd[1024]; + Com_sprintf( cmd, sizeof( cmd ), "%c%s %d %d\n", + ( down ) ? '+' : '-', p + 1, key, time ); + Cbuf_AddText( cmd ); + } + } + else if( down ) + { + // normal commands only execute on key press + if ( allCommands || CL_BindUICommand( p ) ) { + Cbuf_AddText( p ); + Cbuf_AddText( "\n" ); + } + } + if( !end ) + break; + p = end + 1; + } +} + +/* +=================== +CL_KeyDownEvent + +Called by CL_KeyEvent to handle a keypress +=================== +*/ +static void CL_KeyDownEvent( int key, unsigned time ) +{ + keys[key].down = true; + keys[key].repeats++; + if( keys[key].repeats == 1 ) + anykeydown++; + + if( keys[K_ALT].down && key == K_ENTER ) + { + Cvar_SetValue( "r_fullscreen", + !Cvar_VariableIntegerValue( "r_fullscreen" ) ); + return; + } + + // console key is hardcoded, so the user can never unbind it + if( key == K_CONSOLE || ( keys[K_SHIFT].down && key == K_ESCAPE ) ) + { + Con_ToggleConsole_f (); + Key_ClearStates (); + return; + } + + + // keys can still be used for bound actions + if ( ( key < 128 || key == K_MOUSE1 ) && + ( clc.demoplaying || clc.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) { + + if (Cvar_VariableValue ("com_cameraMode") == 0) { + if( clc.demoplaying && key != K_ESCAPE ) { + // avoid accidental stopping of demos from pressing a random key by opening the console + Con_ToggleConsole_f (); + Key_ClearStates (); + return; + } + Cvar_Set ("nextdemo",""); + key = K_ESCAPE; + } + } + + // escape is always handled special + if ( key == K_ESCAPE ) { + if ( clc.netchan.alternateProtocol == 2 && + ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) { + // clear message mode + Message_Key( key ); + return; + } + + // escape always gets out of CGAME stuff + if (Key_GetCatcher( ) & KEYCATCH_CGAME) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME ); + VM_Call (cls.cgame, CG_EVENT_HANDLING, CGAME_EVENT_NONE); + return; + } + + if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) { + if ( clc.state == CA_ACTIVE && !clc.demoplaying ) { + VM_Call( cls.ui, UI_SET_ACTIVE_MENU - ( cls.uiInterface == 2 ? 2 : 0 ), UIMENU_INGAME ); + } + else if ( clc.state != CA_DISCONNECTED ) { + CL_Disconnect_f(); + S_StopAllSounds(); + VM_Call( cls.ui, UI_SET_ACTIVE_MENU - ( cls.uiInterface == 2 ? 2 : 0 ), UIMENU_MAIN ); + } + return; + } + + VM_Call( cls.ui, UI_KEY_EVENT, key, true ); + return; + } + + // send the bound action + CL_ParseBinding( key, true, time ); + + // distribute the key down event to the apropriate handler + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) { + Console_Key( key ); + } else if ( Key_GetCatcher( ) & KEYCATCH_UI ) { + if ( cls.ui ) { + VM_Call( cls.ui, UI_KEY_EVENT, key, true ); + } + } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) { + if ( cls.cgame ) { + VM_Call( cls.cgame, CG_KEY_EVENT, key, true ); + } + } else if ( clc.netchan.alternateProtocol == 2 && + ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) { + Message_Key( key ); + } else if ( clc.state == CA_DISCONNECTED ) { + Console_Key( key ); + } +} + +/* +=================== +CL_KeyUpEvent + +Called by CL_KeyEvent to handle a keyrelease +=================== +*/ +static void CL_KeyUpEvent( int key, unsigned time ) +{ + keys[key].repeats = 0; + keys[key].down = false; + anykeydown--; + + if (anykeydown < 0) { + anykeydown = 0; + } + + // don't process key-up events for the console key + if ( key == K_CONSOLE || ( key == K_ESCAPE && keys[K_SHIFT].down ) ) + return; + + // + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + // + CL_ParseBinding( key, false, time ); + + if ( Key_GetCatcher( ) & KEYCATCH_UI && cls.ui ) { + VM_Call( cls.ui, UI_KEY_EVENT, key, false ); + } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cls.cgame ) { + VM_Call( cls.cgame, CG_KEY_EVENT, key, false ); + } +} + +/* +=================== +CL_KeyEvent + +Called by the system for both key up and key down events +=================== +*/ +void CL_KeyEvent(int key, bool down, unsigned time) +{ + if( down ) + CL_KeyDownEvent( key, time ); + else + CL_KeyUpEvent( key, time ); +} + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) +{ + // delete is not a printable character and is otherwise handled by + // Field_KeyDownEvent + if ( key == 127 ) + return; + + // distribute the key down event to the apropriate handler + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + Field_CharEvent( &g_consoleField, key ); + + else if ( Key_GetCatcher( ) & KEYCATCH_UI ) + VM_Call( cls.ui, UI_KEY_EVENT, key | K_CHAR_FLAG, true ); + + else if ( clc.netchan.alternateProtocol == 2 && + ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) ) + Field_CharEvent( &chatField, key ); + + else if ( clc.state == CA_DISCONNECTED ) + Field_CharEvent( &g_consoleField, key ); +} + + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates(void) +{ + anykeydown = 0; + + for ( int i = 0 ; i < MAX_KEYS ; i++ ) + { + if ( keys[i].down ) + CL_KeyEvent( i, false, 0 ); + + keys[i].down = 0; + keys[i].repeats = 0; + } +} + +/* +==================== +Key_KeynumToStringBuf +==================== +*/ +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) +{ + Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen ); +} + +/* +==================== +Key_GetBindingBuf +==================== +*/ +void Key_GetBindingBuf( int keynum, char *buf, int buflen ) +{ + const char* value = Key_GetBinding( keynum ); + if ( value ) + { + Q_strncpyz( buf, value, buflen ); + } + else + { + *buf = 0; + } +} + +static int keyCatchers = 0; + +/* +==================== +Key_GetCatcher +==================== +*/ +int Key_GetCatcher( void ) +{ + return keyCatchers; +} + +/* +==================== +Key_SetCatcher +==================== +*/ +void Key_SetCatcher( int catcher ) +{ + // If the catcher state is changing, clear all key states + if( catcher != keyCatchers ) + Key_ClearStates( ); + + keyCatchers = catcher; +} + +// This must not exceed MAX_CMD_LINE +#define MAX_CONSOLE_SAVE_BUFFER 1024 +#define CONSOLE_HISTORY_FILE "q3history" +static char consoleSaveBuffer[ MAX_CONSOLE_SAVE_BUFFER ]; +static int consoleSaveBufferSize = 0; + +/* +================ +CL_LoadConsoleHistory + +Load the console history from cl_consoleHistory +================ +*/ +void CL_LoadConsoleHistory( void ) +{ + char *token, *text_p; + int i, numChars, numLines = 0; + fileHandle_t f; + + consoleSaveBufferSize = FS_FOpenFileRead( CONSOLE_HISTORY_FILE, &f, false ); + if( !f ) + { + Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE ); + return; + } + + if( consoleSaveBufferSize <= MAX_CONSOLE_SAVE_BUFFER && + FS_Read( consoleSaveBuffer, consoleSaveBufferSize, f ) == consoleSaveBufferSize ) + { + text_p = consoleSaveBuffer; + + for( i = COMMAND_HISTORY - 1; i >= 0; i-- ) + { + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + historyEditLines[ i ].cursor = atoi( token ); + + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + historyEditLines[ i ].scroll = atoi( token ); + + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + numChars = atoi( token ); + text_p++; + if( numChars > ( strlen( consoleSaveBuffer ) - ( text_p - consoleSaveBuffer ) ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: probable corrupt history\n" ); + break; + } + ::memcpy( historyEditLines[ i ].buffer, text_p, numChars ); + historyEditLines[ i ].buffer[ numChars ] = '\0'; + text_p += numChars; + + numLines++; + } + + memmove( &historyEditLines[ 0 ], &historyEditLines[ i + 1 ], + numLines * sizeof( field_t ) ); + for( i = numLines; i < COMMAND_HISTORY; i++ ) + Field_Clear( &historyEditLines[ i ] ); + + historyLine = nextHistoryLine = numLines; + } + else + Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE ); + + FS_FCloseFile( f ); +} + +/* +================ +CL_SaveConsoleHistory + +Save the console history into the cvar cl_consoleHistory +so that it persists across invocations of q3 +================ +*/ +void CL_SaveConsoleHistory( void ) +{ + int i; + int lineLength, saveBufferLength, additionalLength; + fileHandle_t f; + + consoleSaveBuffer[ 0 ] = '\0'; + + i = ( nextHistoryLine - 1 ) % COMMAND_HISTORY; + do + { + if( historyEditLines[ i ].buffer[ 0 ] ) + { + lineLength = strlen( historyEditLines[ i ].buffer ); + saveBufferLength = strlen( consoleSaveBuffer ); + + //ICK + additionalLength = lineLength + strlen( "999 999 999 " ); + + if( saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER ) + { + Q_strcat( consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER, + va( "%d %d %d %s ", + historyEditLines[ i ].cursor, + historyEditLines[ i ].scroll, + lineLength, + historyEditLines[ i ].buffer ) ); + } + else + break; + } + i = ( i - 1 + COMMAND_HISTORY ) % COMMAND_HISTORY; + } + while( i != ( nextHistoryLine - 1 ) % COMMAND_HISTORY ); + + consoleSaveBufferSize = strlen( consoleSaveBuffer ); + + f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE ); + if( !f ) + { + Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE ); + return; + } + + if( FS_Write( consoleSaveBuffer, consoleSaveBufferSize, f ) < consoleSaveBufferSize ) + Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE ); + + FS_FCloseFile( f ); +} diff --git a/src/client/cl_main.cpp b/src/client/cl_main.cpp new file mode 100644 index 0000000..8b33acd --- /dev/null +++ b/src/client/cl_main.cpp @@ -0,0 +1,5083 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// cl_main.c -- client main loop + +#include "client.h" + +#ifndef _WIN32 +#include +#endif + +#include + +#include "sys/sys_loadlib.h" +#include "sys/sys_local.h" + +#include "cl_updates.h" +#ifdef USE_MUMBLE +#include "libmumblelink.h" +#endif + +#ifdef USE_MUMBLE +cvar_t *cl_useMumble; +cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +cvar_t *cl_voipUseVAD; +cvar_t *cl_voipVADThreshold; +cvar_t *cl_voipSend; +cvar_t *cl_voipSendTarget; +cvar_t *cl_voipGainDuringCapture; +cvar_t *cl_voipCaptureMult; +cvar_t *cl_voipShowMeter; +cvar_t *cl_voipProtocol; +cvar_t *cl_voip; +#endif + +#ifdef USE_RENDERER_DLOPEN +cvar_t *cl_renderer; +#endif + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_timedemoLog; +cvar_t *cl_autoRecordDemo; +cvar_t *cl_aviFrameRate; +cvar_t *cl_aviMotionJpeg; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_mouseAccelOffset; +cvar_t *cl_mouseAccelStyle; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *j_pitch; +cvar_t *j_yaw; +cvar_t *j_forward; +cvar_t *j_side; +cvar_t *j_up; +cvar_t *j_pitch_axis; +cvar_t *j_yaw_axis; +cvar_t *j_forward_axis; +cvar_t *j_side_axis; +cvar_t *j_up_axis; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *com_downloadPrompt; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; + +cvar_t *cl_lanForcePackets; + +cvar_t *cl_guidServerUniq; + +cvar_t *cl_clantag; + +cvar_t *cl_consoleKeys; + +cvar_t *cl_rate; + +cvar_t *cl_rsaAuth; + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; + +char cl_reconnectArgs[MAX_OSPATH]; +char cl_oldGame[MAX_QPATH]; +bool cl_oldGameSet; + +// Structure containing functions exported from refresh DLL +refexport_t re; +#ifdef USE_RENDERER_DLOPEN +static void *rendererLib = NULL; +#endif + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s { + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + bool pending; + bool print; + bool retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; + +static void CL_InitRef(void); + +#if defined __USEA3D && defined __A3D_GEOM +void hA3Dg_ExportRenderGeom(refexport_t *incoming_re); +#endif + +static bool noGameRestart = false; + +static void CL_DownloadUpdate_f() { CL_DownloadRelease(); } + +static void CL_InstallUpdate_f() +{ + if (Cmd_Argc() > 1) + CL_ExecuteInstaller(); + else + CL_ExecuteInstaller(); +} + +static void CL_CheckForUpdate_f() { CL_GetLatestRelease(); } + +static void CL_BrowseHomepath_f() { FS_BrowseHomepath(); } + +static void CL_BrowseDemos_f() { FS_OpenBaseGamePath( "demos/" ); } + +static void CL_BrowseScreenShots_f() { FS_OpenBaseGamePath( "screenshots/" ); } + +#ifdef USE_MUMBLE +static void CL_UpdateMumble(void) +{ + vec3_t pos, forward, up; + float scale = cl_mumbleScale->value; + float tmp; + + if (!cl_useMumble->integer) return; + + // !!! FIXME: not sure if this is even close to correct. + if (clc.netchan.alternateProtocol == 2) + { + AngleVectors(cl.snap.alternatePs.viewangles, forward, NULL, up); + + pos[0] = cl.snap.alternatePs.origin[0] * scale; + pos[1] = cl.snap.alternatePs.origin[2] * scale; + pos[2] = cl.snap.alternatePs.origin[1] * scale; + } + else + { + AngleVectors(cl.snap.ps.viewangles, forward, NULL, up); + + pos[0] = cl.snap.ps.origin[0] * scale; + pos[1] = cl.snap.ps.origin[2] * scale; + pos[2] = cl.snap.ps.origin[1] * scale; + } + + tmp = forward[1]; + forward[1] = forward[2]; + forward[2] = tmp; + + tmp = up[1]; + up[1] = up[2]; + up[2] = tmp; + + if (cl_useMumble->integer > 1) + { + fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n", + pos[0], pos[1], pos[2], + forward[0], forward[1], forward[2], + up[0], up[1], up[2]); + } + + mumble_update_coordinates(pos, forward, up); +} +#endif + +#ifdef USE_VOIP +static void CL_UpdateVoipIgnore(const char *idstr, bool ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) + { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) + { + clc.voipIgnore[id] = ignore; + CL_AddReliableCommand(va("voip %s %d", ignore ? "ignore" : "unignore", id), false); + Com_Printf("VoIP: %s ignoring player #%d\n", ignore ? "Now" : "No longer", id); + return; + } + } + Com_Printf("VoIP: invalid player ID#\n"); +} + +static void CL_UpdateVoipGain(const char *idstr, float gain) +{ + if ((*idstr >= '0') && (*idstr <= '9')) + { + const int id = atoi(idstr); + if (gain < 0.0f) gain = 0.0f; + if ((id >= 0) && (id < MAX_CLIENTS)) + { + clc.voipGain[id] = gain; + Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain); + } + } +} + +void CL_Voip_f(void) +{ + const char *cmd = Cmd_Argv(1); + const char *reason = NULL; + + if (clc.state != CA_ACTIVE) + reason = "Not connected to a server"; + else if (!clc.voipCodecInitialized) + reason = "Voip codec not initialized"; + else if (!clc.voipEnabled) + reason = "Server doesn't support VoIP"; + + if (reason != NULL) + { + Com_Printf("VoIP: command ignored: %s\n", reason); + return; + } + + if (strcmp(cmd, "ignore") == 0) + { + CL_UpdateVoipIgnore(Cmd_Argv(2), true); + } + else if (strcmp(cmd, "unignore") == 0) + { + CL_UpdateVoipIgnore(Cmd_Argv(2), false); + } + else if (strcmp(cmd, "gain") == 0) + { + if (Cmd_Argc() > 3) + { + CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3))); + } + else if (Q_isanumber(Cmd_Argv(2))) + { + int id = atoi(Cmd_Argv(2)); + if (id >= 0 && id < MAX_CLIENTS) + { + Com_Printf( + "VoIP: current gain for player #%d " + "is %f\n", + id, clc.voipGain[id]); + } + else + { + Com_Printf("VoIP: invalid player ID#\n"); + } + } + else + { + Com_Printf("usage: voip gain [value]\n"); + } + } + else if (strcmp(cmd, "muteall") == 0) + { + Com_Printf("VoIP: muting incoming voice\n"); + CL_AddReliableCommand("voip muteall", false); + clc.voipMuteAll = true; + } + else if (strcmp(cmd, "unmuteall") == 0) + { + Com_Printf("VoIP: unmuting incoming voice\n"); + CL_AddReliableCommand("voip unmuteall", false); + clc.voipMuteAll = false; + } + else + { + Com_Printf( + "usage: voip [un]ignore \n" + " voip [un]muteall\n" + " voip gain [value]\n"); + } +} + +static void CL_VoipNewGeneration(void) +{ + // don't have a zero generation so new clients won't match, and don't + // wrap to negative so MSG_ReadLong() doesn't "fail." + clc.voipOutgoingGeneration++; + if (clc.voipOutgoingGeneration <= 0) clc.voipOutgoingGeneration = 1; + clc.voipPower = 0.0f; + clc.voipOutgoingSequence = 0; + + opus_encoder_ctl(clc.opusEncoder, OPUS_RESET_STATE); +} + +/* +=============== +CL_VoipParseTargets + +sets clc.voipTargets according to cl_voipSendTarget +Generally we don't want who's listening to change during a transmission, +so this is only called when the key is first pressed +=============== +*/ +static void CL_VoipParseTargets(void) +{ + const char *target = cl_voipSendTarget->string; + char *end; + int val; + + ::memset(clc.voipTargets, 0, sizeof(clc.voipTargets)); + clc.voipFlags &= ~VOIP_SPATIAL; + + while (target) + { + while (*target == ',' || *target == ' ') target++; + + if (!*target) break; + + if (isdigit(*target)) + { + val = strtol(target, &end, 10); + target = end; + } + else + { + if (!Q_stricmpn(target, "all", 3)) + { + ::memset(clc.voipTargets, ~0, sizeof(clc.voipTargets)); + return; + } + if (!Q_stricmpn(target, "spatial", 7)) + { + clc.voipFlags |= VOIP_SPATIAL; + target += 7; + continue; + } + else + { + if (!Q_stricmpn(target, "attacker", 8)) + { + val = VM_Call(cls.cgame, CG_LAST_ATTACKER); + target += 8; + } + else if (!Q_stricmpn(target, "crosshair", 9)) + { + val = VM_Call(cls.cgame, CG_CROSSHAIR_PLAYER); + target += 9; + } + else + { + while (*target && *target != ',' && *target != ' ') target++; + + continue; + } + + if (val < 0) continue; + } + } + + if (val < 0 || val >= MAX_CLIENTS) + { + Com_Printf(S_COLOR_YELLOW + "WARNING: VoIP " + "target %d is not a valid client " + "number\n", + val); + + continue; + } + + clc.voipTargets[val / 8] |= 1 << (val % 8); + } +} + +/* +=============== +CL_CaptureVoip + +Record more audio from the hardware if required and encode it into Opus + data for later transmission. +=============== +*/ +static void CL_CaptureVoip(void) +{ + const float audioMult = cl_voipCaptureMult->value; + const bool useVad = (cl_voipUseVAD->integer != 0); + bool initialFrame = false; + bool finalFrame = false; + +#if USE_MUMBLE + // if we're using Mumble, don't try to handle VoIP transmission ourselves. + if (cl_useMumble->integer) return; +#endif + + // If your data rate is too low, you'll get Connection Interrupted warnings + // when VoIP packets arrive, even if you have a broadband connection. + // This might work on rates lower than 25000, but for safety's sake, we'll + // just demand it. Who doesn't have at least a DSL line now, anyhow? If + // you don't, you don't need VoIP. :) + if (cl_voip->modified || cl_rate->modified) + { + if ((cl_voip->integer) && (cl_rate->integer < 25000)) + { + Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n"); + Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network'.\n"); + Com_Printf("Until then, VoIP is disabled.\n"); + Cvar_Set("cl_voip", "0"); + } + Cvar_Set("cl_voipProtocol", cl_voip->integer ? "opus" : ""); + cl_voip->modified = false; + cl_rate->modified = false; + } + + if (!clc.voipCodecInitialized) return; // just in case this gets called at a bad time. + + if (clc.voipOutgoingDataSize > 0) return; // packet is pending transmission, don't record more yet. + + if (cl_voipUseVAD->modified) + { + Cvar_Set("cl_voipSend", (useVad) ? "1" : "0"); + cl_voipUseVAD->modified = false; + } + + if ((useVad) && (!cl_voipSend->integer)) Cvar_Set("cl_voipSend", "1"); // lots of things reset this. + + if (cl_voipSend->modified) + { + bool dontCapture = false; + if (clc.state != CA_ACTIVE) + dontCapture = true; // not connected to a server. + else if (!clc.voipEnabled) + dontCapture = true; // server doesn't support VoIP. + else if (clc.demoplaying) + dontCapture = true; // playing back a demo. + else if (cl_voip->integer == 0) + dontCapture = true; // client has VoIP support disabled. + else if (audioMult == 0.0f) + dontCapture = true; // basically silenced incoming audio. + + cl_voipSend->modified = false; + + if (dontCapture) + { + Cvar_Set("cl_voipSend", "0"); + return; + } + + if (cl_voipSend->integer) + { + initialFrame = true; + } + else + { + finalFrame = true; + } + } + + // try to get more audio data from the sound card... + + if (initialFrame) + { + S_MasterGain(Com_Clamp(0.0f, 1.0f, cl_voipGainDuringCapture->value)); + S_StartCapture(); + CL_VoipNewGeneration(); + CL_VoipParseTargets(); + } + + if ((cl_voipSend->integer) || (finalFrame)) + { // user wants to capture audio? + int samples = S_AvailableCaptureSamples(); + const int packetSamples = (finalFrame) ? VOIP_MAX_FRAME_SAMPLES : VOIP_MAX_PACKET_SAMPLES; + + // enough data buffered in audio hardware to process yet? + if (samples >= packetSamples) + { + // audio capture is always MONO16. + static int16_t sampbuffer[VOIP_MAX_PACKET_SAMPLES]; + float voipPower = 0.0f; + int voipFrames; + int i, bytes; + + if (samples > VOIP_MAX_PACKET_SAMPLES) samples = VOIP_MAX_PACKET_SAMPLES; + + // !!! FIXME: maybe separate recording from encoding, so voipPower + // !!! FIXME: updates faster than 4Hz? + + samples -= samples % VOIP_MAX_FRAME_SAMPLES; + if (samples != 120 && samples != 240 && samples != 480 && samples != 960 && samples != 1920 && + samples != 2880) + { + Com_Printf("Voip: bad number of samples %d\n", samples); + return; + } + voipFrames = samples / VOIP_MAX_FRAME_SAMPLES; + + S_Capture(samples, (byte *)sampbuffer); // grab from audio card. + + // check the "power" of this packet... + for (i = 0; i < samples; i++) + { + const float flsamp = (float)sampbuffer[i]; + const float s = fabs(flsamp); + voipPower += s * s; + sampbuffer[i] = (int16_t)((flsamp)*audioMult); + } + + // encode raw audio samples into Opus data... + bytes = opus_encode(clc.opusEncoder, sampbuffer, samples, (unsigned char *)clc.voipOutgoingData, + sizeof(clc.voipOutgoingData)); + if (bytes <= 0) + { + Com_DPrintf("VoIP: Error encoding %d samples\n", samples); + bytes = 0; + } + + clc.voipPower = (voipPower / (32768.0f * 32768.0f * ((float)samples))) * 100.0f; + + if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) + { + CL_VoipNewGeneration(); // no "talk" for at least 1/4 second. + } + else + { + clc.voipOutgoingDataSize = bytes; + clc.voipOutgoingDataFrames = voipFrames; + + Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n", voipFrames, bytes, clc.voipPower); + +#if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb"); + if (encio != NULL) { fwrite(clc.voipOutgoingData, bytes, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb"); + if (decio != NULL) { fwrite(sampbuffer, voipFrames * VOIP_MAX_FRAME_SAMPLES * 2, 1, decio); fflush(decio); } +#endif + } + } + } + + // User requested we stop recording, and we've now processed the last of + // any previously-buffered data. Pause the capture device, etc. + if (finalFrame) + { + S_StopCapture(); + S_MasterGain(1.0f); + clc.voipPower = 0.0f; // force this value so it doesn't linger. + } +} +#endif + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand(const char *cmd, bool isDisconnectCmd) +{ + int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // also leave one slot open for the disconnect command in this case. + + if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) || + (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS)) + { + if (com_errorEntered) + return; + else + Com_Error(ERR_DROP, "Client command overflow"); + } + + Q_strncpyz( + clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)], cmd, sizeof(*clc.reliableCommands)); +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage(msg_t *msg, int headerBytes) +{ + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong(len); + FS_Write(&swlen, 4, clc.demofile); + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong(len); + FS_Write(&swlen, 4, clc.demofile); + FS_Write(msg->data + headerBytes, len, clc.demofile); +} + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f(void) +{ + int len; + + if (!clc.demorecording) + { + Com_Printf("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + FS_Write(&len, 4, clc.demofile); + FS_Write(&len, 4, clc.demofile); + FS_FCloseFile(clc.demofile); + clc.demofile = 0; + clc.demorecording = false; + clc.spDemoRecording = false; + Com_Printf("Stopped demo.\n"); +} + +/* +================== +CL_DemoFilename +================== +*/ +static void CL_DemoFilename(int number, char *fileName, int fileNameSize) +{ + int a, b, c, d; + + if (number < 0 || number > 9999) number = 9999; + + a = number / 1000; + number -= a * 1000; + b = number / 100; + number -= b * 100; + c = number / 10; + number -= c * 10; + d = number; + + Com_sprintf(fileName, fileNameSize, "demo%i%i%i%i", a, b, c, d); +} + +/* +==================== +CL_Record_f + +record + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround + +static void CL_Record_f(void) +{ + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + + if (Cmd_Argc() > 2) + { + Com_Printf("record \n"); + return; + } + + if (clc.demorecording) + { + if (!clc.spDemoRecording) + { + Com_Printf("Already recording.\n"); + } + return; + } + + if (clc.state != CA_ACTIVE) + { + Com_Printf("You must be in a level to record.\n"); + return; + } + + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if (NET_IsLocalAddress(clc.serverAddress) && !Cvar_VariableValue("g_synchronousClients")) + { + Com_Printf(S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); + } + + if (Cmd_Argc() == 2) + { + const char *s = Cmd_Argv(1); + Q_strncpyz(demoName, s, sizeof(demoName)); + Com_sprintf( + name, sizeof(name), + "demos/%s.%s%d", demoName, DEMOEXT, + (clc.netchan.alternateProtocol == 0 ? + PROTOCOL_VERSION : clc.netchan.alternateProtocol == 1 ? 70 : 69)); + } + else + { + int number; + + // scan for a free demo name + for (number = 0; number <= 9999; number++) + { + CL_DemoFilename(number, demoName, sizeof(demoName)); + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, PROTOCOL_VERSION); + + if (!FS_FileExists(name)) break; // file doesn't exist + } + } + + // open the demo file + + Com_Printf("recording to %s.\n", name); + clc.demofile = FS_FOpenFileWrite(name); + if (!clc.demofile) + { + Com_Printf("ERROR: couldn't open.\n"); + return; + } + clc.demorecording = true; + if (Cvar_VariableValue("ui_recordSPDemo")) + { + clc.spDemoRecording = true; + } + else + { + clc.spDemoRecording = false; + } + + Q_strncpyz(clc.demoName, demoName, sizeof(clc.demoName)); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = true; + + // write out the gamestate message + MSG_Init(&buf, bufData, sizeof(bufData)); + MSG_Bitstream(&buf); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong(&buf, clc.reliableSequence); + + MSG_WriteByte(&buf, svc_gamestate); + MSG_WriteLong(&buf, clc.serverCommandSequence); + + // configstrings + for (i = 0; i < MAX_CONFIGSTRINGS; i++) + { + if (!cl.gameState.stringOffsets[i]) + { + continue; + } + const char *s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte(&buf, svc_configstring); + MSG_WriteShort(&buf, i); + MSG_WriteBigString(&buf, s); + } + + // baselines + ::memset(&nullstate, 0, sizeof(nullstate)); + for (i = 0; i < MAX_GENTITIES; i++) + { + ent = &cl.entityBaselines[i]; + if (!ent->number) + { + continue; + } + MSG_WriteByte(&buf, svc_baseline); + MSG_WriteDeltaEntity(clc.netchan.alternateProtocol, &buf, &nullstate, ent, true); + } + + MSG_WriteByte(&buf, svc_EOF); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong(&buf, clc.clientNum); + // write the checksum feed + MSG_WriteLong(&buf, clc.checksumFeed); + + // finished writing the client packet + MSG_WriteByte(&buf, svc_EOF); + + // write it to the demo file + len = LittleLong(clc.serverMessageSequence - 1); + FS_Write(&len, 4, clc.demofile); + + len = LittleLong(buf.cursize); + FS_Write(&len, 4, clc.demofile); + FS_Write(buf.data, buf.cursize, clc.demofile); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoFrameDurationSDev +================= +*/ +static float CL_DemoFrameDurationSDev(void) +{ + int i; + int numFrames; + float mean = 0.0f; + float variance = 0.0f; + + if ((clc.timeDemoFrames - 1) > MAX_TIMEDEMO_DURATIONS) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + for (i = 0; i < numFrames; i++) mean += clc.timeDemoDurations[i]; + mean /= numFrames; + + for (i = 0; i < numFrames; i++) + { + float x = clc.timeDemoDurations[i]; + + variance += ((x - mean) * (x - mean)); + } + variance /= numFrames; + + return sqrt(variance); +} + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted(void) +{ + char buffer[MAX_STRING_CHARS]; + + if (cl_timedemo && cl_timedemo->integer) + { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if (time > 0) + { + // Millisecond times are frame durations: + // minimum/average/maximum/std deviation + Com_sprintf(buffer, sizeof(buffer), "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n", + clc.timeDemoFrames, time / 1000.0, clc.timeDemoFrames * 1000.0 / time, clc.timeDemoMinDuration, + time / (float)clc.timeDemoFrames, clc.timeDemoMaxDuration, CL_DemoFrameDurationSDev()); + Com_Printf("%s", buffer); + + // Write a log of all the frame durations + if (cl_timedemoLog && strlen(cl_timedemoLog->string) > 0) + { + int i; + int numFrames; + fileHandle_t f; + + if ((clc.timeDemoFrames - 1) > MAX_TIMEDEMO_DURATIONS) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + f = FS_FOpenFileWrite(cl_timedemoLog->string); + if (f) + { + FS_Printf(f, "# %s", buffer); + + for (i = 0; i < numFrames; i++) FS_Printf(f, "%d\n", clc.timeDemoDurations[i]); + + FS_FCloseFile(f); + Com_Printf("%s written\n", cl_timedemoLog->string); + } + else + { + Com_Printf("Couldn't open %s for writing\n", cl_timedemoLog->string); + } + } + } + } + + CL_Disconnect(true); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage(void) +{ + int r; + msg_t buf; + byte bufData[MAX_MSGLEN]; + int s; + + if (!clc.demofile) + { + CL_DemoCompleted(); + return; + } + + // get the sequence number + r = FS_Read(&s, 4, clc.demofile); + if (r != 4) + { + CL_DemoCompleted(); + return; + } + clc.serverMessageSequence = LittleLong(s); + + // init the message + MSG_Init(&buf, bufData, sizeof(bufData)); + + // get the length + r = FS_Read(&buf.cursize, 4, clc.demofile); + if (r != 4) + { + CL_DemoCompleted(); + return; + } + buf.cursize = LittleLong(buf.cursize); + if (buf.cursize == -1) + { + CL_DemoCompleted(); + return; + } + if (buf.cursize > buf.maxsize) + { + Com_Error(ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); + } + r = FS_Read(buf.data, buf.cursize, clc.demofile); + if (r != buf.cursize) + { + Com_Printf("Demo file was truncated.\n"); + CL_DemoCompleted(); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage(&buf); +} + +/* +==================== +CL_WalkDemoExt +==================== +*/ +static int CL_WalkDemoExt(const char *arg, char *name, int *demofile) +{ + int i; + *demofile = 0; + + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION); + FS_FOpenFileRead(name, demofile, true); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return PROTOCOL_VERSION; + } + + Com_Printf("Not found: %s\n", name); + + for (i = 0; demo_protocols[i]; ++i) + { + if (demo_protocols[i] == PROTOCOL_VERSION) continue; + + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]); + FS_FOpenFileRead(name, demofile, true); + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + + return demo_protocols[i]; + } + else + Com_Printf("Not found: %s\n", name); + } + + return -1; +} + +/* +==================== +CL_CompleteDemoName +==================== +*/ +static void CL_CompleteDemoName(char *args, int argNum) +{ + if (argNum == 2) + { + char demoExt[16]; + + Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, PROTOCOL_VERSION); + Field_CompleteFilename("demos", demoExt, true, true); + } +} + +/* +==================== +CL_PlayDemo_f + +demo + +==================== +*/ +void CL_PlayDemo_f(void) +{ + char name[MAX_OSPATH]; + const char *ext_test; + int protocol, i; + char retry[MAX_OSPATH]; + + if (Cmd_Argc() != 2) + { + Com_Printf("demo \n"); + return; + } + + // make sure a local server is killed + // 2 means don't force disconnect of local client + Cvar_Set("sv_killserver", "2"); + + // open the demo file + const char *arg = Cmd_Argv(1); + + CL_Disconnect(true); + + // check for an extension .DEMOEXT_?? (?? is protocol) + ext_test = strrchr(arg, '.'); + + if (ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) + { + protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + + for (i = 0; demo_protocols[i]; i++) + { + if (demo_protocols[i] == protocol) break; + } + + if (demo_protocols[i] || protocol == PROTOCOL_VERSION) + { + Com_sprintf(name, sizeof(name), "demos/%s", arg); + FS_FOpenFileRead(name, &clc.demofile, true); + } + else + { + int len; + + Com_Printf("Protocol %d not supported for demos\n", protocol); + len = ext_test - arg; + + if (len >= ARRAY_LEN(retry)) len = ARRAY_LEN(retry) - 1; + + Q_strncpyz(retry, arg, len + 1); + retry[len] = '\0'; + protocol = CL_WalkDemoExt(retry, name, &clc.demofile); + } + } + else + protocol = CL_WalkDemoExt(arg, name, &clc.demofile); + + if (!clc.demofile) + { + Com_Error(ERR_DROP, "couldn't open %s", name); + return; + } + Q_strncpyz(clc.demoName, arg, sizeof(clc.demoName)); + + clc.state = CA_CONNECTED; + clc.demoplaying = true; + Q_strncpyz(clc.servername, arg, sizeof(clc.servername)); + clc.netchan.alternateProtocol = (protocol == 69 ? 2 : protocol == 70 ? 1 : 0); + + // read demo messages until connected + while (clc.state >= CA_CONNECTED && clc.state < CA_PRIMED) + { + CL_ReadDemoMessage(); + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = false; +} + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +static void CL_StartDemoLoop(void) +{ + // start the demo loop again + Cbuf_AddText("d1\n"); + Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE); +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo(void) +{ + char v[MAX_STRING_CHARS]; + + Q_strncpyz(v, Cvar_VariableString("nextdemo"), sizeof(v)); + v[MAX_STRING_CHARS - 1] = 0; + Com_DPrintf("CL_NextDemo: %s\n", v); + if (!v[0]) + { + return; + } + + Cvar_Set("nextdemo", ""); + Cbuf_AddText(v); + Cbuf_AddText("\n"); + Cbuf_Execute(); +} + +/* +================== +CL_DemoState + +Returns the current state of the demo system +================== +*/ +demoState_t CL_DemoState(void) +{ + if (clc.demoplaying) + { + return DS_PLAYBACK; + } + else if (clc.demorecording) + { + return DS_RECORDING; + } + else + { + return DS_NONE; + } +} + +/* +================== +CL_DemoPos + +Returns the current position of the demo +================== +*/ +int CL_DemoPos(void) +{ + if (clc.demoplaying || clc.demorecording) + { + return FS_FTell(clc.demofile); + } + else + { + return 0; + } +} + +/* +================== +CL_DemoName + +Returns the name of the demo +================== +*/ +void CL_DemoName(char *buffer, int size) +{ + if (clc.demoplaying || clc.demorecording) + { + Q_strncpyz(buffer, clc.demoName, size); + } + else if (size >= 1) + { + buffer[0] = '\0'; + } +} + +//====================================================================== + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState(void) +{ + // S_StopAllSounds(); + + ::memset(&cl, 0, sizeof(cl)); +} + +/* +==================== +CL_UpdateGUID + +update cl_guid using QKEY_FILE and optional prefix +==================== +*/ +static void CL_UpdateGUID(const char *prefix, int prefix_len) +{ + fileHandle_t f; + int len; + + len = FS_SV_FOpenFileRead(QKEY_FILE, &f); + FS_FCloseFile(f); + + if (len != QKEY_SIZE) + Cvar_Set("cl_guid", ""); + else + Cvar_Set("cl_guid", Com_MD5File(QKEY_FILE, QKEY_SIZE, prefix, prefix_len)); +} + +static void CL_OldGame(void) +{ + if (cl_oldGameSet) + { + // change back to previous fs_game + cl_oldGameSet = false; + Cvar_Set2("fs_game", cl_oldGame, true); + FS_ConditionalRestart(clc.checksumFeed, false); + } +} + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect(bool showMainMenu) +{ + if (!com_cl_running || !com_cl_running->integer) + { + return; + } + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if (clc.demorecording) + { + CL_StopRecord_f(); + } + + if (clc.download) + { + FS_FCloseFile(clc.download); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set("cl_downloadName", ""); + +#ifdef USE_MUMBLE + if (cl_useMumble->integer && mumble_islinked()) + { + Com_Printf("Mumble: Unlinking from Mumble application\n"); + mumble_unlink(); + } +#endif + +#ifdef USE_VOIP + if (cl_voipSend->integer) + { + int tmp = cl_voipUseVAD->integer; + cl_voipUseVAD->integer = 0; // disable this for a moment. + clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission. + Cvar_Set("cl_voipSend", "0"); + CL_CaptureVoip(); // clean up any state... + cl_voipUseVAD->integer = tmp; + } + + if (clc.voipCodecInitialized) + { + int i; + opus_encoder_destroy(clc.opusEncoder); + for (i = 0; i < MAX_CLIENTS; i++) + { + opus_decoder_destroy(clc.opusDecoder[i]); + } + } + Cmd_RemoveCommand("voip"); +#endif + + if (clc.demofile) + { + FS_FCloseFile(clc.demofile); + clc.demofile = 0; + } + + if (cls.ui && showMainMenu) + { + VM_Call(cls.ui, UI_SET_ACTIVE_MENU - (cls.uiInterface == 2 ? 2 : 0), UIMENU_NONE); + } + + SCR_StopCinematic(); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if (clc.state >= CA_CONNECTED) + { + CL_AddReliableCommand("disconnect", true); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + // Remove pure paks + FS_PureServerSetLoadedPaks("", ""); + FS_PureServerSetReferencedPaks("", ""); + + CL_ClearState(); + + // wipe the client connection + ::memset(&clc, 0, sizeof(clc)); + + clc.state = CA_DISCONNECTED; + + CL_ProtocolSpecificCommandsInit(); + + // allow cheats locally + Cvar_Set("sv_cheats", "1"); + + // not connected to a pure server anymore + cl_connectedToPureServer = false; + +#ifdef USE_VOIP + // not connected to voip server anymore. + clc.voipEnabled = false; +#endif + + // Stop recording any video + if (CL_VideoRecording()) + { + // Finish rendering current frame + SCR_UpdateScreen(); + CL_CloseAVI(); + } + + CL_UpdateGUID(NULL, 0); + + if (!noGameRestart) + CL_OldGame(); + else + noGameRestart = false; +} + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +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 CL_ForwardCommandToServer(const char *string) +{ + const char *cmd = Cmd_Argv(0); + + // ignore key up commands + if (cmd[0] == '-') + { + return; + } + + if (clc.demoplaying || clc.state < CA_CONNECTED || cmd[0] == '+') + { + Com_Printf("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd); + return; + } + + if (Cmd_Argc() > 1) + { + CL_AddReliableCommand(string, false); + } + else + { + CL_AddReliableCommand(cmd, false); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +static void CL_RequestMotd(void) +{ + char info[MAX_INFO_STRING]; + + if (!cl_motd->integer) + { + return; + } + Com_Printf("Resolving %s\n", MASTER_SERVER_NAME); + + switch (NET_StringToAdr(MASTER_SERVER_NAME, &cls.updateServer, NA_UNSPEC)) + { + case 0: + Com_Printf("Couldn't resolve master address\n"); + return; + + case 2: + cls.updateServer.port = BigShort(PORT_MASTER); + default: + break; + } + + Com_Printf("%s resolved to %s\n", MASTER_SERVER_NAME, NET_AdrToStringwPort(cls.updateServer)); + + info[0] = 0; + + Com_sprintf(cls.updateChallenge, sizeof(cls.updateChallenge), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); + + Info_SetValueForKey(info, "challenge", cls.updateChallenge); + + NET_OutOfBandPrint(NS_CLIENT, cls.updateServer, "getmotd%s", info); +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ShowIP_f +================== +*/ +static void CL_ShowIP_f(void) { Sys_ShowIP(); } + +/* +================== +CL_ForwardToServer_f +================== +*/ +static void CL_ForwardToServer_f(void) +{ + if (clc.state != CA_ACTIVE || clc.demoplaying) + { + Com_Printf("Not connected to a server.\n"); + return; + } + + // don't forward the first argument + if (Cmd_Argc() > 1) + { + CL_AddReliableCommand(Cmd_Args(), false); + } +} + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f(void) +{ + SCR_StopCinematic(); + if (clc.state != CA_DISCONNECTED && clc.state != CA_CINEMATIC) + { + Com_Error(ERR_DISCONNECT, "Disconnected from server"); + } +} + +/* +================ +CL_Reconnect_f + +================ +*/ +static void CL_Reconnect_f(void) +{ + if (!strlen(cl_reconnectArgs)) return; + Cbuf_AddText(va("connect %s\n", cl_reconnectArgs)); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f(void) +{ + const char *server; + int alternateProtocol; + const char *serverString; + int argc = Cmd_Argc(); + netadrtype_t family = NA_UNSPEC; + + if (argc < 2 || argc > 4) + { + Com_Printf("usage: connect [-4|-6] server [-g|-1]\n"); + return; + } + + alternateProtocol = 0; + if (argc == 2) + { + } + else if (!strcmp(Cmd_Argv(argc - 1), "-g")) + { + alternateProtocol = 1; + --argc; + } + else if (!strcmp(Cmd_Argv(argc - 1), "-1")) + { + alternateProtocol = 2; + --argc; + } + else if (argc == 4) + { + --argc; + } + + if (argc == 2) + server = Cmd_Argv(1); + else + { + if (!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if (!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf("warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + // save arguments for reconnect + Q_strncpyz(cl_reconnectArgs, Cmd_Args(), sizeof(cl_reconnectArgs)); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + if (com_sv_running->integer && !strcmp(server, "localhost")) + { + // if running a local server, kill it + SV_Shutdown("Server quit"); + } + + // make sure a local server is killed + Cvar_Set("sv_killserver", "1"); + SV_Frame(0); + + noGameRestart = true; + CL_Disconnect(true); + + Q_strncpyz(clc.servername, server, sizeof(clc.servername)); + + if (!NET_StringToAdr(clc.servername, &clc.serverAddress, family)) + { + Com_Printf("Bad server address\n"); + clc.state = CA_DISCONNECTED; + CL_ProtocolSpecificCommandsInit(); + return; + } + if (clc.serverAddress.port == 0) + { + clc.serverAddress.port = BigShort(PORT_SERVER); + } + clc.serverAddress.alternateProtocol = alternateProtocol; + + serverString = NET_AdrToStringwPort(clc.serverAddress); + + Com_Printf("%s resolved to %s\n", clc.servername, serverString); + + if (cl_guidServerUniq->integer) + CL_UpdateGUID(serverString, strlen(serverString)); + else + CL_UpdateGUID(NULL, 0); + + clc.challenge2[0] = '\0'; + clc.sendSignature = false; + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if (NET_IsLocalAddress(clc.serverAddress)) + { + clc.state = CA_CHALLENGING; + clc.sendSignature = true; + } + else + { + clc.state = CA_CONNECTING; + + // Set a client challenge number that ideally is mirrored back by the server. + clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds(); + } + + Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE); + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; +} + +#define MAX_RCON_MESSAGE 1024 + +/* +================== +CL_CompleteRcon +================== +*/ +static void CL_CompleteRcon(char *args, int argNum) +{ + if (argNum == 2) + { + // Skip "rcon " + char *p = Com_SkipTokens(args, 1, " "); + + if (p > args) Field_CompleteCommand(p, true, true); + } +} + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +static void CL_Rcon_f(void) +{ + char message[MAX_RCON_MESSAGE]; + netadr_t to; + + if (!rcon_client_password->string[0]) + { + Com_Printf( + "You must set 'rconpassword' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + Q_strcat(message, MAX_RCON_MESSAGE, "rcon "); + + Q_strcat(message, MAX_RCON_MESSAGE, rcon_client_password->string); + Q_strcat(message, MAX_RCON_MESSAGE, " "); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + Q_strcat(message, MAX_RCON_MESSAGE, Cmd_Cmd() + 5); + + if (clc.state >= CA_CONNECTED) + { + to = clc.netchan.remoteAddress; + } + else + { + if (!strlen(rconAddress->string)) + { + Com_Printf( + "You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr(rconAddress->string, &to, NA_UNSPEC); + if (to.port == 0) + { + to.port = BigShort(PORT_SERVER); + } + } + + NET_SendPacket(NS_CLIENT, strlen(message) + 1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +static void CL_SendPureChecksums(void) +{ + char cMsg[MAX_INFO_VALUE]; + + // if we are pure we need to send back a command with our referenced pk3 checksums + Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums()); + + CL_AddReliableCommand(cMsg, false); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +static void CL_ResetPureClientAtServer(void) { CL_AddReliableCommand("vdr", false); } + +/* +================= +CL_Snd_Restart + +Restart the sound subsystem +================= +*/ +static void CL_Snd_Shutdown(void) +{ + S_Shutdown(); + cls.soundStarted = false; +} +/* +================== +CL_PK3List_f +================== +*/ +static void CL_OpenedPK3List_f(void) { Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames(false)); } + +/* +================== +CL_PureList_f +================== +*/ +static void CL_ReferencedPK3List_f(void) { Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames(false)); } + +/* +================== +CL_Configstrings_f +================== +*/ +static void CL_Configstrings_f(void) +{ + int i; + int ofs; + + if (clc.state != CA_ACTIVE) + { + Com_Printf("Not connected to a server.\n"); + return; + } + + for (i = 0; i < MAX_CONFIGSTRINGS; i++) + { + ofs = cl.gameState.stringOffsets[i]; + if (!ofs) + { + continue; + } + Com_Printf("%4i: %s\n", i, cl.gameState.stringData + ofs); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +static void CL_Clientinfo_f(void) +{ + Com_Printf("--------- Client Information ---------\n"); + Com_Printf("state: %i\n", clc.state); + Com_Printf("Server: %s\n", clc.servername); + Com_Printf("User info settings:\n"); + Info_Print(Cvar_InfoString(CVAR_USERINFO)); + Com_Printf("--------------------------------------\n"); +} + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +static void CL_DownloadsComplete(void) +{ + Com_Printf("Downloads complete\n"); + + // if we downloaded with cURL + if (clc.cURLUsed) + { + clc.cURLUsed = false; + CL_cURL_Shutdown(); + if (clc.cURLDisconnected) + { + if (clc.downloadRestart) + { + if (!clc.activeCURLNotGameRelated) FS_Restart(clc.checksumFeed); + clc.downloadRestart = false; + } + clc.cURLDisconnected = false; + if (!clc.activeCURLNotGameRelated) CL_Reconnect_f(); + return; + } + } + + // if we downloaded files we need to restart the file system + if (clc.downloadRestart) + { + clc.downloadRestart = false; + + FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand("donedl", false); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + else + { + FS_ClearPakReferences(0); + } + + // let the client game init and load data + clc.state = CA_LOADING; + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if (clc.state != CA_LOADING) + { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = true; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +static void CL_BeginDownload(const char *localName, const char *remoteName) +{ + Com_DPrintf( + "***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", + localName, remoteName); + + Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName)); + Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName); + + // Set so UI gets access to it + Cvar_Set("cl_downloadName", remoteName); + Cvar_Set("cl_downloadSize", "0"); + Cvar_Set("cl_downloadCount", "0"); + Cvar_SetValue("cl_downloadTime", cls.realtime); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + // Stop any errant looping sounds that may be playing + S_ClearLoopingSounds(true); + + CL_AddReliableCommand(va("download %s", remoteName), false); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) +{ + char *s; + char *remoteName, *localName; + bool useCURL = false; + int prompt; + + // A download has finished, check whether this matches a referenced checksum + if (*clc.downloadName && !clc.activeCURLNotGameRelated) + { + char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, ""); + zippath[strlen(zippath) - 1] = '\0'; + + if (!FS_CompareZipChecksum(zippath)) Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName); + } + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set("cl_downloadName", ""); + + // We are looking to start a download here + if (*clc.downloadList) + { + // Prompt if we do not allow automatic downloads + prompt = com_downloadPrompt->integer; + if (!(prompt & DLP_TYPE_MASK) && !(cl_allowDownload->integer & DLF_ENABLE)) + { + char files[MAX_INFO_STRING] = ""; + char *name, *head; + const char *pure_msg; + const char *url_msg = ""; + int i = 0, others = 0, swap = 0, max_list = 12; + + // Set the download URL message + if ((clc.sv_allowDownload & DLF_ENABLE) && !(clc.sv_allowDownload & DLF_NO_REDIRECT)) + { + url_msg = va("The server redirects to the following URL:\n%s", clc.sv_dlURL); + max_list -= 6; + } + + // Make a pretty version of the download list + name = clc.downloadList; + if (*name == '@') name++; + + do + { + // Copy remote name + head = name; + while (*head && *head != '@') head++; + + swap = *head; + *head = 0; + + if (i++ < max_list) + { + if (i > 1) Q_strcat(files, sizeof(files), ", "); + Q_strcat(files, sizeof(files), name); + } + else + { + others++; + } + + *head = swap; + if (!swap) break; + + // Skip local name + head++; + while (*head && *head != '@') head++; + + name = head + 1; + } while (*head); + + if (others) + { + Q_strcat(files, sizeof(files), va("(%d other file%s)\n", others, others > 1 ? "s" : "")); + } + + // Set the pure message + if (cl_connectedToPureServer) + { + if (!(clc.sv_allowDownload & DLF_ENABLE) || + ((clc.sv_allowDownload & DLF_NO_UDP) && (clc.sv_allowDownload & DLF_NO_REDIRECT))) + { + pure_msg = + "You are missing files required by the server. " + "The server does not allow downloading. " + "You must install these files manually:"; + } + else + { + pure_msg = + "You are missing files required by the server. " + "You must download these files or disconnect:"; + } + } + else + { + pure_msg = + "You are missing optional files provided by the " + "server. You may not need them to play but can " + "choose to download them anyway:"; + } + + Cvar_Set("com_downloadPromptText", va("%s\n\n%s\n%s", pure_msg, files, url_msg)); + Cvar_Set("com_downloadPrompt", va("%d", DLP_SHOW)); + return; + } + + if (!(prompt & DLP_PROMPTED)) Cvar_Set("com_downloadPrompt", va("%d", prompt | DLP_PROMPTED)); + + prompt &= DLP_TYPE_MASK; + + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') s++; + remoteName = s; + + if ((s = strchr(s, '@')) == NULL) + { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ((s = strchr(s, '@')) != NULL) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte + + if (((cl_allowDownload->integer & DLF_ENABLE) && !(cl_allowDownload->integer & DLF_NO_REDIRECT)) || + prompt == DLP_CURL) + { + Com_Printf("Trying CURL download: %s; %s\n", localName, remoteName); + if (clc.sv_allowDownload & DLF_NO_REDIRECT) + { + Com_Printf( + "WARNING: server does not " + "allow download redirection " + "(sv_allowDownload is %d)\n", + clc.sv_allowDownload); + } + else if (!*clc.sv_dlURL) + { + Com_Printf( + "WARNING: server allows " + "download redirection, but does not " + "have sv_dlURL set\n"); + } + else if (!CL_cURL_Init()) + { + Com_Printf( + "WARNING: could not load " + "cURL library\n"); + } + else + { + CL_cURL_BeginDownload(localName, va("%s/%s", clc.sv_dlURL, remoteName)); + useCURL = true; + } + } + else if (!(clc.sv_allowDownload & DLF_NO_REDIRECT)) + { + Com_Printf( + "WARNING: server allows download " + "redirection, but it disabled by client " + "configuration (cl_allowDownload is %d)\n", + cl_allowDownload->integer); + } + if (!useCURL) + { + Com_Printf("Trying UDP download: %s; %s\n", localName, remoteName); + + if ((!(cl_allowDownload->integer & DLF_ENABLE) || (cl_allowDownload->integer & DLF_NO_UDP)) && + prompt != DLP_UDP) + { + if (cl_connectedToPureServer) + { + Com_Error(ERR_DROP, + "Automatic downloads are " + "disabled on your client (cl_allowDownload is %d). " + "You can enable automatic downloads in the Options " + "menu.", + cl_allowDownload->integer); + return; + } + + Com_Printf("WARNING: UDP downloads are disabled.\n"); + CL_DownloadsComplete(); + return; + } + else + { + CL_BeginDownload(localName, remoteName); + } + } + clc.downloadRestart = true; + + // move over the rest + memmove(clc.downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) +{ + if (FS_ComparePaks(clc.downloadList, sizeof(clc.downloadList), true)) + { + Com_Printf("Need paks: %s\n", clc.downloadList); + + Cvar_Set("com_downloadPrompt", "0"); + if (*clc.downloadList) + { + // if autodownloading is not enabled on the server + clc.state = CA_CONNECTED; + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set("cl_downloadName", ""); + CL_NextDownload(); + return; + } + } + CL_DownloadsComplete(); +} + +/* +=============== +CL_UnloadRSAKeypair +=============== +*/ +static void CL_UnloadRSAKeypair(void) +{ + rsa_public_key_clear(&cls.rsa.public_key); + rsa_private_key_clear(&cls.rsa.private_key); +} + +/* +=============== +CL_WriteRSAPublicKey +=============== +*/ +static bool CL_WriteRSAPublicKey(void) +{ + struct nettle_buffer key_buffer; + fileHandle_t f; + + f = FS_SV_FOpenFileWrite(RSA_PUBLIC_KEY_FILE); + if (!f) return false; + + nettle_buffer_init(&key_buffer); + if (!rsa_keypair_to_sexp(&key_buffer, NULL, &cls.rsa.public_key, NULL)) + { + FS_FCloseFile(f); + nettle_buffer_clear(&key_buffer); + return false; + } + + FS_Write(key_buffer.contents, key_buffer.size, f); + FS_FCloseFile(f); + + nettle_buffer_clear(&key_buffer); + return true; +} + +/* +=============== +CL_WriteRSAPrivateKey +=============== +*/ +static bool CL_WriteRSAPrivateKey(void) +{ + struct nettle_buffer key_buffer; + fileHandle_t f; + +#ifndef _WIN32 + int old_umask = umask(0377); +#endif + f = FS_SV_FOpenFileWrite(RSA_PRIVATE_KEY_FILE); +#ifndef _WIN32 + umask(old_umask); +#endif + if (!f) return false; + + nettle_buffer_init(&key_buffer); + if (!rsa_keypair_to_sexp(&key_buffer, NULL, &cls.rsa.public_key, &cls.rsa.private_key)) + { + FS_FCloseFile(f); + nettle_buffer_clear(&key_buffer); + return false; + } + + FS_Write(key_buffer.contents, key_buffer.size, f); + FS_FCloseFile(f); + + nettle_buffer_clear(&key_buffer); + return true; +} + +/* +=============== +CL_GenerateRSAKeypair + +public_key and private_key must already be inititalized before calling this +function. This is done by CL_LoadRSAKeypair. +=============== +*/ +static void CL_GenerateRSAKeypair(void) +{ + mpz_set_ui(cls.rsa.public_key.e, RSA_PUBLIC_EXPONENT); + + int success = rsa_generate_keypair( + &cls.rsa.public_key, &cls.rsa.private_key, NULL, qnettle_random, NULL, NULL, RSA_KEY_LENGTH, 0); + if (success) + if (CL_WriteRSAPrivateKey()) + if (CL_WriteRSAPublicKey()) + { + Com_Printf("RSA keypair generated\n"); + return; + } + + // failure + CL_UnloadRSAKeypair(); + Com_Printf("Error generating RSA keypair, setting cl_rsaAuth to 0\n"); + Cvar_Set("cl_rsaAuth", "0"); +} + +/* +=============== +CL_LoadRSAKeypair + +Attempt to load RSA keys from RSA_PRIVATE_KEY_FILE +If this fails, generate a new keypair +=============== +*/ +static void CL_LoadRSAKeypair(void) +{ + int len; + fileHandle_t f; + uint8_t *buf; + + rsa_public_key_init(&cls.rsa.public_key); + rsa_private_key_init(&cls.rsa.private_key); + + Com_DPrintf("Loading RSA private key from %s\n", RSA_PRIVATE_KEY_FILE); + + len = FS_SV_FOpenFileRead(RSA_PRIVATE_KEY_FILE, &f); + if (!f) + { + Com_DPrintf("RSA private key not found, generating\n"); + CL_GenerateRSAKeypair(); + return; + } + + if (len < 1) + { + Com_DPrintf("RSA private key empty, generating\n"); + FS_FCloseFile(f); + CL_GenerateRSAKeypair(); + return; + } + + buf = (uint8_t *)Z_Malloc(len); + FS_Read(buf, len, f); + FS_FCloseFile(f); + + if (!rsa_keypair_from_sexp(&cls.rsa.public_key, &cls.rsa.private_key, 0, len, buf)) + { + memset(buf, 0, len); + Z_Free(buf); + CL_UnloadRSAKeypair(); + Com_Error(ERR_FATAL, "Invalid RSA private key found."); + return; + } + + memset(buf, 0, len); + Z_Free(buf); + + len = FS_SV_FOpenFileRead(RSA_PUBLIC_KEY_FILE, &f); + if (!f || len < 1) CL_WriteRSAPublicKey(); + if (f) FS_FCloseFile(f); + + Com_DPrintf("RSA private key loaded\n"); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +static void CL_CheckForResend(void) +{ + int port; + char info[MAX_INFO_STRING]; + char data[MAX_MSGLEN]; + + // don't send anything if playing back a demo + if (clc.demoplaying) + { + return; + } + + // resend if we haven't gotten a reply yet + if (clc.state != CA_CONNECTING && clc.state != CA_CHALLENGING) + { + return; + } + + if (cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT) + { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + switch (clc.state) + { + case CA_CONNECTING: + // requesting a challenge + + // The challenge request shall be followed by a client challenge so no malicious server can hijack this + // connection. + // Add the gamename so the server knows we're running the correct game or can reject the client + // with a meaningful message + if (clc.serverAddress.alternateProtocol == 2) + { + Com_sprintf(data, sizeof(data), "getchallenge"); + } + else if (clc.serverAddress.alternateProtocol == 1) + { + Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge); + } + else + Com_sprintf(data, sizeof(data), "getchallenge %d %s", clc.challenge, com_gamename->string); + + NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue("net_qport"); + + Q_strncpyz(info, Cvar_InfoString(CVAR_USERINFO), sizeof(info)); + Info_SetValueForKey(info, "protocol", + va("%i", + (clc.serverAddress.alternateProtocol == 0 ? PROTOCOL_VERSION + : clc.serverAddress.alternateProtocol == 1 ? 70 : 69))); + Info_SetValueForKey(info, "qport", va("%i", port)); + Info_SetValueForKey(info, "challenge", va("%i", clc.challenge)); + + if (cl_rsaAuth->integer && clc.sendSignature) + { + char public_key[RSA_STRING_LENGTH]; + char signature[RSA_STRING_LENGTH]; + struct sha256_ctx sha256_hash; + mpz_t n; + + Info_SetValueForKey(info, "challenge2", clc.challenge2); + + sha256_init(&sha256_hash); + sha256_update(&sha256_hash, strlen(info), (uint8_t *)info); + + mpz_init(n); + rsa_sha256_sign(&cls.rsa.private_key, &sha256_hash, n); + mpz_get_str(signature, 16, n); + mpz_clear(n); + + mpz_get_str(public_key, 16, cls.rsa.public_key.n); + + Com_sprintf(data, sizeof(data), "connect \"%s\" %s %s", info, public_key, signature); + } + else + { + Com_sprintf(data, sizeof(data), "connect \"%s\"", info); + } + + NET_OutOfBandData(NS_CLIENT, clc.serverAddress, (byte *)data, strlen(data)); + // the most current userinfo has been sent, so watch for any + // newer changes to userinfo variables + cvar_modifiedFlags &= ~CVAR_USERINFO; + break; + + default: + Com_Error(ERR_FATAL, "CL_CheckForResend: bad clc.state"); + } +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading(void) +{ + if (com_dedicated->integer) + { + clc.state = CA_DISCONNECTED; + Key_SetCatcher(KEYCATCH_CONSOLE); + CL_ProtocolSpecificCommandsInit(); + return; + } + + if (!com_cl_running->integer) + { + return; + } + + Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE); + + // if we are already connected to the local host, stay connected + if (clc.state >= CA_CONNECTED && !Q_stricmp(clc.servername, "localhost")) + { + clc.state = CA_CONNECTED; // so the connect screen is drawn + ::memset(cls.updateInfoString, 0, sizeof(cls.updateInfoString)); + ::memset(clc.serverMessage, 0, sizeof(clc.serverMessage)); + ::memset(&cl.gameState, 0, sizeof(cl.gameState)); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } + else + { + CL_Disconnect(true); + Q_strncpyz(clc.servername, "localhost", sizeof(clc.servername)); + clc.state = CA_CHALLENGING; // so the connect screen is drawn + Key_SetCatcher(Key_GetCatcher() & KEYCATCH_CONSOLE); + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr(clc.servername, &clc.serverAddress, NA_UNSPEC); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +=================== +CL_MotdPacket + +=================== +*/ +static void CL_MotdPacket(netadr_t from, const char *info) +{ + const char *v; + + // if not from our server, ignore it + if (!NET_CompareAdr(from, cls.updateServer)) + { + Com_DPrintf("MOTD packet from unexpected source\n"); + return; + } + + Com_DPrintf("MOTD packet: %s\n", info); + while (*info != '\\') info++; + + // check challenge + v = Info_ValueForKey(info, "challenge"); + if (strcmp(v, cls.updateChallenge)) + { + Com_DPrintf( + "MOTD packet mismatched challenge: " + "'%s' != '%s'\n", + v, cls.updateChallenge); + return; + } + + v = Info_ValueForKey(info, "motd"); + + Q_strncpyz(cls.updateInfoString, info, sizeof(cls.updateInfoString)); + Cvar_Set("cl_motdString", v); +} + +/* +=================== +CL_InitServerInfo +=================== +*/ +static void CL_InitServerInfo(serverInfo_t *server, netadr_t *address) +{ + server->adr = *address; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->label[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; +} + +/* +=================== +CL_GSRSequenceInformation + +Parses this packet's index and the number of packets from a master server's +response. Updates the packet count and returns the index. Advances the data +pointer as appropriate (but only when parsing was successful) + +The sequencing information isn't terribly useful at present (we can skip +duplicate packets, but we don't bother to make sure we've got all of them). +=================== +*/ +static int CL_GSRSequenceInformation(int alternateProtocol, byte **data) +{ + char *p = (char *)*data, *e; + int ind, num; + // '\0'-delimited fields: this packet's index, total number of packets + if (*p++ != '\0') return -1; + + ind = strtol(p, (char **)&e, 10); + if (*e++ != '\0') return -1; + + num = strtol(e, (char **)&p, 10); + if (*p++ != '\0') return -1; + + if (num <= 0 || ind <= 0 || ind > num) return -1; // nonsensical response + + if (cls.numAlternateMasterPackets[alternateProtocol] > 0 && num != cls.numAlternateMasterPackets[alternateProtocol]) + { + // Assume we sent two getservers and somehow they changed in + // between - only use the results that arrive later + Com_DPrintf("Master changed its mind about%s packet count!\n", + (alternateProtocol == 0 ? "" : alternateProtocol == 1 ? " alternate-1" : " alternate-2")); + cls.receivedAlternateMasterPackets[alternateProtocol] = 0; + // cls.numglobalservers = 0; + // cls.numGlobalServerAddresses = 0; + } + cls.numAlternateMasterPackets[alternateProtocol] = num; + + // successfully parsed + *data = (byte *)p; + return ind; +} + +/* +=================== +CL_GSRFeaturedLabel + +Parses from the data an arbitrary text string labelling the servers in the +following getserversresponse packet. +The result is copied to *buf, and *data is advanced as appropriate +=================== +*/ +static void CL_GSRFeaturedLabel(byte **data, char *buf, int size) +{ + char *l = buf; + + // copy until '\0' which indicates field break + // or slash which indicates beginning of server list + while (**data && **data != '\\' && **data != '/') + { + if (l < &buf[size - 1]) + *l = **data; + else if (l == &buf[size - 1]) + Com_DPrintf(S_COLOR_YELLOW + "Warning: " + "CL_GSRFeaturedLabel: overflow\n"); + l++, (*data)++; + } + + if (l < &buf[size - 1]) + *l = '\0'; + else + buf[size - 1] = '\0'; +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +static void CL_ServersResponsePacket(const netadr_t *from, msg_t *msg, bool extended) +{ + int i, count, total; + netadr_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte *buffptr; + byte *buffend; + char label[MAX_FEATLABEL_CHARS] = ""; + + Com_DPrintf("CL_ServersResponsePacket from %s %s\n", + NET_AdrToStringwPort(*from), + extended ? " (extended)" : ""); + + if (cls.numglobalservers == -1) + { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + for (i = 0; i < 3; ++i) + { + cls.numAlternateMasterPackets[i] = 0; + cls.receivedAlternateMasterPackets[i] = 0; + } + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + + // skip header + buffptr += 4; + + // advance to initial token + // I considered using strchr for this but I don't feel like relying + // on its behaviour with '\0' + while (*buffptr && *buffptr != '\\' && *buffptr != '/') + { + buffptr++; + + if (buffptr >= buffend) break; + } + + if (*buffptr == '\0') + { + int ind = CL_GSRSequenceInformation(from->alternateProtocol, &buffptr); + if (ind >= 0) + { + // this denotes the start of new-syntax stuff + // have we already received this packet? + if (cls.receivedAlternateMasterPackets[from->alternateProtocol] & (1 << (ind - 1))) + { + Com_DPrintf( + "CL_ServersResponsePacket: " + "received packet %d again, ignoring\n", + ind); + return; + } + // TODO: detect dropped packets and make another + // request + Com_DPrintf( + "CL_ServersResponsePacket:%s packet " + "%d of %d\n", + (from->alternateProtocol == 0 ? "" : from->alternateProtocol == 1 ? " alternate-1" : " alternate-2"), + ind, cls.numAlternateMasterPackets[from->alternateProtocol]); + cls.receivedAlternateMasterPackets[from->alternateProtocol] |= (1 << (ind - 1)); + + CL_GSRFeaturedLabel(&buffptr, label, sizeof(label)); + } + // now skip to the server list + for (; buffptr < buffend && *buffptr != '\\' && *buffptr != '/'; buffptr++) + ; + } + + while (buffptr + 1 < buffend) + { + // IPv4 address + if (*buffptr == '\\') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1) break; + + for (i = 0; i < sizeof(addresses[numservers].ip); i++) addresses[numservers].ip[i] = *buffptr++; + + addresses[numservers].type = NA_IP; + } + // IPv6 address, if it's an extended response + else if (extended && *buffptr == '/') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1) break; + + for (i = 0; i < sizeof(addresses[numservers].ip6); i++) addresses[numservers].ip6[i] = *buffptr++; + + addresses[numservers].type = NA_IP6; + addresses[numservers].scope_id = from->scope_id; + } + else + // syntax error! + break; + + // parse out port + addresses[numservers].port = (*buffptr++) << 8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort(addresses[numservers].port); + + // syntax check + if (*buffptr != '\\' && *buffptr != '/') break; + + addresses[numservers].alternateProtocol = from->alternateProtocol; + + numservers++; + if (numservers >= MAX_SERVERSPERPACKET) break; + } + + count = cls.numglobalservers; + + for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) + { + // build net address + serverInfo_t *server = &cls.globalServers[count]; + + CL_InitServerInfo(server, &addresses[i]); + Q_strncpyz(server->label, label, sizeof(server->label)); + // advance to next slot + count++; + } + + // if getting the global list + if (count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS) + { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++) + { + // just store the addresses in an additional list + cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i]; + } + } + + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + + Com_Printf("%d servers parsed (total %d)\n", numservers, total); +} +/* +================== +CL_CheckTimeout + +================== +*/ +static void CL_CheckTimeout(void) +{ + // + // check timeout + // + if ((!CL_CheckPaused() || !sv_paused->integer) && clc.state >= CA_CONNECTED && clc.state != CA_CINEMATIC && + cls.realtime - clc.lastPacketTime > cl_timeout->value * 1000) + { + if (++cl.timeoutcount > 5) + { // timeoutcount saves debugger + Com_Printf("\nServer connection timed out.\n"); + CL_Disconnect(true); + return; + } + } + else + { + cl.timeoutcount = 0; + } +} + +/* +================== +CL_CheckPaused +Check whether client has been paused. +================== +*/ +bool CL_CheckPaused(void) +{ // if cl_paused->modified is set, the cvar has only been changed in + // this frame. Keep paused in this frame to ensure the server doesn't + // lag behind. + if (cl_paused->integer || cl_paused->modified) return true; + + return false; +} + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +static void CL_CheckUserinfo(void) +{ + // don't add reliable commands when not yet connected + if (clc.state < CA_CONNECTED) return; + + // don't overflow the reliable command buffer when paused + if (CL_CheckPaused()) return; + + // send a reliable userinfo update if needed + if (cvar_modifiedFlags & CVAR_USERINFO) + { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString(CVAR_USERINFO)), false); + } +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame(int msec) +{ + if (!com_cl_running->integer) return; + + // We may have a download prompt ready + if ((com_downloadPrompt->integer & DLP_TYPE_MASK) && !(com_downloadPrompt->integer & DLP_PROMPTED)) + { + Com_Printf("Download prompt returned %d\n", com_downloadPrompt->integer); + CL_NextDownload(); + } + else if (com_downloadPrompt->integer & DLP_SHOW) + { + // If the UI VM does not support the download prompt, we need to catch + // the prompt here and replicate regular behavior. + // One frame will always run between requesting and showing the prompt. + + if (com_downloadPrompt->integer & DLP_STALE) + { + Com_Printf("WARNING: UI VM does not support download prompt\n"); + Cvar_Set("com_downloadPrompt", va("%d", DLP_IGNORE)); + CL_NextDownload(); + } + else + { + Cvar_Set("com_downloadPrompt", va("%d", com_downloadPrompt->integer | DLP_STALE)); + } + } + + if (clc.downloadCURLM) + { + CL_cURL_PerformDownload(); + // we can't process frames normally when in disconnected download mode + // since the ui vm expects clc.state to be CA_CONNECTED + if (clc.cURLDisconnected) + { + cls.realFrametime = msec; + cls.frametime = msec; + cls.realtime += cls.frametime; + + SCR_UpdateScreen(); + S_Update(); + Con_RunConsole(); + + cls.framecount++; + return; + } + } + + if (clc.state == CA_DISCONNECTED && !(Key_GetCatcher() & KEYCATCH_UI) && !com_sv_running->integer && cls.ui) + { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call(cls.ui, UI_SET_ACTIVE_MENU - (cls.uiInterface == 2 ? 2 : 0), UIMENU_MAIN); + } + + // if recording an avi, lock to a fixed fps + if (CL_VideoRecording() && cl_aviFrameRate->integer && msec) + { + // save the current screen + if (clc.state == CA_ACTIVE || cl_forceavidemo->integer) + { + float fps = MIN(cl_aviFrameRate->value * com_timescale->value, 1000.0f); + float frameDuration = MAX(1000.0f / fps, 1.0f) + clc.aviVideoFrameRemainder; + + CL_TakeVideoFrame(); + + msec = (int)frameDuration; + clc.aviVideoFrameRemainder = frameDuration - msec; + } + } + + if (cl_autoRecordDemo->integer) + { + if (clc.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying) + { + // If not recording a demo, and we should be, start one + qtime_t now; + Com_RealTime(&now); + + const char *nowString = va("%04d%02d%02d%02d%02d%02d", 1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec); + + char serverName[MAX_OSPATH]; + Q_strncpyz(serverName, clc.servername, MAX_OSPATH); + + // Replace the ":" in the address as it is not a valid + // file name character + char *p = strstr(serverName, ":"); + if (p) *p = '.'; + + char mapName[MAX_QPATH]; + Q_strncpyz(mapName, COM_SkipPath(cl.mapname), sizeof(cl.mapname)); + COM_StripExtension(mapName, mapName, sizeof(mapName)); + Cbuf_ExecuteText(EXEC_NOW, va("record \"%s-%s-%s\"\n", nowString, serverName, mapName)); + } + else if (clc.state != CA_ACTIVE && clc.demorecording) + { + // Recording, but not CA_ACTIVE, so stop recording + CL_StopRecord_f(); + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if (cl_timegraph->integer) + { + SCR_DebugGraph(cls.realFrametime * 0.25); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + +#ifdef USE_VOIP + CL_CaptureVoip(); +#endif + +#ifdef USE_MUMBLE + CL_UpdateMumble(); +#endif + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +static __attribute__((format(printf, 2, 3))) void QDECL CL_RefPrintf(int print_level, const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start(argptr, fmt); + Q_vsnprintf(msg, sizeof(msg), fmt, argptr); + va_end(argptr); + + switch (print_level) + { + case PRINT_ALL: + Com_Printf("%s", msg); + break; + + case PRINT_WARNING: + Com_Printf(S_COLOR_YELLOW "%s", msg); + break; + + case PRINT_DEVELOPER: + Com_DPrintf(S_COLOR_RED "%s", msg); + break; + } +} + +/* +============ +CL_ShutdownRef +============ +*/ +static void CL_ShutdownRef(void) +{ + if (re.Shutdown) re.Shutdown(true); + + ::memset(&re, 0, sizeof(re)); + +#ifdef USE_RENDERER_DLOPEN + if (rendererLib) + { + Sys_UnloadLibrary(rendererLib); + rendererLib = NULL; + } +#endif +} + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(bool shutdownRef) +{ + if (CL_VideoRecording()) CL_CloseAVI(); + + if (clc.demorecording) CL_StopRecord_f(); + + CL_cURL_Shutdown(); + + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if (shutdownRef) + CL_ShutdownRef(); + else if (re.Shutdown) + re.Shutdown(false); // don't destroy window or context + + cls.uiStarted = false; + cls.cgameStarted = false; + cls.rendererStarted = false; + cls.soundRegistered = false; +} + +/* +================= +CL_ClearMemory + +Called by Com_GameRestart +================= +*/ +static void CL_ClearMemory(bool shutdownRef) +{ + // shutdown all the client stuff + CL_ShutdownAll(shutdownRef); + + // if not running a server clear the whole hunk + if (!com_sv_running->integer) + { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } + else + { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory(void) +{ + CL_ClearMemory(false); + CL_StartHunkUsers(false); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +static void CL_Vid_Restart_f(void) +{ + // Settings may have changed so stop recording now + if (CL_VideoRecording()) + { + CL_CloseAVI(); + } + + if (clc.demorecording) CL_StopRecord_f(); + + // don't let them loop during the restart + S_StopAllSounds(); + + if (!FS_ConditionalRestart(clc.checksumFeed, true)) + { + // if not running a server clear the whole hunk + if (com_sv_running->integer) + { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + else + { + // clear the whole hunk + Hunk_Clear(); + } + + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences(FS_UI_REF | FS_CGAME_REF); + // reinitialize the filesystem if the game directory or checksum has changed + + cls.rendererStarted = false; + cls.uiStarted = false; + cls.cgameStarted = false; + cls.soundRegistered = false; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set("cl_paused", "0"); + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers(false); + + // start the cgame if connected + if (clc.state > CA_CONNECTED && clc.state != CA_CINEMATIC) + { + cls.cgameStarted = true; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } + } +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +static void CL_Snd_Restart_f(void) +{ + CL_Snd_Shutdown(); + // sound will be reinitialized by vid_restart + CL_Vid_Restart_f(); +} + + +/* +============ +CL_InitRenderer +============ +*/ +static void CL_InitRenderer(void) +{ + // this sets up the renderer and calls R_Init + re.BeginRegistration(&cls.glconfig); + + // load character sets + cls.charSetShader = re.RegisterShader("gfx/2d/bigchars"); + cls.whiteShader = re.RegisterShader("white"); + cls.consoleShader = re.RegisterShader("console"); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers(bool rendererOnly) +{ + if (!com_cl_running) + { + return; + } + + if (!com_cl_running->integer) + { + return; + } + + if (!cls.rendererStarted) + { + cls.rendererStarted = true; + CL_InitRenderer(); + } + + if (rendererOnly) + { + return; + } + + if (!cls.soundStarted) + { + cls.soundStarted = true; + S_Init(); + } + + if (!cls.soundRegistered) + { + cls.soundRegistered = true; + S_BeginRegistration(); + } + + if (com_dedicated->integer) + { + return; + } + + if (!cls.uiStarted) + { + cls.uiStarted = true; + CL_InitUI(); + } +} + +/* +============ +CL_RefMalloc +============ +*/ +static void *CL_RefMalloc(int size) { return Z_TagMalloc(size, TAG_RENDERER); } + +/* +============ +CL_ScaledMilliseconds +============ +*/ +int CL_ScaledMilliseconds(void) { return Sys_Milliseconds() * com_timescale->value; } + +//=========================================================================================== + +static void CL_SetModel_f(void) +{ + char name[256]; + + const char *arg = Cmd_Argv(1); + if (arg[0]) + { + Cvar_Set("model", arg); + Cvar_Set("headmodel", arg); + } + else + { + Cvar_VariableStringBuffer("model", name, sizeof(name)); + Com_Printf("model is set to %s\n", name); + } +} + +//=========================================================================================== + +/* +=============== +CL_Video_f + +video +video [filename] +=============== +*/ +static void CL_Video_f(void) +{ + char filename[MAX_OSPATH]; + + if (!clc.demoplaying) + { + Com_Printf("The video command can only be used when playing back demos\n"); + return; + } + + if (Cmd_Argc() == 2) + { + // explicit filename + Com_sprintf(filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv(1)); + } + else + { + int i, last; + + // scan for a free filename + for (i = 0; i <= 9999; i++) + { + int a, b, c, d; + + last = i; + + a = last / 1000; + last -= a * 1000; + b = last / 100; + last -= b * 100; + c = last / 10; + last -= c * 10; + d = last; + + Com_sprintf(filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", a, b, c, d); + + if (!FS_FileExists(filename)) break; // file doesn't exist + } + + if (i > 9999) + { + Com_Printf(S_COLOR_RED "ERROR: no free file names to create video\n"); + return; + } + } + + CL_OpenAVIForWriting(filename); +} + +/* +=============== +CL_StopVideo_f +=============== +*/ +static void CL_StopVideo_f(void) { CL_CloseAVI(); } +/* +=============== +CL_GenerateQKey + +test to see if a valid QKEY_FILE exists. If one does not, try to generate +it by filling it with 2048 bytes of random data. +=============== +*/ +static void CL_GenerateQKey(void) +{ + int len = 0; + unsigned char buff[QKEY_SIZE]; + fileHandle_t f; + + len = FS_SV_FOpenFileRead(QKEY_FILE, &f); + FS_FCloseFile(f); + if (len == QKEY_SIZE) + { + Com_Printf("QKEY found.\n"); + return; + } + else + { + if (len > 0) + { + Com_Printf("QKEY file size != %d, regenerating\n", QKEY_SIZE); + } + + Com_Printf("QKEY building random string\n"); + Com_RandomBytes(buff, sizeof(buff)); + + f = FS_SV_FOpenFileWrite(QKEY_FILE); + if (!f) + { + Com_Printf("QKEY could not open %s for write\n", QKEY_FILE); + return; + } + FS_Write(buff, sizeof(buff), f); + FS_FCloseFile(f); + Com_Printf("QKEY generated\n"); + } +} + +static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) +{ + if (server) + { + if (info) + { + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostName, Info_ValueForKey(info, "hostname"), MAX_HOSTNAME_LENGTH); + Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); + server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + server->gameType = atoi(Info_ValueForKey(info, "gametype")); + server->netType = atoi(Info_ValueForKey(info, "nettype")); + server->minPing = atoi(Info_ValueForKey(info, "minping")); + server->maxPing = atoi(Info_ValueForKey(info, "maxping")); + const char *game = Info_ValueForKey(info, "game"); + Q_strncpyz(server->game, (game[0]) ? game : BASEGAME, MAX_NAME_LENGTH); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) +{ + int i; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) + { + if (NET_CompareAdr(from, cls.localServers[i].adr)) + { + CL_SetServerInfo(&cls.localServers[i], info, ping); + } + } + + for (i = 0; i < MAX_GLOBAL_SERVERS; i++) + { + if (NET_CompareAdr(from, cls.globalServers[i].adr)) + { + CL_SetServerInfo(&cls.globalServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) + { + if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) + { + CL_SetServerInfo(&cls.favoriteServers[i], info, ping); + } + } +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +static void CL_ServerInfoPacket(netadr_t from, msg_t *msg) +{ + int i, type; + char info[MAX_INFO_STRING]; + char *infoString; + int prot; + char *gamename; + bool gameMismatch; + + infoString = MSG_ReadString(msg); + + if (from.alternateProtocol == 0) + { + // if this isn't the correct gamename, ignore it + gamename = Info_ValueForKey(infoString, "gamename"); + + gameMismatch = !*gamename || strcmp(gamename, com_gamename->string) != 0; + + if (gameMismatch) + { + Com_DPrintf("Game mismatch in info packet: %s\n", infoString); + return; + } + } + + // if this isn't the correct protocol version, ignore it + prot = atoi(Info_ValueForKey(infoString, "protocol")); + if (prot != (from.alternateProtocol == 0 ? PROTOCOL_VERSION : from.alternateProtocol == 1 ? 70 : 69)) + { + Com_DPrintf("Different protocol info packet: %s\n", infoString); + return; + } + + // iterate servers waiting for ping response + for (i = 0; i < MAX_PINGREQUESTS; i++) + { + if (cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr(from, cl_pinglist[i].adr)) + { + // calc ping time + cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start; + Com_DPrintf("ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString(from)); + + // save of info + Q_strncpyz(cl_pinglist[i].info, infoString, sizeof(cl_pinglist[i].info)); + + // tack on the net type + // NOTE: make sure these types are in sync with the netnames strings in the UI + switch (from.type) + { + case NA_BROADCAST: + case NA_IP: + type = 1; + break; + case NA_IP6: + type = 2; + break; + default: + type = 0; + break; + } + Info_SetValueForKey(cl_pinglist[i].info, "nettype", va("%d", type)); + CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time); + + return; + } + } + + // if not just sent a local broadcast or pinging local servers + if (cls.pingUpdateSource != AS_LOCAL) + { + return; + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) + { + // empty slot + if (cls.localServers[i].adr.port == 0) + { + break; + } + + // avoid duplicate + if (NET_CompareAdr(from, cls.localServers[i].adr)) + { + return; + } + } + + if (i == MAX_OTHER_SERVERS) + { + Com_DPrintf("MAX_OTHER_SERVERS hit, dropping infoResponse\n"); + return; + } + + // add this to the list + cls.numlocalservers = i + 1; + CL_InitServerInfo(&cls.localServers[i], &from); + + Q_strncpyz(info, MSG_ReadString(msg), MAX_INFO_STRING); + if (strlen(info)) + { + if (info[strlen(info) - 1] != '\n') + { + Q_strcat(info, sizeof(info), "\n"); + } + Com_Printf("%s: %s", NET_AdrToStringwPort(from), info); + } +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +static void CL_ServerStatusResponse(netadr_t from, msg_t *msg) +{ + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + if (NET_CompareAdr(from, cl_serverStatusList[i].address)) + { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if (!serverStatus) + { + return; + } + + const char *s = MSG_ReadStringLine(msg); + + len = 0; + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "%s", s); + + if (serverStatus->print) + { + Com_Printf("Server settings:\n"); + // print cvars + while (*s) + { + for (i = 0; i < 2 && *s; i++) + { + if (*s == '\\') s++; + l = 0; + while (*s) + { + info[l++] = *s; + if (l >= MAX_INFO_STRING - 1) break; + s++; + if (*s == '\\') + { + break; + } + } + info[l] = '\0'; + if (i) + { + Com_Printf("%s\n", info); + } + else + { + Com_Printf("%-24s", info); + } + } + } + } + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "\\"); + + if (serverStatus->print) + { + Com_Printf("\nPlayers:\n"); + Com_Printf("num: score: ping: name:\n"); + } + for (i = 0, s = MSG_ReadStringLine(msg); *s; s = MSG_ReadStringLine(msg), i++) + { + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "\\%s", s); + + if (serverStatus->print) + { + score = ping = 0; + sscanf(s, "%d %d", &score, &ping); + s = strchr(s, ' '); + if (s) s = strchr(s + 1, ' '); + if (s) + s++; + else + s = "unknown"; + Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s); + } + } + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string) - len, "\\"); + + serverStatus->time = Com_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = false; + if (serverStatus->print) + { + serverStatus->retrieved = true; + } +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +static void CL_ConnectionlessPacket(netadr_t from, msg_t *msg) +{ + int challenge = 0; + + MSG_BeginReadingOOB(msg); + MSG_ReadLong(msg); // skip the -1 + + const char *s = MSG_ReadStringLine(msg); + + Cmd_TokenizeString(s); + + const char *c = Cmd_Argv(0); + + Com_DPrintf("CL packet %s: %s\n", NET_AdrToStringwPort(from), c); + + // challenge from the server we are connecting to + if (!Q_stricmp(c, "challengeResponse")) + { + int ver; + + if (clc.state != CA_CONNECTING) + { + Com_DPrintf("Unwanted challenge response received. Ignored.\n"); + return; + } + + const char *strver = Cmd_Argv(3); + if (*strver) + { + ver = atoi(strver); + + if (ver != PROTOCOL_VERSION) + { + Com_Printf(S_COLOR_YELLOW + "Warning: Server reports protocol version %d, we have %d. " + "Trying anyways.\n", + ver, PROTOCOL_VERSION); + } + } + if (clc.serverAddress.alternateProtocol == 0) + { + c = Cmd_Argv(2); + if (*c) challenge = atoi(c); + + if (!*c || challenge != clc.challenge) + { + Com_Printf("Bad challenge for challengeResponse. Ignored.\n"); + return; + } + } + + // start sending challenge response instead of challenge request packets + clc.challenge = atoi(Cmd_Argv(1)); + clc.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + if (cl_rsaAuth->integer) + { + s = Cmd_Argv(4); + if (*s) + { + Q_strncpyz(clc.challenge2, s, sizeof(clc.challenge2)); + clc.sendSignature = true; + } + } + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf("challengeResponse: %d\n", clc.challenge); + return; + } + + // server connection + if (!Q_stricmp(c, "connectResponse")) + { + if (clc.state >= CA_CONNECTED) + { + Com_Printf("Dup connect received. Ignored.\n"); + return; + } + if (clc.state != CA_CHALLENGING) + { + Com_Printf("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if (!NET_CompareAdr(from, clc.serverAddress)) + { + Com_Printf("connectResponse from wrong address. Ignored.\n"); + return; + } + + if (clc.serverAddress.alternateProtocol == 0) + { + c = Cmd_Argv(1); + + if (*c) + challenge = atoi(c); + else + { + Com_Printf("Bad connectResponse received. Ignored.\n"); + return; + } + + if (challenge != clc.challenge) + { + Com_Printf("ConnectResponse with bad challenge received. Ignored.\n"); + return; + } + } + + Netchan_Setup(clc.serverAddress.alternateProtocol, NS_CLIENT, &clc.netchan, from, + Cvar_VariableValue("net_qport"), clc.challenge); + + clc.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if (!Q_stricmp(c, "infoResponse")) + { + CL_ServerInfoPacket(from, msg); + return; + } + + // server responding to a get playerlist + if (!Q_stricmp(c, "statusResponse")) + { + CL_ServerStatusResponse(from, msg); + return; + } + + // echo request from server + if (!Q_stricmp(c, "echo")) + { + NET_OutOfBandPrint(NS_CLIENT, from, "%s", Cmd_Argv(1)); + return; + } + + // global MOTD from trem master + if (!Q_stricmp(c, "motd")) + { + CL_MotdPacket(from, s); + return; + } + + // echo request from server + if (!Q_stricmp(c, "print")) + { + s = MSG_ReadString(msg); + + Q_strncpyz(clc.serverMessage, s, sizeof(clc.serverMessage)); + + while (clc.serverMessage[strlen(clc.serverMessage) - 1] == '\n') + clc.serverMessage[strlen(clc.serverMessage) - 1] = '\0'; + + Com_Printf("%s", s); + + return; + } + + // list of servers sent back by a master server (classic) + if (!Q_strncmp(c, "getserversResponse", 18)) + { + CL_ServersResponsePacket(&from, msg, false); + + return; + } + + // list of servers sent back by a master server (extended) + if (!Q_strncmp(c, "getserversExtResponse", 21)) + { + CL_ServersResponsePacket(&from, msg, true); + return; + } + + Com_DPrintf("Unknown connectionless packet command.\n"); +} + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent(netadr_t from, msg_t *msg) +{ + int headerBytes; + + clc.lastPacketTime = cls.realtime; + + if (msg->cursize >= 4 && *(int *)msg->data == -1) + { + CL_ConnectionlessPacket(from, msg); + return; + } + + if (clc.state < CA_CONNECTED) + { + return; // can't be a valid sequenced packet + } + + if (msg->cursize < 4) + { + Com_Printf("%s: Runt packet\n", NET_AdrToStringwPort(from)); + return; + } + + // + // packet from server + // + if (!NET_CompareAdr(from, clc.netchan.remoteAddress)) + { + Com_DPrintf("%s:sequenced packet without connection\n", NET_AdrToStringwPort(from)); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process(&clc.netchan, msg)) + { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong(*(int *)msg->data); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage(msg); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if (clc.demorecording && !clc.demowaiting) + { + CL_WriteDemoMessage(msg, headerBytes); + } +} + +/* +=================== +CL_GetServerStatus +=================== +*/ +static serverStatus_t *CL_GetServerStatus(netadr_t from) +{ + int i, oldest, oldestTime; + + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + if (NET_CompareAdr(from, cl_serverStatusList[i].address)) + { + return &cl_serverStatusList[i]; + } + } + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + if (cl_serverStatusList[i].retrieved) + { + return &cl_serverStatusList[i]; + } + } + oldest = -1; + oldestTime = 0; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) + { + oldest = i; + oldestTime = cl_serverStatusList[i].startTime; + } + } + return &cl_serverStatusList[oldest]; +} + +/* +=================== +CL_ServerStatus +=================== +*/ +bool CL_ServerStatus(char *serverAddress, char *serverStatusString, int maxLen) +{ + int i; + netadr_t to; + serverStatus_t *serverStatus; + + // if no server address then reset all server status requests + if (!serverAddress) + { + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + cl_serverStatusList[i].address.port = 0; + cl_serverStatusList[i].retrieved = true; + } + return false; + } + // get the address + if (!NET_StringToAdr(serverAddress, &to, NA_UNSPEC)) + { + return false; + } + serverStatus = CL_GetServerStatus(to); + // if no server status string then reset the server status request for this address + if (!serverStatusString) + { + serverStatus->retrieved = true; + return false; + } + + // if this server status request has the same address + if (NET_CompareAdr(to, serverStatus->address)) + { + // if we received a response for this server status request + if (!serverStatus->pending) + { + Q_strncpyz(serverStatusString, serverStatus->string, maxLen); + serverStatus->retrieved = true; + serverStatus->startTime = 0; + return true; + } + // resend the request regularly + else if (serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer) + { + serverStatus->print = false; + serverStatus->pending = true; + serverStatus->retrieved = false; + serverStatus->time = 0; + serverStatus->startTime = Com_Milliseconds(); + NET_OutOfBandPrint(NS_CLIENT, to, "getstatus"); + return false; + } + } + // if retrieved + else if (serverStatus->retrieved) + { + serverStatus->address = to; + serverStatus->print = false; + serverStatus->pending = true; + serverStatus->retrieved = false; + serverStatus->startTime = Com_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint(NS_CLIENT, to, "getstatus"); + return false; + } + return false; +} + +/* +================== +CL_LocalServers_f +================== +*/ +static void CL_LocalServers_f(void) +{ + const char *message; + int i, j; + netadr_t to; + + Com_Printf("Scanning for servers on the local network...\n"); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) + { + bool b = cls.localServers[i].visible; + ::memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); + cls.localServers[i].visible = b; + } + ::memset(&to, 0, sizeof(to)); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for (i = 0; i < 2; i++) + { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for (j = 0; j < NUM_SERVER_PORTS; j++) + { + to.port = BigShort((short)(PORT_SERVER + j)); + + to.type = NA_BROADCAST; + NET_SendPacket(NS_CLIENT, strlen(message), message, to); + to.type = NA_MULTICAST6; + NET_SendPacket(NS_CLIENT, strlen(message), message, to); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +static void CL_GlobalServers_f(void) +{ + int netAlternateProtocols, a; + int i; + char command[1024]; + const char *masteraddress; + + int masterNum; + int count = Cmd_Argc(); + if ( count < 2 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS ) + { + Com_Printf("usage: globalservers [keywords]\n", MAX_MASTER_SERVERS); + return; + } + + netAlternateProtocols = Cvar_VariableIntegerValue("net_alternateProtocols"); + + for (a = 0; a < 3; ++a) + { + // indent + if (a == 0 && (netAlternateProtocols & NET_DISABLEPRIMPROTO)) continue; + if (a == 1 && !(netAlternateProtocols & NET_ENABLEALT1PROTO)) continue; + if (a == 2 && !(netAlternateProtocols & NET_ENABLEALT2PROTO)) continue; + + // request from all master servers + if ( masterNum == 0 ) + { + int numAddress = 0; + + for ( int i = 1; i <= MAX_MASTER_SERVERS; i++ ) + { + sprintf(command, "sv_master%d", i); + masteraddress = Cvar_VariableString(command); + + if(!*masteraddress) + continue; + + numAddress++; + + Com_sprintf(command, sizeof(command), "globalservers %d %s %s\n", i, Cmd_Argv(2), Cmd_ArgsFrom(3)); + Cbuf_AddText(command); + } + + if ( !numAddress ) + Com_Printf("CL_GlobalServers_f: Error: No master server addresses.\n"); + + return; + } + + sprintf(command, "sv_%smaster%d", (a == 0 ? "" : a == 1 ? "alt1" : "alt2"), masterNum); + masteraddress = Cvar_VariableString(command); + + if (!*masteraddress) + { + Com_Printf("CL_GlobalServers_f: Error: No%s master server address given.\n", + (a == 0 ? "" : a == 1 ? " alternate-1" : " alternate-2")); + continue; + } + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + netadr_t to; + int i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC); + + if ( i == 0 ) + { + Com_Printf("CL_GlobalServers_f: Error: could not resolve address of%s master %s\n", + (a == 0 ? "" : a == 1 ? " alternate-1" : " alternate-2"), masteraddress); + continue; + } + else if ( i == 2 ) + { + to.port = BigShort(a == 0 ? PORT_MASTER : a == 1 ? ALT1PORT_MASTER : ALT2PORT_MASTER); + } + to.alternateProtocol = a; + + Com_Printf("Requesting servers from%s master %s...\n", + a == 0 ? "" : a == 1 ? " alternate-1" : " alternate-2", + masteraddress); + + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + + Com_sprintf(command, sizeof(command), "getserversExt %s %i%s", + com_gamename->string, + a == 0 ? PROTOCOL_VERSION : a == 1 ? 70 : 69, + Cvar_VariableIntegerValue("net_enabled") & NET_ENABLEV4 ? "" : " ipv6"); + + for (i = 3; i < count; i++) + { + Q_strcat(command, sizeof(command), " "); + Q_strcat(command, sizeof(command), Cmd_Argv(i)); + } + + NET_OutOfBandPrint(NS_SERVER, to, "%s", command); + // outdent + } + CL_RequestMotd(); +} + +/* +================== +CL_GetPing +================== +*/ +void CL_GetPing(int n, char *buf, int buflen, int *pingtime) +{ + const char *str; + int time; + int maxPing; + + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + buf[0] = '\0'; + *pingtime = 0; + return; + } + + str = NET_AdrToStringwPort(cl_pinglist[n].adr); + Q_strncpyz(buf, str, buflen); + + time = cl_pinglist[n].time; + if (!time) + { + // check for timeout + time = Sys_Milliseconds() - cl_pinglist[n].start; + maxPing = Cvar_VariableIntegerValue("cl_maxPing"); + if (maxPing < 100) + { + maxPing = 100; + } + if (time < maxPing) + { + // not timed out yet + time = 0; + } + } + + CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time); + + *pingtime = time; +} + +/* +================== +CL_GetPingInfo +================== +*/ +void CL_GetPingInfo(int n, char *buf, int buflen) +{ + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + if (buflen) buf[0] = '\0'; + return; + } + + Q_strncpyz(buf, cl_pinglist[n].info, buflen); +} + +/* +================== +CL_ClearPing +================== +*/ +void CL_ClearPing(int n) +{ + if (n < 0 || n >= MAX_PINGREQUESTS) return; + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount(void) +{ + int i; + int count; + ping_t *pingptr; + + count = 0; + pingptr = cl_pinglist; + + for (i = 0; i < MAX_PINGREQUESTS; i++, pingptr++) + { + if (pingptr->adr.port) + { + count++; + } + } + + return (count); +} + +/* +================== +CL_GetFreePing +================== +*/ +static ping_t *CL_GetFreePing(void) +{ + ping_t *pingptr; + ping_t *best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for (i = 0; i < MAX_PINGREQUESTS; i++, pingptr++) + { + // find free ping slot + if (pingptr->adr.port) + { + if (!pingptr->time) + { + if (Sys_Milliseconds() - pingptr->start < 500) + { + // still waiting for response + continue; + } + } + else if (pingptr->time < 500) + { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return (pingptr); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for (i = 0; i < MAX_PINGREQUESTS; i++, pingptr++) + { + // scan for oldest + time = Sys_Milliseconds() - pingptr->start; + if (time > oldest) + { + oldest = time; + best = pingptr; + } + } + + return (best); +} + +/* +================== +CL_Ping_f +================== +*/ +static void CL_Ping_f(void) +{ + netadr_t to; + ping_t *pingptr; + const char *server; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if (argc != 2 && argc != 3) + { + Com_Printf("usage: ping [-4|-6] server\n"); + return; + } + + if (argc == 2) + server = Cmd_Argv(1); + else + { + if (!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if (!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf("warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + ::memset(&to, 0, sizeof(netadr_t)); + + if (!NET_StringToAdr(server, &to, family)) + { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy(&pingptr->adr, &to, sizeof(netadr_t)); + pingptr->start = Sys_Milliseconds(); + pingptr->time = 0; + + CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); + + NET_OutOfBandPrint(NS_CLIENT, to, "getinfo xxx"); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +bool CL_UpdateVisiblePings_f(int source) +{ + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + bool status = false; + + if (source < 0 || source > AS_FAVORITES) + { + return false; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if (slots < MAX_PINGREQUESTS) + { + serverInfo_t *server = NULL; + + switch (source) + { + case AS_LOCAL: + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_GLOBAL: + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES: + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + default: + return false; + } + for (i = 0; i < max; i++) + { + if (server[i].visible) + { + if (server[i].ping == -1) + { + int j; + + if (slots >= MAX_PINGREQUESTS) + { + break; + } + for (j = 0; j < MAX_PINGREQUESTS; j++) + { + if (!cl_pinglist[j].adr.port) + { + continue; + } + if (NET_CompareAdr(cl_pinglist[j].adr, server[i].adr)) + { + // already on the list + break; + } + } + if (j >= MAX_PINGREQUESTS) + { + status = true; + for (j = 0; j < MAX_PINGREQUESTS; j++) + { + if (!cl_pinglist[j].adr.port) + { + break; + } + } + memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); + cl_pinglist[j].start = Sys_Milliseconds(); + cl_pinglist[j].time = 0; + NET_OutOfBandPrint(NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx"); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if (server[i].ping == 0) + { + // if we are updating global servers + if (source == AS_GLOBAL) + { + // + if (cls.numGlobalServerAddresses > 0) + { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if (slots) + { + status = true; + } + for (i = 0; i < MAX_PINGREQUESTS; i++) + { + if (!cl_pinglist[i].adr.port) + { + continue; + } + CL_GetPing(i, buff, MAX_STRING_CHARS, &pingTime); + if (pingTime != 0) + { + CL_ClearPing(i); + status = true; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +static void CL_ServerStatus_f(void) +{ + netadr_t to, *toptr = NULL; + const char *server; + serverStatus_t *serverStatus; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if (argc != 2 && argc != 3) + { + if (clc.state != CA_ACTIVE || clc.demoplaying) + { + Com_Printf("Not connected to a server.\n"); + Com_Printf("usage: serverstatus [-4|-6] server\n"); + return; + } + + toptr = &clc.serverAddress; + } + + if (!toptr) + { + ::memset(&to, 0, sizeof(netadr_t)); + + if (argc == 2) + server = Cmd_Argv(1); + else + { + if (!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if (!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf("warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + toptr = &to; + if (!NET_StringToAdr(server, toptr, family)) return; + } + + NET_OutOfBandPrint(NS_CLIENT, *toptr, "getstatus"); + + serverStatus = CL_GetServerStatus(*toptr); + serverStatus->address = *toptr; + serverStatus->print = true; + serverStatus->pending = true; +} + +/* +============ +CL_InitRef +============ +*/ +static void CL_InitRef(void) +{ + refimport_t ri; + refexport_t *ret; +#ifdef USE_RENDERER_DLOPEN + GetRefAPI_t GetRefAPI; + char dllName[MAX_OSPATH]; +#endif + + Com_Printf("----- Initializing Renderer ----\n"); + +#ifdef USE_RENDERER_DLOPEN + cl_renderer = Cvar_Get("cl_renderer", "opengl2", CVAR_ARCHIVE | CVAR_LATCH); + + Com_sprintf(dllName, sizeof(dllName), "renderer_%s" DLL_EXT, cl_renderer->string); + + if (!(rendererLib = Sys_LoadDll(dllName, false)) && strcmp(cl_renderer->string, cl_renderer->resetString)) + { + Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError()); + Cvar_ForceReset("cl_renderer"); + + Com_sprintf(dllName, sizeof(dllName), "renderer_opengl1" DLL_EXT); + rendererLib = Sys_LoadDll(dllName, false); + } + + if (!rendererLib) + { + Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError()); + Com_Error(ERR_FATAL, "Failed to load renderer"); + } + + GetRefAPI = (GetRefAPI_t)Sys_LoadFunction(rendererLib, "GetRefAPI"); + if (!GetRefAPI) + { + Com_Error(ERR_FATAL, "Can't load symbol GetRefAPI: '%s'", Sys_LibraryError()); + } +#endif + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; + ri.Malloc = CL_RefMalloc; + ri.Free = Z_Free; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + + ri.CM_ClusterPVS = CM_ClusterPVS; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + ri.Cvar_SetValue = Cvar_SetValue; + ri.Cvar_CheckRange = Cvar_CheckRange; + ri.Cvar_SetDescription = Cvar_SetDescription; + ri.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame; + + ri.IN_Init = IN_Init; + ri.IN_Shutdown = IN_Shutdown; + ri.IN_Restart = IN_Restart; + + ri.Sys_GLimpSafeInit = Sys_GLimpSafeInit; + ri.Sys_GLimpInit = Sys_GLimpInit; + ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory; + + ret = GetRefAPI(REF_API_VERSION, &ri); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom(ret); +#endif + + Com_Printf("-------------------------------\n"); + + if (!ret) + { + Com_Error(ERR_FATAL, "Couldn't initialize refresh"); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set("cl_paused", "0"); +} + +/* +==================== +CL_ProtocolSpecificCommandsInit + +For adding/remove commands that depend on a/some +specific protocols, whenever the protcol may change +==================== +*/ +void CL_ProtocolSpecificCommandsInit(void) +{ + Con_MessageModesInit(); +} + +/* +==================== +CL_Init +==================== +*/ +void CL_Init(void) +{ + Com_Printf("----- Client Initialization -----\n"); + + Con_Init(); + + if (!com_fullyInitialized) + { + CL_ClearState(); + clc.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + cl_oldGameSet = false; + } + + CL_InitInput(); + + // + // register our variables + // + cl_noprint = Cvar_Get("cl_noprint", "0", 0); + cl_motd = Cvar_Get("cl_motd", "1", 0); + + cl_timeout = Cvar_Get("cl_timeout", "200", 0); + + cl_timeNudge = Cvar_Get("cl_timeNudge", "0", CVAR_TEMP); + cl_shownet = Cvar_Get("cl_shownet", "0", CVAR_TEMP); + cl_showSend = Cvar_Get("cl_showSend", "0", CVAR_TEMP); + cl_showTimeDelta = Cvar_Get("cl_showTimeDelta", "0", CVAR_TEMP); + cl_freezeDemo = Cvar_Get("cl_freezeDemo", "0", CVAR_TEMP); + rcon_client_password = Cvar_Get("rconPassword", "", CVAR_TEMP); + cl_activeAction = Cvar_Get("activeAction", "", CVAR_TEMP); + + cl_timedemo = Cvar_Get("timedemo", "0", 0); + cl_timedemoLog = Cvar_Get("cl_timedemoLog", "", CVAR_ARCHIVE); + cl_autoRecordDemo = Cvar_Get("cl_autoRecordDemo", "0", CVAR_ARCHIVE); + cl_aviFrameRate = Cvar_Get("cl_aviFrameRate", "25", CVAR_ARCHIVE); + cl_aviMotionJpeg = Cvar_Get("cl_aviMotionJpeg", "1", CVAR_ARCHIVE); + cl_forceavidemo = Cvar_Get("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get("cl_anglespeedkey", "1.5", 0); + + cl_maxpackets = Cvar_Get("cl_maxpackets", "30", CVAR_ARCHIVE); + cl_packetdup = Cvar_Get("cl_packetdup", "1", CVAR_ARCHIVE); + + cl_run = Cvar_Get("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get("sensitivity", "5", CVAR_ARCHIVE); + cl_mouseAccel = Cvar_Get("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get("cl_freelook", "1", CVAR_ARCHIVE); + + // 0: legacy mouse acceleration + // 1: new implementation + cl_mouseAccelStyle = Cvar_Get("cl_mouseAccelStyle", "0", CVAR_ARCHIVE); + // offset for the power function (for style 1, ignored otherwise) + // this should be set to the max rate value + cl_mouseAccelOffset = Cvar_Get("cl_mouseAccelOffset", "5", CVAR_ARCHIVE); + Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, false); + + cl_showMouseRate = Cvar_Get("cl_showmouserate", "0", 0); + + cl_allowDownload = Cvar_Get("cl_allowDownload", "1", CVAR_ARCHIVE); + + if (cl_allowDownload->integer != -1) cl_allowDownload->integer = DLF_ENABLE; + + com_downloadPrompt = Cvar_Get("com_downloadPrompt", "0", CVAR_ROM); + Cvar_Get("com_downloadPromptText", "", CVAR_TEMP); + + cl_conXOffset = Cvar_Get("cl_conXOffset", "0", 0); +#ifdef __APPLE__ + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get("cl_serverStatusResendTime", "750", 0); + + m_pitch = Cvar_Get("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get("m_side", "0.25", CVAR_ARCHIVE); +#ifdef __APPLE__ + // Input is jittery on OS X w/o this + m_filter = Cvar_Get("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get("m_filter", "0", CVAR_ARCHIVE); +#endif + + j_pitch = Cvar_Get("j_pitch", "0.022", CVAR_ARCHIVE); + j_yaw = Cvar_Get("j_yaw", "-0.022", CVAR_ARCHIVE); + j_forward = Cvar_Get("j_forward", "-0.25", CVAR_ARCHIVE); + j_side = Cvar_Get("j_side", "0.25", CVAR_ARCHIVE); + j_up = Cvar_Get("j_up", "0", CVAR_ARCHIVE); + + j_pitch_axis = Cvar_Get("j_pitch_axis", "3", CVAR_ARCHIVE); + j_yaw_axis = Cvar_Get("j_yaw_axis", "2", CVAR_ARCHIVE); + j_forward_axis = Cvar_Get("j_forward_axis", "1", CVAR_ARCHIVE); + j_side_axis = Cvar_Get("j_side_axis", "0", CVAR_ARCHIVE); + j_up_axis = Cvar_Get("j_up_axis", "4", CVAR_ARCHIVE); + + Cvar_CheckRange(j_pitch_axis, 0, MAX_JOYSTICK_AXIS - 1, true); + Cvar_CheckRange(j_yaw_axis, 0, MAX_JOYSTICK_AXIS - 1, true); + Cvar_CheckRange(j_forward_axis, 0, MAX_JOYSTICK_AXIS - 1, true); + Cvar_CheckRange(j_side_axis, 0, MAX_JOYSTICK_AXIS - 1, true); + Cvar_CheckRange(j_up_axis, 0, MAX_JOYSTICK_AXIS - 1, true); + + cl_motdString = Cvar_Get("cl_motdString", "", CVAR_ROM); + + Cvar_Get("cl_maxPing", "800", CVAR_ARCHIVE); + + cl_lanForcePackets = Cvar_Get("cl_lanForcePackets", "1", CVAR_ARCHIVE); + + cl_guidServerUniq = Cvar_Get("cl_guidServerUniq", "1", CVAR_ARCHIVE); + + cl_rsaAuth = Cvar_Get("cl_rsaAuth", "0", CVAR_INIT | CVAR_PROTECTED); + + // ~ and `, as keys and characters + cl_consoleKeys = Cvar_Get("cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE); + + cl_clantag = Cvar_Get ("cl_clantag", "", CVAR_ARCHIVE); + + // userinfo + Cvar_Get("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE); + cl_rate = Cvar_Get("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE); + Cvar_Get("snaps", "40", CVAR_USERINFO | CVAR_ARCHIVE); + Cvar_Get("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE); + Cvar_Get("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE); + Cvar_Get("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE); + Cvar_Get("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE); + + Cvar_Get("password", "", CVAR_USERINFO); + +#ifdef USE_MUMBLE + cl_useMumble = Cvar_Get("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH); + cl_mumbleScale = Cvar_Get("cl_mumbleScale", "0.0254", CVAR_ARCHIVE); +#endif + +#ifdef USE_VOIP + cl_voipSend = Cvar_Get("cl_voipSend", "0", 0); + cl_voipSendTarget = Cvar_Get("cl_voipSendTarget", "spatial", 0); + cl_voipGainDuringCapture = Cvar_Get("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE); + cl_voipCaptureMult = Cvar_Get("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE); + cl_voipUseVAD = Cvar_Get("cl_voipUseVAD", "0", CVAR_ARCHIVE); + cl_voipVADThreshold = Cvar_Get("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE); + cl_voipShowMeter = Cvar_Get("cl_voipShowMeter", "1", CVAR_ARCHIVE); + + cl_voip = Cvar_Get("cl_voip", "1", CVAR_ARCHIVE); + Cvar_CheckRange(cl_voip, 0, 1, true); + cl_voipProtocol = Cvar_Get("cl_voipProtocol", cl_voip->integer ? "opus" : "", CVAR_USERINFO | CVAR_ROM); +#endif + + // cgame might not be initialized before menu is used + Cvar_Get("cg_viewsize", "100", CVAR_ARCHIVE); + // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore. + Cvar_Get("cg_stereoSeparation", "0", CVAR_ROM); + + // + // register our commands + // + Cmd_AddCommand("cmd", CL_ForwardToServer_f); + Cmd_AddCommand("configstrings", CL_Configstrings_f); + Cmd_AddCommand("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand("disconnect", CL_Disconnect_f); + Cmd_AddCommand("record", CL_Record_f); + Cmd_AddCommand("demo", CL_PlayDemo_f); + Cmd_SetCommandCompletionFunc("demo", CL_CompleteDemoName); + Cmd_AddCommand("cinematic", CL_PlayCinematic_f); + Cmd_AddCommand("stoprecord", CL_StopRecord_f); + Cmd_AddCommand("connect", CL_Connect_f); + Cmd_AddCommand("reconnect", CL_Reconnect_f); + Cmd_AddCommand("localservers", CL_LocalServers_f); + Cmd_AddCommand("globalservers", CL_GlobalServers_f); + Cmd_AddCommand("rcon", CL_Rcon_f); + Cmd_SetCommandCompletionFunc("rcon", CL_CompleteRcon); + Cmd_AddCommand("ping", CL_Ping_f); + Cmd_AddCommand("serverstatus", CL_ServerStatus_f); + Cmd_AddCommand("showip", CL_ShowIP_f); + Cmd_AddCommand("fs_openedList", CL_OpenedPK3List_f); + Cmd_AddCommand("fs_referencedList", CL_ReferencedPK3List_f); + Cmd_AddCommand("model", CL_SetModel_f); + Cmd_AddCommand("video", CL_Video_f); + Cmd_AddCommand("stopvideo", CL_StopVideo_f); + Cmd_AddCommand("downloadUpdate", CL_DownloadUpdate_f); + Cmd_AddCommand("installUpdate", CL_InstallUpdate_f); + Cmd_AddCommand("checkForUpdate", CL_CheckForUpdate_f); + Cmd_AddCommand("browseHomepath", CL_BrowseHomepath_f); + Cmd_AddCommand("browseDemos", CL_BrowseDemos_f); + Cmd_AddCommand("browseScreenShots", CL_BrowseScreenShots_f); + + CL_InitRef(); + + SCR_Init(); + + // Cbuf_Execute (); + + Cvar_Set("cl_running", "1"); + + if (cl_rsaAuth->integer) CL_LoadRSAKeypair(); + + CL_GenerateQKey(); + Cvar_Get("cl_guid", "", CVAR_USERINFO | CVAR_ROM); + if (clc.state == CA_DISCONNECTED) CL_UpdateGUID(NULL, 0); + + Com_Printf("----- Client Initialization Complete -----\n"); +} + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown(const char *finalmsg, bool disconnect, bool quit) +{ + static bool recursive = false; + int realtime; + + // check whether the client is running at all. + if (!(com_cl_running && com_cl_running->integer)) return; + + Com_Printf("----- Client Shutdown (%s) -----\n", finalmsg); + + if (recursive) + { + Com_Printf("WARNING: Recursive shutdown\n"); + return; + } + recursive = true; + + noGameRestart = quit; + + if (disconnect) CL_Disconnect(true); + + CL_ClearMemory(true); + CL_Snd_Shutdown(); + + Cmd_RemoveCommand("cmd"); + Cmd_RemoveCommand("configstrings"); + Cmd_RemoveCommand("clientinfo"); + Cmd_RemoveCommand("snd_restart"); + Cmd_RemoveCommand("vid_restart"); + Cmd_RemoveCommand("disconnect"); + Cmd_RemoveCommand("record"); + Cmd_RemoveCommand("demo"); + Cmd_RemoveCommand("cinematic"); + Cmd_RemoveCommand("stoprecord"); + Cmd_RemoveCommand("connect"); + Cmd_RemoveCommand("reconnect"); + Cmd_RemoveCommand("localservers"); + Cmd_RemoveCommand("globalservers"); + Cmd_RemoveCommand("rcon"); + Cmd_RemoveCommand("ping"); + Cmd_RemoveCommand("serverstatus"); + Cmd_RemoveCommand("showip"); + Cmd_RemoveCommand("fs_openedList"); + Cmd_RemoveCommand("fs_referencedList"); + Cmd_RemoveCommand("model"); + Cmd_RemoveCommand("video"); + Cmd_RemoveCommand("stopvideo"); + + CL_ShutdownInput(); + Con_Shutdown(); + + Cvar_Set("cl_running", "0"); + + recursive = false; + + if (cl_rsaAuth->integer) CL_UnloadRSAKeypair(); + + realtime = cls.realtime; + ::memset(&cls, 0, sizeof(cls)); + cls.realtime = realtime; + Key_SetCatcher(0); + + Com_Printf("-----------------------\n"); +} diff --git a/src/client/cl_net_chan.cpp b/src/client/cl_net_chan.cpp new file mode 100644 index 0000000..80ac25d --- /dev/null +++ b/src/client/cl_net_chan.cpp @@ -0,0 +1,190 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +/* +============== +CL_Netchan_Encode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Encode(msg_t *msg) +{ + int serverId, messageAcknowledge, reliableAcknowledge; + int i, idx, srdc, sbit; + bool soob; + byte key, *string; + + if (msg->cursize <= CL_ENCODE_START) + { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = false; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc.serverCommands[reliableAcknowledge & (MAX_RELIABLE_COMMANDS - 1)]; + idx = 0; + // + key = clc.challenge ^ serverId ^ messageAcknowledge; + for (i = CL_ENCODE_START; i < msg->cursize; i++) + { + // modify the key with the last received now acknowledged server command + if (!string[idx]) idx = 0; + if (string[idx] > 127 || (string[idx] == '%' && clc.netchan.alternateProtocol == 2)) + { + key ^= '.' << (i & 1); + } + else + { + key ^= string[idx] << (i & 1); + } + idx++; + // encode the data with this key + *(msg->data + i) = (*(msg->data + i)) ^ key; + } +} + +/* +============== +CL_Netchan_Decode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Decode(msg_t *msg) +{ + long reliableAcknowledge, i, idx; + byte key, *string; + int srdc, sbit; + bool soob; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = false; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc.reliableCommands[reliableAcknowledge & (MAX_RELIABLE_COMMANDS - 1)]; + idx = 0; + // xor the client challenge with the netchan sequence number (need something that changes every message) + key = clc.challenge ^ LittleLong(*(unsigned *)msg->data); + for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) + { + // modify the key with the last sent and with this message acknowledged client command + if (!string[idx]) idx = 0; + if (string[idx] > 127 || (string[idx] == '%' && clc.netchan.alternateProtocol == 2)) + { + key ^= '.' << (i & 1); + } + else + { + key ^= string[idx] << (i & 1); + } + idx++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +================= +CL_Netchan_TransmitNextFragment +================= +*/ +static bool CL_Netchan_TransmitNextFragment(netchan_t *chan) +{ + if (chan->unsentFragments) + { + Netchan_TransmitNextFragment(chan); + return true; + } + + return false; +} + +/* +=============== +CL_Netchan_Transmit +================ +*/ +void CL_Netchan_Transmit(netchan_t *chan, msg_t *msg) +{ + MSG_WriteByte(msg, clc_EOF); + + if (chan->alternateProtocol != 0) CL_Netchan_Encode(msg); + Netchan_Transmit(chan, msg->cursize, msg->data); + + // Transmit all fragments without delay + while (CL_Netchan_TransmitNextFragment(chan)) + { + Com_DPrintf("WARNING: #462 unsent fragments (not supposed to happen!)\n"); + } +} + +/* +================= +CL_Netchan_Process +================= +*/ +bool CL_Netchan_Process(netchan_t *chan, msg_t *msg) +{ + int ret; + + ret = Netchan_Process(chan, msg); + if (!ret) return false; + if (chan->alternateProtocol != 0) CL_Netchan_Decode(msg); + + return true; +} diff --git a/src/client/cl_parse.cpp b/src/client/cl_parse.cpp new file mode 100644 index 0000000..111211a --- /dev/null +++ b/src/client/cl_parse.cpp @@ -0,0 +1,961 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// cl_parse.c -- parse a message received from the server + +#include "client.h" + +const char *svc_strings[256] = { + "svc_bad", + "svc_nop", + "svc_gamestate", + "svc_configstring", + "svc_baseline", + "svc_serverCommand", + "svc_download", + "svc_snapshot", + "svc_EOF", + "svc_voipSpeex", + "svc_voipOpus", +}; + +void SHOWNET( msg_t *msg, const char *s) { + if ( cl_shownet->integer >= 2) { + Com_Printf ("%3i:%s\n", msg->readcount-1, s); + } +} + + +/* +========================================================================= + +MESSAGE PARSING + +========================================================================= +*/ + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +static void CL_DeltaEntity(msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, bool unchanged) +{ + entityState_t *state; + + // save the parsed entity state into the big circular buffer so it can be + // used as the source for a later delta + state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; + + if ( unchanged ) + { + *state = *old; + } + else + { + MSG_ReadDeltaEntity( clc.netchan.alternateProtocol, msg, old, state, newnum ); + } + + if ( state->number == (MAX_GENTITIES-1) ) + { + return; // entity was delta removed + } + + cl.parseEntitiesNum++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +================== +*/ +static void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) +{ + int newnum; + entityState_t *oldstate; + int oldindex, oldnum; + + newframe->parseEntitiesNum = cl.parseEntitiesNum; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + oldstate = NULL; + if (!oldframe) { + oldnum = 99999; + } else { + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + while ( 1 ) { + // read the entity index number + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + + if ( newnum == (MAX_GENTITIES-1) ) { + break; + } + + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); + } + + while ( oldnum < newnum ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, true ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + if (oldnum == newnum) { + // delta from previous state + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, oldstate, false ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if ( oldnum > newnum ) { + // delta from baseline + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], false ); + 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->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, true ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } +} + + +/* +================ +CL_ParseSnapshot + +If the snapshot is parsed properly, it will be copied to +cl.snap and saved in cl.snapshots[]. If the snapshot is invalid +for any reason, no changes to the state will be made at all. +================ +*/ +void CL_ParseSnapshot( msg_t *msg ) { + int len; + clSnapshot_t *old; + clSnapshot_t newSnap; + int deltaNum; + int oldMessageNum; + int i, packetNum; + + // get the reliable sequence acknowledge number + // NOTE: now sent with all server to client messages + //clc.reliableAcknowledge = MSG_ReadLong( msg ); + + // read in the new snapshot to a temporary buffer + // we will only copy to cl.snap if it is valid + ::memset(&newSnap, 0, sizeof(newSnap)); + + // we will have read any new server commands in this + // message before we got to svc_snapshot + newSnap.serverCommandNum = clc.serverCommandSequence; + + newSnap.serverTime = MSG_ReadLong( msg ); + + // if we were just unpaused, we can only *now* really let the + // change come into effect or the client hangs. + cl_paused->modified = false; + + newSnap.messageNum = clc.serverMessageSequence; + + deltaNum = MSG_ReadByte( msg ); + if ( !deltaNum ) { + newSnap.deltaNum = -1; + } else { + newSnap.deltaNum = newSnap.messageNum - deltaNum; + } + newSnap.snapFlags = MSG_ReadByte( msg ); + + // 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 ( newSnap.deltaNum <= 0 ) { + newSnap.valid = true; // uncompressed frame + old = NULL; + clc.demowaiting = false; // we can start recording now + } else { + old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; + if ( !old->valid ) { + // should never happen + Com_DPrintf ("Delta from invalid frame (not supposed to happen!).\n"); + } else if ( old->messageNum != newSnap.deltaNum ) { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_DPrintf ("Delta frame too old.\n"); + } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - MAX_SNAPSHOT_ENTITIES ) { + Com_DPrintf ("Delta parseEntitiesNum too old.\n"); + } else { + newSnap.valid = true; // valid delta parse + } + } + + // read areamask + len = MSG_ReadByte( msg ); + + if(len > sizeof(newSnap.areamask)) + { + Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask", len); + return; + } + + MSG_ReadData( msg, &newSnap.areamask, len); + + // read playerinfo + SHOWNET( msg, "playerstate" ); + if ( clc.netchan.alternateProtocol == 2 ) { + if ( old ) { + MSG_ReadDeltaAlternatePlayerstate( msg, &old->alternatePs, &newSnap.alternatePs ); + } else { + MSG_ReadDeltaAlternatePlayerstate( msg, NULL, &newSnap.alternatePs ); + } + } else { + if ( old ) { + MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); + } else { + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); + } + } + + // read packet entities + SHOWNET( msg, "packet entities" ); + CL_ParsePacketEntities( msg, old, &newSnap ); + + // if not valid, dump the entire thing now that it has + // been properly read + if ( !newSnap.valid ) { + return; + } + + // clear the valid flags of any snapshots between the last + // received and this one, so if there was a dropped packet + // it won't look like something valid to delta from next + // time we wrap around in the buffer + oldMessageNum = cl.snap.messageNum + 1; + + if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { + oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); + } + for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { + cl.snapshots[oldMessageNum & PACKET_MASK].valid = false; + } + + // copy to the current good spot + cl.snap = newSnap; + cl.snap.ping = 999; + // calculate ping time + for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { + packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; + if ( ( clc.netchan.alternateProtocol == 2 ? cl.snap.alternatePs.commandTime : cl.snap.ps.commandTime ) >= cl.outPackets[ packetNum ].p_serverTime ) { + cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; + break; + } + } + // save the frame off in the backup array for later delta comparisons + cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; + + if (cl_shownet->integer == 3) { + Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, + cl.snap.deltaNum, cl.snap.ping ); + } + + cl.newSnapshots = true; +} + + +//===================================================================== + +bool cl_connectedToPureServer; +bool cl_connectedToCheatServer; + +/* +================== +CL_SystemInfoChanged + +The systeminfo configstring has been changed, so parse +new information out of it. This will happen at every +gamestate, and possibly during gameplay. +================== +*/ +void CL_SystemInfoChanged( void ) { + char *systemInfo; + const char *s, *t; + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + bool gameSet; + + systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; + // NOTE TTimo: + // when the serverId changes, any further messages we send to the server will use this new serverId + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // in some cases, outdated cp commands might get sent with this news serverId + cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); + +#ifdef USE_VOIP + s = Info_ValueForKey( systemInfo, "sv_voipProtocol" ); + clc.voipEnabled = !Q_stricmp(s, "opus"); +#endif + + // only set the paks, fs_game, and sv_pure when playing a demo + if ( !clc.demoplaying ) { + s = Info_ValueForKey( systemInfo, "sv_cheats" ); + cl_connectedToCheatServer = (bool)atoi( s ); + if ( !cl_connectedToCheatServer ) { + Cvar_SetCheatState(); + } + } + + // check pure server string + s = Info_ValueForKey( systemInfo, "sv_paks" ); + t = Info_ValueForKey( systemInfo, "sv_pakNames" ); + FS_PureServerSetLoadedPaks( s, t ); + + s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); + t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); + FS_PureServerSetReferencedPaks( s, t ); + + gameSet = false; + // scan through all the variables in the systeminfo and locally set cvars to match + s = systemInfo; + while ( s ) { + int cvar_flags; + + Info_NextPair( &s, key, value ); + if ( !key[0] ) { + break; + } + + // ehw! + if (!Q_stricmp(key, "fs_game")) + { + if(FS_CheckDirTraversal(value)) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value); + continue; + } + + gameSet = true; + } else if ( clc.demoplaying && Q_stricmp(key, "sv_pure" ) ) { + continue; + } + + if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT) + Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM); + else + { + // If this cvar may not be modified by a server discard the value. + if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED))) + { + if(Q_stricmp(key, "g_synchronousClients") && Q_stricmp(key, "pmove_fixed") && + Q_stricmp(key, "pmove_msec")) + { + Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value); + continue; + } + } + + Cvar_SetSafe(key, value); + } + } + // if game folder should not be set and it is set at the client side + if ( !gameSet && *Cvar_VariableString("fs_game") ) { + Cvar_Set( "fs_game", "" ); + } + cl_connectedToPureServer = (bool)Cvar_VariableValue( "sv_pure" ); +} + +/* +================== +CL_ParseServerInfo +================== +*/ +static void CL_ParseServerInfo(void) +{ + const char *serverInfo; + + serverInfo = cl.gameState.stringData + + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + + clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo, + "sv_allowDownload")); + Q_strncpyz(clc.sv_dlURL, + Info_ValueForKey(serverInfo, "sv_dlURL"), + sizeof(clc.sv_dlURL)); +} + +/* +================== +CL_ParseGamestate +================== +*/ +void CL_ParseGamestate( msg_t *msg ) { + int i; + entityState_t *es; + int newnum; + entityState_t nullstate; + int cmd; + char *s; + char oldGame[MAX_QPATH]; + + clc.connectPacketCount = 0; + + // wipe local client state + CL_ClearState(); + + // a gamestate always marks a server command sequence + clc.serverCommandSequence = MSG_ReadLong( msg ); + + // parse all the configstrings and baselines + cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings + while ( 1 ) { + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + break; + } + + if ( cmd == svc_configstring ) { + int len; + + i = MSG_ReadShort( msg ); + if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + s = MSG_ReadBigString( msg ); + len = strlen( s ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + ::memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); + cl.gameState.dataCount += len + 1; + } else if ( cmd == svc_baseline ) { + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + if ( newnum < 0 || newnum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); + } + ::memset(&nullstate, 0, sizeof(nullstate)); + es = &cl.entityBaselines[ newnum ]; + MSG_ReadDeltaEntity( clc.netchan.alternateProtocol, msg, &nullstate, es, newnum ); + } else { + Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); + } + } + + clc.clientNum = MSG_ReadLong(msg); + // read the checksum feed + clc.checksumFeed = MSG_ReadLong( msg ); + + // save old gamedir + Cvar_VariableStringBuffer("fs_game", oldGame, sizeof(oldGame)); + + // parse useful values out of CS_SERVERINFO + CL_ParseServerInfo(); + + // parse serverId and other cvars + CL_SystemInfoChanged(); + + // stop recording now so the demo won't have an unnecessary level load at the end. + if(cl_autoRecordDemo->integer && clc.demorecording) + CL_StopRecord_f(); + + // reinitialize the filesystem if the game directory has changed + if(!cl_oldGameSet && (Cvar_Flags("fs_game") & CVAR_MODIFIED)) + { + cl_oldGameSet = true; + Q_strncpyz(cl_oldGame, oldGame, sizeof(cl_oldGame)); + } + + FS_ConditionalRestart(clc.checksumFeed, false); + + // This used to call CL_StartHunkUsers, but now we enter the download state before loading the + // cgame + CL_InitDownloads(); + + // make sure the game starts + Cvar_Set( "cl_paused", "0" ); +} + + +//===================================================================== + +/* +===================== +CL_ParseDownload + +A download message has been received from the server +===================== +*/ +void CL_ParseDownload ( msg_t *msg ) { + int size; + unsigned char data[MAX_MSGLEN]; + uint16_t block; + + if (!*clc.downloadTempName) { + Com_Printf("Server sending download, but no download was requested\n"); + CL_AddReliableCommand("stopdl", false); + return; + } + + // read the data + block = MSG_ReadShort ( msg ); + + if(!block && !clc.downloadBlock) + { + // block zero is special, contains file size + clc.downloadSize = MSG_ReadLong ( msg ); + + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + + if (clc.downloadSize < 0) + { + Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) ); + return; + } + } + + size = MSG_ReadShort ( msg ); + if (size < 0 || size > sizeof(data)) + { + Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk", size); + return; + } + + MSG_ReadData(msg, data, size); + + if((clc.downloadBlock & 0xFFFF) != block) + { + Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", (clc.downloadBlock & 0xFFFF), block); + return; + } + + // open the file if not opened yet + if (!clc.download) + { + clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); + + if (!clc.download) { + Com_Printf( "Could not create %s\n", clc.downloadTempName ); + CL_AddReliableCommand("stopdl", false); + CL_NextDownload(); + return; + } + } + + if (size) + FS_Write( data, size, clc.download ); + + CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), false); + clc.downloadBlock++; + + clc.downloadCount += size; + + // So UI gets access to it + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + + if (!size) { // A zero length block means EOF + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + + // rename the file + FS_SV_Rename( clc.downloadTempName, clc.downloadName, false ); + } + + // send intentions now + // We need this because without it, we would hold the last nextdl and then start + // loading right away. If we take a while to load, the server is happily trying + // to send us that last block over and over. + // Write it twice to help make sure we acknowledge the download + CL_WritePacket(); + CL_WritePacket(); + + // get another file if needed + CL_NextDownload (); + } +} + +#ifdef USE_VOIP +static bool CL_ShouldIgnoreVoipSender(int sender) +{ + if (!cl_voip->integer) + return true; // VoIP is disabled. + else if ((sender == clc.clientNum) && (!clc.demoplaying)) + return true; // ignore own voice (unless playing back a demo). + else if (clc.voipMuteAll) + return true; // all channels are muted with extreme prejudice. + else if (clc.voipIgnore[sender]) + return true; // just ignoring this guy. + else if (clc.voipGain[sender] == 0.0f) + return true; // too quiet to play. + + return false; +} + +/* +===================== +CL_PlayVoip + +Play raw data +===================== +*/ + +static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags) +{ + if(flags & VOIP_DIRECT) + { + S_RawSamples(sender + 1, samplecnt, 48000, 2, 1, data, clc.voipGain[sender], -1); + } + + if(flags & VOIP_SPATIAL) + { + S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, 48000, 2, 1, data, 1.0f, sender); + } +} + +/* +===================== +CL_ParseVoip + +A VoIP message has been received from the server +===================== +*/ +static void CL_ParseVoip( msg_t *msg, bool ignoreData ) +{ + static short decoded[VOIP_MAX_PACKET_SAMPLES*4]; // !!! FIXME: don't hard code + + const int sender = MSG_ReadShort(msg); + const int generation = MSG_ReadByte(msg); + const int sequence = MSG_ReadLong(msg); + const int frames = MSG_ReadByte(msg); + const int packetsize = MSG_ReadShort(msg); + int flags = VOIP_DIRECT; + if (clc.netchan.alternateProtocol == 0) + flags = MSG_ReadBits(msg, VOIP_FLAGCNT); + char encoded[4000]; + int numSamples; + int seqdiff; + int written = 0; + int i; + + Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender); + + if (sender < 0) + return; // short/invalid packet, bail. + else if (generation < 0) + return; // short/invalid packet, bail. + else if (sequence < 0) + return; // short/invalid packet, bail. + else if (frames < 0) + return; // short/invalid packet, bail. + else if (packetsize < 0) + return; // short/invalid packet, bail. + + if (packetsize > sizeof (encoded)) { // overlarge packet? + int bytesleft = packetsize; + while (bytesleft) { + int br = bytesleft; + if (br > sizeof (encoded)) + br = sizeof (encoded); + MSG_ReadData(msg, encoded, br); + bytesleft -= br; + } + return; // overlarge packet, bail. + } + + MSG_ReadData(msg, encoded, packetsize); + + if (ignoreData) { + return; // just ignore legacy speex voip data + } else if (!clc.voipCodecInitialized) { + return; // can't handle VoIP without libopus! + } else if (sender >= MAX_CLIENTS) { + return; // bogus sender. + } else if (CL_ShouldIgnoreVoipSender(sender)) { + return; // Channel is muted, bail. + } + + // !!! FIXME: make sure data is narrowband? Does decoder handle this? + + Com_DPrintf("VoIP: packet accepted!\n"); + + seqdiff = sequence - clc.voipIncomingSequence[sender]; + + // This is a new "generation" ... a new recording started, reset the bits. + if (generation != clc.voipIncomingGeneration[sender]) { + Com_DPrintf("VoIP: new generation %d!\n", generation); + opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE); + clc.voipIncomingGeneration[sender] = generation; + seqdiff = 0; + } else if (seqdiff < 0) { // we're ahead of the sequence?! + // This shouldn't happen unless the packet is corrupted or something. + Com_DPrintf("VoIP: misordered sequence! %d < %d!\n", + sequence, clc.voipIncomingSequence[sender]); + // reset the decoder just in case. + opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE); + seqdiff = 0; + } else if (seqdiff * VOIP_MAX_PACKET_SAMPLES*2 >= sizeof (decoded)) { // dropped more than we can handle? + // just start over. + Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n", + seqdiff, sender); + opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE); + seqdiff = 0; + } + + if (seqdiff != 0) { + Com_DPrintf("VoIP: Dropped %d frames from client #%d\n", + seqdiff, sender); + // tell opus that we're missing frames... + for (i = 0; i < seqdiff; i++) { + assert((written + VOIP_MAX_PACKET_SAMPLES) * 2 < sizeof (decoded)); + numSamples = opus_decode(clc.opusDecoder[sender], NULL, 0, decoded + written, VOIP_MAX_PACKET_SAMPLES, 0); + if ( numSamples <= 0 ) { + Com_DPrintf("VoIP: Error decoding frame %d from client #%d\n", i, sender); + continue; + } + written += numSamples; + } + } + + numSamples = opus_decode(clc.opusDecoder[sender], + (const unsigned char*)encoded, + packetsize, + decoded + written, + ARRAY_LEN(decoded) - written, + 0); + + if ( numSamples <= 0 ) { + Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender); + numSamples = 0; + } + + #if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb"); + if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb"); + if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); } + #endif + + written += numSamples; + + Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", + written * 2, written, frames); + + if(written > 0) + CL_PlayVoip(sender, written, (const byte *) decoded, flags); + + clc.voipIncomingSequence[sender] = sequence + frames; +} +#endif + + +/* +===================== +CL_ParseCommandString + +Command strings are just saved off until cgame asks for them +when it transitions a snapshot +===================== +*/ +void CL_ParseCommandString( msg_t *msg ) { + char *s; + int seq; + int index; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed stored it off + if ( clc.serverCommandSequence >= seq ) { + return; + } + clc.serverCommandSequence = seq; + + index = seq & (MAX_RELIABLE_COMMANDS-1); + Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); +} + + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage( msg_t *msg ) { + int cmd; + + if ( cl_shownet->integer == 1 ) { + Com_Printf ("%i ",msg->cursize); + } else if ( cl_shownet->integer >= 2 ) { + Com_Printf ("------------------\n"); + } + + MSG_Bitstream(msg); + + // get the reliable sequence acknowledge number + clc.reliableAcknowledge = MSG_ReadLong( msg ); + // + if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { + clc.reliableAcknowledge = clc.reliableSequence; + } + + // + // parse the message + // + while ( 1 ) { + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); + break; + } + + cmd = MSG_ReadByte( msg ); + + if ( clc.netchan.alternateProtocol != 0 ) + { + // See if this is an extension command after the EOF, which means we + // got data that a legacy client should ignore. + if ( cmd == svc_EOF && MSG_LookaheadByte( msg ) == svc_voipSpeex ) { + SHOWNET( msg, "EXTENSION" ); + MSG_ReadByte( msg ); // throw the svc_extension byte away. + cmd = MSG_ReadByte( msg ); // something legacy clients can't do! + // sometimes you get a svc_extension at end of stream...dangling + // bits in the huffman decoder giving a bogus value? + if ( cmd == -1 ) { + cmd = svc_EOF; + } + } + + if ( cmd == svc_voipSpeex ) { + cmd = svc_voipSpeex + 1; + } else if ( cmd == svc_voipSpeex + 1 ) { + cmd = svc_voipSpeex; + } + } + + if (cmd == svc_EOF) { + SHOWNET( msg, "END OF MESSAGE" ); + break; + } + + if ( cl_shownet->integer >= 2 ) { + if ( (cmd < 0) || (!svc_strings[cmd]) ) { + Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); + } else { + SHOWNET( msg, svc_strings[cmd] ); + } + } + + // other commands + switch ( cmd ) { + default: + Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message"); + break; + case svc_nop: + break; + case svc_serverCommand: + CL_ParseCommandString( msg ); + break; + case svc_gamestate: + CL_ParseGamestate( msg ); + break; + case svc_snapshot: + CL_ParseSnapshot( msg ); + break; + case svc_download: + CL_ParseDownload( msg ); + break; + case svc_voipSpeex: +#ifdef USE_VOIP + CL_ParseVoip( msg, true ); +#endif + break; + case svc_voipOpus: +#ifdef USE_VOIP + CL_ParseVoip( msg, !clc.voipEnabled ); +#endif + break; + } + } +} diff --git a/src/client/cl_rest.cpp b/src/client/cl_rest.cpp new file mode 100644 index 0000000..c1ff2fc --- /dev/null +++ b/src/client/cl_rest.cpp @@ -0,0 +1,117 @@ +// +// Restclient wrapper abusively modified to be a one-off donwloader. +// -wtfbbqhax +// +// TODO Verify sha256 of file if exists + +#include "cl_rest.h" + +#include + +#include +#include +#include +#include +#include + +#include "qcommon/files.h" +#include "restclient/restclient.h" + +bool is_good(std::string filename, int permissions = (R_OK|W_OK)) +{ + int ret = access(filename.c_str(), permissions); + if (ret) + std::cerr << filename << ": " << strerror(errno) << std::endl; + + return !ret; +} + +bool MakeDir(std::string destdir, std::string basegame) +{ + std::string destpath(destdir); + + if ( basegame != "" ) + { + destpath += '/'; + destpath += basegame; + } + + if ( *destpath.rbegin() != '/' ) + destpath += '/'; // XXX FS_CreatePath requires a trailing slash. + // Maybe the assumption is that a file listing might be included? + + FS_CreatePath(destpath.c_str()); + return true; +} + +#include "sys/dialog.h" +static bool PromptDownloadPk3s(std::string basegame, const std::vector& missing) +{ + std::string msg; + + msg = "The following files must be downloaded to complete the installation.\n\n"; + for ( auto f : missing ) + msg += "\t" + basegame + "/" + f + "\n"; + + msg += "\n"; + msg += "Yes to continue, No to quit the game."; + + if( Sys_Dialog( DT_YES_NO, msg.c_str(), "You're almost ready!" ) == DR_YES ) + return true; + + return false; +} + +bool GetTremulousPk3s(const char* destdir, const char* basegame) +{ + std::string baseuri = "https://github.com/wtfbbqhax/tremulous-data/raw/master/"; + std::vector files = { + "data-gpp1.pk3", + "data-1.1.0.pk3", + "map-arachnid2-1.1.0.pk3", + "map-atcs-1.1.0.pk3", + "map-karith-1.1.0.pk3", + "map-nexus6-1.1.0.pk3", + "map-niveus-1.1.0.pk3", + "map-transit-1.1.0.pk3", + "map-tremor-1.1.0.pk3", + "map-uncreation-1.1.0.pk3" + }; + + RestClient::init(); + + MakeDir(destdir, basegame); + + if (!PromptDownloadPk3s(basegame, files)) + return false; + + for (auto f : files ) + { + std::string destpath(destdir); + destpath += "/"; + destpath += basegame; + destpath += "/"; + destpath += f; + + if ( is_good(destpath) ) + { + return false; + } + + std::cout << "Downloading " << baseuri << f << std::endl; + std::ofstream dl(destpath); + //dl.open(destpath); + if ( dl.fail() ) + { + std::cerr << "Error " << strerror(errno) << "\n"; + continue; + } + + RestClient::Response resp = RestClient::get(baseuri + f); + + dl << resp.body; + dl.close(); + } + + return true; +} diff --git a/src/client/cl_rest.h b/src/client/cl_rest.h new file mode 100644 index 0000000..afde7d2 --- /dev/null +++ b/src/client/cl_rest.h @@ -0,0 +1,18 @@ +#ifndef _CL_REST_H_ +#define _CL_REST_H_ + +bool GetPermissions(const char*execpath); + +// Download a fresh copy of the "required" base game files to the top-most fs_basegame. +// +// Returns true(1) on success +// Return false(0) on error +// +bool GetTremulousPk3s(const char* destdir, const char* basegame); + +#include + +//bool is_good(std::string filename, int permissions = (R_OK|W_OK)); +bool MakeDir(std::string destdir, std::string basegame); + +#endif diff --git a/src/client/cl_scrn.cpp b/src/client/cl_scrn.cpp new file mode 100644 index 0000000..7d0c7e2 --- /dev/null +++ b/src/client/cl_scrn.cpp @@ -0,0 +1,588 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +#include "client.h" + +bool scr_initialized; // ready to draw + +cvar_t *cl_timegraph; +cvar_t *cl_debuggraph; +cvar_t *cl_graphheight; +cvar_t *cl_graphscale; +cvar_t *cl_graphshift; + +/* +================ +SCR_DrawNamedPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawNamedPic(float x, float y, float width, float height, const char *picname) +{ + qhandle_t hShader; + + assert(width != 0); + + hShader = re.RegisterShader(picname); + SCR_AdjustFrom640(&x, &y, &width, &height); + re.DrawStretchPic(x, y, width, height, 0, 0, 1, 1, hShader); +} + +/* +================ +SCR_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void SCR_AdjustFrom640(float *x, float *y, float *w, float *h) +{ + float xscale; + float yscale; + +#if 0 + // adjust for wide screens + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + + // scale for screen sizes + xscale = cls.glconfig.vidWidth / 640.0; + yscale = cls.glconfig.vidHeight / 480.0; + if (x) + { + *x *= xscale; + } + if (y) + { + *y *= yscale; + } + if (w) + { + *w *= xscale; + } + if (h) + { + *h *= yscale; + } +} + +/* +================ +SCR_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_FillRect(float x, float y, float width, float height, const float *color) +{ + re.SetColor(color); + + SCR_AdjustFrom640(&x, &y, &width, &height); + re.DrawStretchPic(x, y, width, height, 0, 0, 0, 0, cls.whiteShader); + + re.SetColor(NULL); +} + +/* +================ +SCR_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawPic(float x, float y, float width, float height, qhandle_t hShader) +{ + SCR_AdjustFrom640(&x, &y, &width, &height); + re.DrawStretchPic(x, y, width, height, 0, 0, 1, 1, hShader); +} + +/* +** SCR_DrawChar +** chars are drawn at 640*480 virtual screen size +*/ +static void SCR_DrawChar(int x, int y, float size, int ch) +{ + int row, col; + float frow, fcol; + float ax, ay, aw, ah; + + ch &= 255; + + if (ch == ' ') + { + return; + } + + if (y < -size) + { + return; + } + + ax = x; + ay = y; + aw = size; + ah = size; + SCR_AdjustFrom640(&ax, &ay, &aw, &ah); + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625; + fcol = col * 0.0625; + size = 0.0625; + + re.DrawStretchPic(ax, ay, aw, ah, fcol, frow, fcol + size, frow + size, cls.charSetShader); +} + +/* +** SCR_DrawSmallChar +** small chars are drawn at native screen resolution +*/ +void SCR_DrawSmallChar(int x, int y, int ch) +{ + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if (ch == ' ') + { + return; + } + + if (y < -SMALLCHAR_HEIGHT) + { + return; + } + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625; + fcol = col * 0.0625; + size = 0.0625; + + re.DrawStretchPic(x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, fcol, frow, fcol + size, frow + size, cls.charSetShader); +} + +/* +================== +SCR_DrawBigString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +static void SCR_DrawStringExt( + int x, int y, float size, const char *string, float *setColor, bool forceColor, bool noColorEscape) +{ + vec4_t color; + const char *s; + int xx; + + // draw the drop shadow + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + re.SetColor(color); + s = string; + xx = x; + while (*s) + { + if (!noColorEscape && Q_IsColorString(s)) + { + s += 2; + continue; + } + SCR_DrawChar(xx + 2, y + 2, size, *s); + xx += size; + s++; + } + + // draw the colored text + s = string; + xx = x; + re.SetColor(setColor); + while (*s) + { + if (Q_IsColorString(s)) + { + if (!forceColor) + { + ::memcpy(color, g_color_table[ColorIndex(*(s + 1))], sizeof(color)); + color[3] = setColor[3]; + re.SetColor(color); + } + if (!noColorEscape) + { + s += 2; + continue; + } + } + SCR_DrawChar(xx, y, size, *s); + xx += size; + s++; + } + re.SetColor(NULL); +} + +void SCR_DrawBigString(int x, int y, const char *s, float alpha, bool noColorEscape) +{ + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + SCR_DrawStringExt(x, y, BIGCHAR_WIDTH, s, color, false, noColorEscape); +} + +void SCR_DrawBigStringColor(int x, int y, const char *s, vec4_t color, bool noColorEscape) +{ + SCR_DrawStringExt(x, y, BIGCHAR_WIDTH, s, color, true, noColorEscape); +} + +/* +================== +SCR_DrawSmallString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. +================== +*/ +void SCR_DrawSmallStringExt(int x, int y, const char *string, float *setColor, bool forceColor, bool noColorEscape) +{ + vec4_t color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + re.SetColor(setColor); + while (*s) + { + if (Q_IsColorString(s)) + { + if (!forceColor) + { + ::memcpy(color, g_color_table[ColorIndex(*(s + 1))], sizeof(color)); + color[3] = setColor[3]; + re.SetColor(color); + } + if (!noColorEscape) + { + s += 2; + continue; + } + } + SCR_DrawSmallChar(xx, y, *s); + xx += SMALLCHAR_WIDTH; + s++; + } + re.SetColor(NULL); +} + +/* +** SCR_Strlen -- skips color escape codes +*/ +static int SCR_Strlen(const char *str) +{ + const char *s = str; + int count = 0; + + while (*s) + { + if (Q_IsColorString(s)) + { + s += 2; + } + else + { + count++; + s++; + } + } + + return count; +} + +/* +** SCR_GetBigStringWidth +*/ +int SCR_GetBigStringWidth(const char *str) { return SCR_Strlen(str) * BIGCHAR_WIDTH; } +//=============================================================================== + +#ifdef USE_VOIP +/* +================= +SCR_DrawVoipMeter + +FIXME: inherited from ioq3, move to cgame/ui +================= +*/ +void SCR_DrawVoipMeter(void) +{ + char buffer[16]; + char string[256]; + int limit, i; + + if (!cl_voipShowMeter->integer) + return; // player doesn't want to show meter at all. + else if (!cl_voipSend->integer) + return; // not recording at the moment. + else if (clc.state != CA_ACTIVE) + return; // not connected to a server. + else if (!clc.voipEnabled) + return; // server doesn't support VoIP. + else if (clc.demoplaying) + return; // playing back a demo. + else if (!cl_voip->integer) + return; // client has VoIP support disabled. + + limit = (int)(clc.voipPower * 10.0f); + if (limit > 10) limit = 10; + + for (i = 0; i < limit; i++) buffer[i] = '*'; + while (i < 10) buffer[i++] = ' '; + buffer[i] = '\0'; + + sprintf(string, "VoIP: [%s]", buffer); + SCR_DrawStringExt(320 - strlen(string) * 4, 10, 8, string, g_color_table[7], true, false); +} +#endif + +/* +=============================================================================== + +DEBUG GRAPH + +=============================================================================== +*/ + +static int current; +static float values[1024]; + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph(float value) +{ + values[current] = value; + current = (current + 1) % ARRAY_LEN(values); +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph(void) +{ + int a, x, y, w, i, h; + float v; + + // + // draw the graph + // + w = cls.glconfig.vidWidth; + x = 0; + y = cls.glconfig.vidHeight; + re.SetColor(g_color_table[0]); + re.DrawStretchPic(x, y - cl_graphheight->integer, w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader); + re.SetColor(NULL); + + for (a = 0; a < w; a++) + { + i = (ARRAY_LEN(values) + current - 1 - (a % ARRAY_LEN(values))) % ARRAY_LEN(values); + v = values[i]; + v = v * cl_graphscale->integer + cl_graphshift->integer; + + if (v < 0) v += cl_graphheight->integer * (1 + (int)(-v / cl_graphheight->integer)); + h = (int)v % cl_graphheight->integer; + re.DrawStretchPic(x + w - 1 - a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader); + } +} + +//============================================================================= + +/* +================== +SCR_Init +================== +*/ +void SCR_Init(void) +{ + cl_timegraph = Cvar_Get("timegraph", "0", CVAR_CHEAT); + cl_debuggraph = Cvar_Get("debuggraph", "0", CVAR_CHEAT); + cl_graphheight = Cvar_Get("graphheight", "32", CVAR_CHEAT); + cl_graphscale = Cvar_Get("graphscale", "1", CVAR_CHEAT); + cl_graphshift = Cvar_Get("graphshift", "0", CVAR_CHEAT); + + scr_initialized = true; +} + +//======================================================= + +/* +================== +SCR_DrawScreenField + +This will be called twice if rendering in stereo mode +================== +*/ +void SCR_DrawScreenField(stereoFrame_t stereoFrame) +{ + bool uiFullscreen; + + re.BeginFrame(stereoFrame); + + uiFullscreen = (cls.ui && VM_Call(cls.ui, UI_IS_FULLSCREEN - (cls.uiInterface == 2 ? 2 : 0))); + + // wide aspect ratio screens need to have the sides cleared + // unless they are displaying game renderings + if (uiFullscreen || clc.state < CA_LOADING) + { + if (cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640) + { + re.SetColor(g_color_table[0]); + re.DrawStretchPic(0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader); + re.SetColor(NULL); + } + } + + // if the menu is going to cover the entire screen, we + // don't need to render anything under it + if (cls.ui && !uiFullscreen) + { + switch (clc.state) + { + default: + Com_Error(ERR_FATAL, "SCR_DrawScreenField: bad clc.state"); + break; + case CA_CINEMATIC: + SCR_DrawCinematic(); + break; + case CA_DISCONNECTED: + // force menu up + S_StopAllSounds(); + VM_Call(cls.ui, UI_SET_ACTIVE_MENU - (cls.uiInterface == 2 ? 2 : 0), UIMENU_MAIN); + break; + case CA_CONNECTING: + case CA_CHALLENGING: + case CA_CONNECTED: + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call(cls.ui, UI_REFRESH - (cls.uiInterface == 2 ? 2 : 0), cls.realtime); + VM_Call(cls.ui, UI_DRAW_CONNECT_SCREEN - (cls.uiInterface == 2 ? 2 : 0), false); + break; + case CA_LOADING: + case CA_PRIMED: + // draw the game information screen and loading progress + CL_CGameRendering(stereoFrame); + break; + case CA_ACTIVE: + // always supply STEREO_CENTER as vieworg offset is now done by the engine. + CL_CGameRendering(stereoFrame); +#ifdef USE_VOIP + SCR_DrawVoipMeter(); +#endif + break; + } + } + + // the menu draws next + if (Key_GetCatcher() & KEYCATCH_UI && cls.ui) + { + VM_Call(cls.ui, UI_REFRESH - (cls.uiInterface == 2 ? 2 : 0), cls.realtime); + } + + // console draws next + Con_DrawConsole(); + + // debug graph can be drawn on top of anything + if (cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer) + { + SCR_DrawDebugGraph(); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen(void) +{ + static int recursive; + + if (!scr_initialized) + { + return; // not initialized yet + } + + if (++recursive > 2) + { + Com_Error(ERR_FATAL, "SCR_UpdateScreen: recursively called"); + } + recursive = 1; + + // If there is no VM, there are also no rendering commands issued. Stop the renderer in + // that case. + if (cls.ui || com_dedicated->integer) + { + // XXX + int in_anaglyphMode = Cvar_VariableIntegerValue("r_anaglyphMode"); + // if running in stereo, we need to draw the frame twice + if (cls.glconfig.stereoEnabled || in_anaglyphMode) + { + SCR_DrawScreenField(STEREO_LEFT); + SCR_DrawScreenField(STEREO_RIGHT); + } + else + { + SCR_DrawScreenField(STEREO_CENTER); + } + + if (com_speeds->integer) + { + re.EndFrame(&time_frontend, &time_backend); + } + else + { + re.EndFrame(NULL, NULL); + } + } + + recursive = 0; +} diff --git a/src/client/cl_ui.cpp b/src/client/cl_ui.cpp new file mode 100644 index 0000000..4052fe6 --- /dev/null +++ b/src/client/cl_ui.cpp @@ -0,0 +1,1269 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" + +#include "cl_updates.h" + +/* +==================== +GetClientState +==================== +*/ +static void GetClientState(uiClientState_t *state) +{ + state->connectPacketCount = clc.connectPacketCount; + state->connState = clc.state; + Q_strncpyz(state->servername, clc.servername, sizeof(state->servername)); + Q_strncpyz(state->updateInfoString, cls.updateInfoString, sizeof(state->updateInfoString)); + Q_strncpyz(state->messageString, clc.serverMessage, sizeof(state->messageString)); + state->clientNum = (clc.netchan.alternateProtocol == 2 ? cl.snap.alternatePs.clientNum : cl.snap.ps.clientNum); +} + +/* +==================== +LAN_LoadCachedServers +==================== +*/ +void LAN_LoadCachedServers(void) +{ + int size; + fileHandle_t fileIn; + cls.numglobalservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) + { + FS_Read(&cls.numglobalservers, sizeof(int), fileIn); + FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn); + FS_Read(&size, sizeof(int), fileIn); + if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers)) + { + FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn); + FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn); + } + else + { + cls.numglobalservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + } + FS_FCloseFile(fileIn); + } +} + +/* +==================== +LAN_SaveServersToCache +==================== +*/ +void LAN_SaveServersToCache(void) +{ + fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat"); + FS_Write(&cls.numglobalservers, sizeof(int), fileOut); + FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); + + int size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers); + FS_Write(&size, sizeof(int), fileOut); + FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut); + FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut); + FS_FCloseFile(fileOut); +} + +/* +==================== +GetNews +==================== +*/ +static bool GetNews(bool begin) +{ + bool finished = false; + fileHandle_t fileIn; + int readSize; + + if (begin) + { // if not already using curl, start the download + if (!clc.downloadCURLM) + { + if (!CL_cURL_Init()) + { + Cvar_Set("cl_newsString", "^1Error: Could not load cURL library"); + return true; + } + clc.activeCURLNotGameRelated = true; + CL_cURL_BeginDownload("news.dat", "http://grangerhub.com/wp-content/uploads/clientnews.txt"); + return false; + } + } + + if (!clc.downloadCURLM && FS_SV_FOpenFileRead("news.dat", &fileIn)) + { + readSize = FS_Read(clc.newsString, sizeof(clc.newsString), fileIn); + FS_FCloseFile(fileIn); + clc.newsString[readSize] = '\0'; + if (readSize > 0) + { + finished = true; + clc.cURLUsed = false; + CL_cURL_Shutdown(); + clc.activeCURLNotGameRelated = false; + } + } + if (!finished) strcpy(clc.newsString, "Retrieving..."); + Cvar_Set("cl_newsString", clc.newsString); + return finished; + Cvar_Set("cl_newsString", "^1You must compile your client with CURL support to use this feature"); + return true; +} + +/* +==================== +LAN_ResetPings +==================== +*/ +static void LAN_ResetPings(int source) +{ + int count, i; + serverInfo_t *servers = NULL; + count = 0; + + switch (source) + { + case AS_LOCAL: + servers = &cls.localServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_MPLAYER: + case AS_GLOBAL: + servers = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES: + servers = &cls.favoriteServers[0]; + count = MAX_OTHER_SERVERS; + break; + } + if (servers) + { + for (i = 0; i < count; i++) + { + servers[i].ping = -1; + } + } +} + +/* +==================== +LAN_AddServer +==================== +*/ +static int LAN_AddServer(int source, const char *name, const char *address) +{ + int max, *count, i; + netadr_t adr; + serverInfo_t *servers = NULL; + max = MAX_OTHER_SERVERS; + count = NULL; + + switch (source) + { + case AS_LOCAL: + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL: + max = MAX_GLOBAL_SERVERS; + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES: + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers && *count < max) + { + NET_StringToAdr(address, &adr, NA_UNSPEC); + for (i = 0; i < *count; i++) + { + if (NET_CompareAdr(servers[i].adr, adr)) + { + break; + } + } + if (i >= *count) + { + servers[*count].adr = adr; + Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName)); + servers[*count].visible = true; + (*count)++; + return 1; + } + return 0; + } + return -1; +} + +/* +==================== +LAN_RemoveServer +==================== +*/ +static void LAN_RemoveServer(int source, const char *addr) +{ + int *count, i; + serverInfo_t *servers = NULL; + count = NULL; + switch (source) + { + case AS_LOCAL: + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL: + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES: + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers) + { + netadr_t comp; + NET_StringToAdr(addr, &comp, NA_UNSPEC); + for (i = 0; i < *count; i++) + { + if (NET_CompareAdr(comp, servers[i].adr)) + { + int j = i; + while (j < *count - 1) + { + ::memcpy(&servers[j], &servers[j + 1], sizeof(servers[j])); + j++; + } + (*count)--; + break; + } + } + } +} + +/* +==================== +LAN_GetServerCount +==================== +*/ +static int LAN_GetServerCount(int source) +{ + switch (source) + { + case AS_LOCAL: + return cls.numlocalservers; + break; + case AS_MPLAYER: + case AS_GLOBAL: + return cls.numglobalservers; + break; + case AS_FAVORITES: + return cls.numfavoriteservers; + break; + } + return 0; +} + +/* +==================== +LAN_GetLocalServerAddressString +==================== +*/ +static void LAN_GetServerAddressString(int source, int n, char *buf, int buflen) +{ + switch (source) + { + case AS_LOCAL: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + Q_strncpyz(buf, NET_AdrToStringwPort(cls.localServers[n].adr), buflen); + if (cls.localServers[n].adr.alternateProtocol != 0) + Q_strncpyz(buf + (int)strlen(buf), (cls.localServers[n].adr.alternateProtocol == 1 ? " -g" : " -1"), + buflen - (int)strlen(buf)); + return; + } + break; + case AS_MPLAYER: + case AS_GLOBAL: + if (n >= 0 && n < MAX_GLOBAL_SERVERS) + { + Q_strncpyz(buf, NET_AdrToStringwPort(cls.globalServers[n].adr), buflen); + if (cls.globalServers[n].adr.alternateProtocol != 0) + Q_strncpyz(buf + (int)strlen(buf), + (cls.globalServers[n].adr.alternateProtocol == 1 ? " -g" : " -1"), buflen - (int)strlen(buf)); + return; + } + break; + case AS_FAVORITES: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + Q_strncpyz(buf, NET_AdrToStringwPort(cls.favoriteServers[n].adr), buflen); + if (cls.favoriteServers[n].adr.alternateProtocol != 0) + Q_strncpyz(buf + (int)strlen(buf), + (cls.favoriteServers[n].adr.alternateProtocol == 1 ? " -g" : " -1"), buflen - (int)strlen(buf)); + return; + } + break; + } + buf[0] = '\0'; +} + +/* +==================== +LAN_GetServerInfo +==================== +*/ +static void LAN_GetServerInfo(int source, int n, char *buf, int buflen) +{ + char info[MAX_STRING_CHARS]; + serverInfo_t *server = NULL; + info[0] = '\0'; + switch (source) + { + case AS_LOCAL: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL: + if (n >= 0 && n < MAX_GLOBAL_SERVERS) + { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + server = &cls.favoriteServers[n]; + } + break; + } + if (server && buf) + { + buf[0] = '\0'; + if (server->adr.alternateProtocol != 0) + { + char hn[MAX_HOSTNAME_LENGTH]; + Q_strncpyz(hn, server->hostName, sizeof(hn)); + Q_strcat( + hn, sizeof(hn), (server->adr.alternateProtocol == 1 ? S_COLOR_WHITE " [GPP]" : S_COLOR_WHITE " [1.1]")); + Info_SetValueForKey(info, "hostname", hn); + } + else + { + Info_SetValueForKey(info, "hostname", server->hostName); + } + Info_SetValueForKey(info, "mapname", server->mapName); + Info_SetValueForKey(info, "label", server->label); + Info_SetValueForKey(info, "clients", va("%i", server->clients)); + Info_SetValueForKey(info, "sv_maxclients", va("%i", server->maxClients)); + Info_SetValueForKey(info, "ping", va("%i", server->ping)); + Info_SetValueForKey(info, "minping", va("%i", server->minPing)); + Info_SetValueForKey(info, "maxping", va("%i", server->maxPing)); + Info_SetValueForKey(info, "game", server->game); + Info_SetValueForKey(info, "gametype", va("%i", server->gameType)); + Info_SetValueForKey(info, "nettype", va("%i", server->netType)); + Info_SetValueForKey(info, "addr", NET_AdrToStringwPort(server->adr)); + Q_strncpyz(buf, info, buflen); + } + else + { + if (buf) + { + buf[0] = '\0'; + } + } +} + +/* +==================== +LAN_GetServerPing +==================== +*/ +static int LAN_GetServerPing(int source, int n) +{ + serverInfo_t *server = NULL; + switch (source) + { + case AS_LOCAL: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL: + if (n >= 0 && n < MAX_GLOBAL_SERVERS) + { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + server = &cls.favoriteServers[n]; + } + break; + } + if (server) + { + return server->ping; + } + return -1; +} + +/* +==================== +LAN_GetServerPtr +==================== +*/ +static serverInfo_t *LAN_GetServerPtr(int source, int n) +{ + switch (source) + { + case AS_LOCAL: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + return &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL: + if (n >= 0 && n < MAX_GLOBAL_SERVERS) + { + return &cls.globalServers[n]; + } + break; + case AS_FAVORITES: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + return &cls.favoriteServers[n]; + } + break; + } + return NULL; +} + +#define FEATURED_MAXPING 200 +/* +==================== +LAN_CompareServers +==================== +*/ +static int LAN_CompareServers(int source, int sortKey, int sortDir, int s1, int s2) +{ + int res; + serverInfo_t *server1, *server2; + + server1 = LAN_GetServerPtr(source, s1); + server2 = LAN_GetServerPtr(source, s2); + if (!server1 || !server2) + { + return 0; + } + + // featured servers on top + if ((server1->label[0] && server1->ping <= FEATURED_MAXPING) || + (server2->label[0] && server2->ping <= FEATURED_MAXPING)) + { + res = Q_stricmpn(server1->label, server2->label, MAX_FEATLABEL_CHARS); + if (res) return -res; + } + + res = 0; + switch (sortKey) + { + case SORT_HOST: + { + char hostName1[MAX_HOSTNAME_LENGTH]; + char hostName2[MAX_HOSTNAME_LENGTH]; + char *p; + int i; + + for (p = server1->hostName, i = 0; *p != '\0'; p++) + { + if (Q_isalpha(*p)) hostName1[i++] = *p; + } + hostName1[i] = '\0'; + + for (p = server2->hostName, i = 0; *p != '\0'; p++) + { + if (Q_isalpha(*p)) hostName2[i++] = *p; + } + hostName2[i] = '\0'; + + res = Q_stricmp(hostName1, hostName2); + } + break; + + case SORT_GAME: + res = Q_stricmp(server1->game, server2->game); + break; + case SORT_MAP: + res = Q_stricmp(server1->mapName, server2->mapName); + break; + case SORT_CLIENTS: + if (server1->clients < server2->clients) + { + res = -1; + } + else if (server1->clients > server2->clients) + { + res = 1; + } + else + { + res = 0; + } + break; + case SORT_PING: + if (server1->ping < server2->ping) + { + res = -1; + } + else if (server1->ping > server2->ping) + { + res = 1; + } + else + { + res = 0; + } + break; + } + + if (sortDir) + { + if (res < 0) return 1; + if (res > 0) return -1; + return 0; + } + return res; +} + +/* +==================== +LAN_GetPingQueueCount +==================== +*/ +static int LAN_GetPingQueueCount(void) { return (CL_GetPingQueueCount()); } +/* +==================== +LAN_ClearPing +==================== +*/ +static void LAN_ClearPing(int n) { CL_ClearPing(n); } +/* +==================== +LAN_GetPing +==================== +*/ +static void LAN_GetPing(int n, char *buf, int buflen, int *pingtime) { CL_GetPing(n, buf, buflen, pingtime); } +/* +==================== +LAN_GetPingInfo +==================== +*/ +static void LAN_GetPingInfo(int n, char *buf, int buflen) { CL_GetPingInfo(n, buf, buflen); } +/* +==================== +LAN_MarkServerVisible +==================== +*/ +static void LAN_MarkServerVisible(int source, int n, bool visible) +{ + if (n == -1) + { + int count = MAX_OTHER_SERVERS; + serverInfo_t *server = NULL; + switch (source) + { + case AS_LOCAL: + server = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL: + server = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES: + server = &cls.favoriteServers[0]; + break; + } + if (server) + { + for (n = 0; n < count; n++) + { + server[n].visible = visible; + } + } + } + else + { + switch (source) + { + case AS_LOCAL: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + cls.localServers[n].visible = visible; + } + break; + case AS_MPLAYER: + case AS_GLOBAL: + if (n >= 0 && n < MAX_GLOBAL_SERVERS) + { + cls.globalServers[n].visible = visible; + } + break; + case AS_FAVORITES: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + cls.favoriteServers[n].visible = visible; + } + break; + } + } +} + +/* +======================= +LAN_ServerIsVisible +======================= +*/ +static bool LAN_ServerIsVisible(int source, int n) +{ + switch (source) + { + case AS_LOCAL: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + return cls.localServers[n].visible; + } + break; + case AS_MPLAYER: + case AS_GLOBAL: + if (n >= 0 && n < MAX_GLOBAL_SERVERS) + { + return cls.globalServers[n].visible; + } + break; + case AS_FAVORITES: + if (n >= 0 && n < MAX_OTHER_SERVERS) + { + return cls.favoriteServers[n].visible; + } + break; + } + return false; +} + +/* +======================= +LAN_UpdateVisiblePings +======================= +*/ +static bool LAN_UpdateVisiblePings(int source) { return CL_UpdateVisiblePings_f(source); } +/* +==================== +LAN_GetServerStatus +==================== +*/ +static bool LAN_GetServerStatus(char *serverAddress, char *serverStatus, int maxLen) +{ + return CL_ServerStatus(serverAddress, serverStatus, maxLen); +} + +/* +==================== +CL_GetGlConfig +==================== +*/ +static void CL_GetGlconfig(glconfig_t *config) { *config = cls.glconfig; } +/* +==================== +GetConfigString +==================== +*/ +static bool GetConfigString(int i, char *buf, int size) +{ + int offset; + + if (i < 0 || i >= MAX_CONFIGSTRINGS) return false; + + offset = cl.gameState.stringOffsets[i]; + if (!offset) + { + if (size) buf[0] = '\0'; + return false; + } + + Q_strncpyz(buf, cl.gameState.stringData + offset, size); + + return true; +} + +/* +==================== +FloatAsInt +==================== +*/ +static int FloatAsInt(float f) +{ + floatint_t fi; + fi.f = f; + return fi.i; +} + +static bool probingUI = false; + +/* +==================== +CL_UISystemCalls + +The ui module is making a system call +==================== +*/ +intptr_t CL_UISystemCalls(intptr_t *args) +{ + if (cls.uiInterface == 2) + { + if (args[0] >= UI_R_SETCLIPREGION && args[0] < UI_MEMSET) + { + if (args[0] < UI_S_STOPBACKGROUNDTRACK - 1) + { + args[0] += 1; + } + else if (args[0] < UI_S_STOPBACKGROUNDTRACK + 4) + { + args[0] += UI_PARSE_ADD_GLOBAL_DEFINE - UI_S_STOPBACKGROUNDTRACK + 1; + } + else if (args[0] >= UI_PARSE_ADD_GLOBAL_DEFINE + 4) + { + args[0] += UI_GETNEWS - UI_PARSE_ADD_GLOBAL_DEFINE - 5; + if (args[0] == UI_PARSE_SOURCE_FILE_AND_LINE || args[0] == UI_GETNEWS) + args[0] = UI_PARSE_SOURCE_FILE_AND_LINE - 1337 - args[0]; + } + else + { + args[0] -= 4; + } + } + + switch (args[0]) + { + case UI_LAN_GETSERVERCOUNT: + case UI_LAN_GETSERVERADDRESSSTRING: + case UI_LAN_GETSERVERINFO: + case UI_LAN_MARKSERVERVISIBLE: + case UI_LAN_UPDATEVISIBLEPINGS: + case UI_LAN_RESETPINGS: + case UI_LAN_ADDSERVER: + case UI_LAN_REMOVESERVER: + case UI_LAN_GETSERVERPING: + case UI_LAN_SERVERISVISIBLE: + case UI_LAN_COMPARESERVERS: + if (args[1] == AS_GLOBAL) + { + args[1] = AS_LOCAL; + } + else if (args[1] == AS_LOCAL) + { + args[1] = AS_GLOBAL; + } + } + } + + switch (args[0]) + { + case UI_ERROR: + if (probingUI) + { + cls.uiInterface = 2; + return 0; + } + Com_Error(ERR_DROP, "%s", (const char *)VMA(1)); + return 0; + + case UI_PRINT: + Com_Printf("%s", (const char *)VMA(1)); + return 0; + + case UI_MILLISECONDS: + return Sys_Milliseconds(); + + case UI_CVAR_REGISTER: + Cvar_Register((vmCvar_t *)VMA(1), (const char *)VMA(2), (const char *)VMA(3), args[4]); + return 0; + + case UI_CVAR_UPDATE: + Cvar_Update((vmCvar_t *)VMA(1)); + return 0; + + case UI_CVAR_SET: + Cvar_SetSafe((const char *)VMA(1), (const char *)VMA(2)); + return 0; + + case UI_CVAR_VARIABLEVALUE: + return FloatAsInt(Cvar_VariableValue((const char *)VMA(1))); + + case UI_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer((const char *)VMA(1), (char *)VMA(2), args[3]); + return 0; + + case UI_CVAR_SETVALUE: + Cvar_SetValueSafe((const char *)VMA(1), VMF(2)); + return 0; + + case UI_CVAR_RESET: + Cvar_Reset((const char *)VMA(1)); + return 0; + + case UI_CVAR_CREATE: + Cvar_Register(NULL, (const char *)VMA(1), (const char *)VMA(2), args[3]); + return 0; + + case UI_CVAR_INFOSTRINGBUFFER: + Cvar_InfoStringBuffer(args[1], (char *)VMA(2), args[3]); + return 0; + + case UI_ARGC: + return Cmd_Argc(); + + case UI_ARGV: + Cmd_ArgvBuffer(args[1], (char *)VMA(2), args[3]); + return 0; + + case UI_CMD_EXECUTETEXT: + if (args[1] == EXEC_NOW) + { + if (!strncmp((const char *)VMA(2), "snd_restart", 11) + || !strncmp((const char *)VMA(2), "vid_restart", 11) + || !strncmp((const char *)VMA(2), "quit", 5)) + { + Com_Printf(S_COLOR_YELLOW "turning EXEC_NOW '%.11s' into EXEC_INSERT\n", (const char *)VMA(2)); + args[1] = EXEC_INSERT; + } + } + + // TODO: Do this better + if (!strncmp((const char *)VMA(2), "checkForUpdate", 14)) + { + CL_GetLatestRelease(); + return 0; + } + + Cbuf_ExecuteText(args[1], (const char *)VMA(2)); + return 0; + + case UI_FS_FOPENFILE: + return FS_FOpenFileByMode((const char *)VMA(1), (fileHandle_t *)VMA(2), (FS_Mode)args[3]); + + case UI_FS_READ: + FS_Read(VMA(1), args[2], args[3]); + return 0; + + case UI_FS_WRITE: + FS_Write(VMA(1), args[2], args[3]); + return 0; + + case UI_FS_FCLOSEFILE: + FS_FCloseFile(args[1]); + return 0; + + case UI_FS_GETFILELIST: + return FS_GetFileList((const char *)VMA(1), (const char *)VMA(2), (char *)VMA(3), args[4]); + + case UI_FS_SEEK: + return FS_Seek((fileHandle_t)args[1], args[2], (FS_Origin)args[3]); + + case UI_R_REGISTERMODEL: + return re.RegisterModel((const char *)VMA(1)); + + case UI_R_REGISTERSKIN: + return re.RegisterSkin((const char *)VMA(1)); + + case UI_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip((const char *)VMA(1)); + + case UI_R_CLEARSCENE: + re.ClearScene(); + return 0; + + case UI_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene((const refEntity_t *)VMA(1)); + return 0; + + case UI_R_ADDPOLYTOSCENE: + re.AddPolyToScene(args[1], args[2], (const polyVert_t *)VMA(3), 1); + return 0; + + case UI_R_ADDLIGHTTOSCENE: + re.AddLightToScene((const float *)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5)); + return 0; + + case UI_R_RENDERSCENE: + re.RenderScene((const refdef_t *)VMA(1)); + return 0; + + case UI_R_SETCOLOR: + re.SetColor((const float *)VMA(1)); + return 0; + + case UI_R_SETCLIPREGION: + re.SetClipRegion((const float *)VMA(1)); + return 0; + + case UI_R_DRAWSTRETCHPIC: + re.DrawStretchPic(VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9]); + return 0; + + case UI_R_MODELBOUNDS: + re.ModelBounds(args[1], (float *)VMA(2), (float *)VMA(3)); + return 0; + + case UI_UPDATESCREEN: + SCR_UpdateScreen(); + return 0; + + case UI_CM_LERPTAG: + re.LerpTag((orientation_t *)VMA(1), args[2], args[3], args[4], VMF(5), (const char *)VMA(6)); + return 0; + + case UI_S_REGISTERSOUND: + return S_RegisterSound((const char *)VMA(1), (bool)args[2]); + + case UI_S_STARTLOCALSOUND: + S_StartLocalSound(args[1], args[2]); + return 0; + + case UI_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf(args[1], (char *)VMA(2), args[3]); + return 0; + + case UI_KEY_GETBINDINGBUF: + Key_GetBindingBuf(args[1], (char *)VMA(2), args[3]); + return 0; + + case UI_KEY_SETBINDING: + Key_SetBinding(args[1], (const char *)VMA(2)); + return 0; + + case UI_KEY_ISDOWN: + return Key_IsDown(args[1]); + + case UI_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode(); + + case UI_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode((bool)args[1]); + return 0; + + case UI_KEY_CLEARSTATES: + Key_ClearStates(); + return 0; + + case UI_KEY_GETCATCHER: + return Key_GetCatcher(); + + case UI_KEY_SETCATCHER: + // don't allow the ui module to toggle the console + Key_SetCatcher((args[1] & ~KEYCATCH_CONSOLE) | (Key_GetCatcher() & KEYCATCH_CONSOLE)); + return 0; + + case UI_GETCLIPBOARDDATA: + ((char *)VMA(1))[0] = '\0'; + return 0; + + case UI_GETCLIENTSTATE: + GetClientState((uiClientState_t *)VMA(1)); + return 0; + + case UI_GETGLCONFIG: + CL_GetGlconfig((glconfig_t *)VMA(1)); + return 0; + + case UI_GETCONFIGSTRING: + return GetConfigString(args[1], (char *)VMA(2), args[3]); + + case UI_LAN_LOADCACHEDSERVERS: + LAN_LoadCachedServers(); + return 0; + + case UI_LAN_SAVECACHEDSERVERS: + LAN_SaveServersToCache(); + return 0; + + case UI_LAN_ADDSERVER: + return LAN_AddServer(args[1], (const char *)VMA(2), (const char *)VMA(3)); + + case UI_LAN_REMOVESERVER: + LAN_RemoveServer(args[1], (const char *)VMA(2)); + return 0; + + case UI_LAN_GETPINGQUEUECOUNT: + return LAN_GetPingQueueCount(); + + case UI_LAN_CLEARPING: + LAN_ClearPing(args[1]); + return 0; + + case UI_LAN_GETPING: + LAN_GetPing(args[1], (char *)VMA(2), args[3], (int *)VMA(4)); + return 0; + + case UI_LAN_GETPINGINFO: + LAN_GetPingInfo(args[1], (char *)VMA(2), args[3]); + return 0; + + case UI_LAN_GETSERVERCOUNT: + return LAN_GetServerCount(args[1]); + + case UI_LAN_GETSERVERADDRESSSTRING: + LAN_GetServerAddressString(args[1], args[2], (char *)VMA(3), args[4]); + return 0; + + case UI_LAN_GETSERVERINFO: + LAN_GetServerInfo(args[1], args[2], (char *)VMA(3), args[4]); + return 0; + + case UI_LAN_GETSERVERPING: + return LAN_GetServerPing(args[1], args[2]); + + case UI_LAN_MARKSERVERVISIBLE: + LAN_MarkServerVisible(args[1], args[2], (bool)args[3]); + return 0; + + case UI_LAN_SERVERISVISIBLE: + return LAN_ServerIsVisible(args[1], args[2]); + + case UI_LAN_UPDATEVISIBLEPINGS: + return LAN_UpdateVisiblePings(args[1]); + + case UI_LAN_RESETPINGS: + LAN_ResetPings(args[1]); + return 0; + + case UI_LAN_SERVERSTATUS: + return LAN_GetServerStatus((char *)VMA(1), (char *)VMA(2), args[3]); + + case UI_GETNEWS: + return GetNews((bool)args[1]); + + case UI_LAN_COMPARESERVERS: + return LAN_CompareServers(args[1], args[2], args[3], args[4], args[5]); + + case UI_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + + case UI_SET_PBCLSTATUS: + return 0; + + case UI_R_REGISTERFONT: + re.RegisterFont((const char *)VMA(1), args[2], (fontInfo_t *)VMA(3)); + return 0; + + case UI_MEMSET: + ::memset(VMA(1), args[2], args[3]); + return 0; + + case UI_MEMCPY: + ::memcpy(VMA(1), VMA(2), args[3]); + return 0; + + case UI_STRNCPY: + strncpy((char *)VMA(1), (const char *)VMA(2), args[3]); + return args[1]; + + case UI_SIN: + return FloatAsInt(sin(VMF(1))); + + case UI_COS: + return FloatAsInt(cos(VMF(1))); + + case UI_ATAN2: + return FloatAsInt(atan2(VMF(1), VMF(2))); + + case UI_SQRT: + return FloatAsInt(sqrt(VMF(1))); + + case UI_FLOOR: + return FloatAsInt(floor(VMF(1))); + + case UI_CEIL: + return FloatAsInt(ceil(VMF(1))); + + case UI_PARSE_ADD_GLOBAL_DEFINE: + return Parse_AddGlobalDefine((char *)VMA(1)); + case UI_PARSE_LOAD_SOURCE: + return Parse_LoadSourceHandle((char *)VMA(1)); + case UI_PARSE_FREE_SOURCE: + return Parse_FreeSourceHandle(args[1]); + case UI_PARSE_READ_TOKEN: + return Parse_ReadTokenHandle(args[1], (pc_token_t *)VMA(2)); + case UI_PARSE_SOURCE_FILE_AND_LINE: + return Parse_SourceFileAndLine(args[1], (char *)VMA(2), (int *)VMA(3)); + + case UI_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + case UI_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack((const char *)VMA(1), (const char *)VMA(2)); + return 0; + + case UI_REAL_TIME: + return Com_RealTime((qtime_t *)VMA(1)); + + case UI_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic((const char *)VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case UI_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case UI_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case UI_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case UI_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case UI_R_REMAP_SHADER: + re.RemapShader((const char *)VMA(1), (const char *)VMA(2), (const char *)VMA(3)); + return 0; + + default: + Com_Error(ERR_DROP, "Bad UI system trap: %ld", (long int)args[0]); + } + + return 0; +} + +/* +==================== +CL_ShutdownUI +==================== +*/ +void CL_ShutdownUI(void) +{ + Key_SetCatcher(Key_GetCatcher() & ~KEYCATCH_UI); + cls.uiStarted = false; + if (!cls.ui) + { + return; + } + VM_Call(cls.ui, UI_SHUTDOWN); + VM_Free(cls.ui); + cls.ui = NULL; +} + +/* +==================== +CL_InitUI +==================== +*/ +void CL_InitUI(void) +{ + // load the dll or bytecode + vmInterpret_t interpret = (vmInterpret_t)Cvar_VariableValue("vm_ui"); + if (cl_connectedToPureServer) + { + // if sv_pure is set we only allow qvms to be loaded + if (interpret != VMI_COMPILED && interpret != VMI_BYTECODE) interpret = VMI_COMPILED; + } + + cls.ui = VM_Create("ui", CL_UISystemCalls, interpret); + if (!cls.ui) + { + Com_Printf("Failed to find a valid UI vm. The following paths were searched:\n"); + Cmd_ExecuteString("path /\n"); + Com_Error(ERR_RECONNECT, "VM_Create on UI failed"); + } + + // sanity check + int v = VM_Call(cls.ui, UI_GETAPIVERSION); + if (v != UI_API_VERSION) + { + // Free cls.ui now, so UI_SHUTDOWN doesn't get called later. + VM_Free(cls.ui); + cls.ui = NULL; + + cls.uiStarted = false; + Com_Error(ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION); + } + + // Probe UI interface + // Calls the GPP UI_CONSOLE_COMMAND (10), if GPP will return false 0. If a 1.1.0 qvm, will hit the error handler. + Cmd_TokenizeString(""); + cls.uiInterface = 0; + probingUI = true; + if ( VM_Call(cls.ui, UI_CONSOLE_COMMAND, 0) < 0 ) + cls.uiInterface = 2; + + probingUI = false; + + if (clc.state >= CA_CONNECTED && clc.state <= CA_ACTIVE && + (clc.netchan.alternateProtocol == 2) != (cls.uiInterface == 2)) + { + Com_Printf(S_COLOR_YELLOW "WARNING: %s protocol %i, but a ui module using the %s interface was found\n", + (clc.demoplaying ? "Demo was recorded using" : "Server uses"), + (clc.netchan.alternateProtocol == 0 ? PROTOCOL_VERSION : clc.netchan.alternateProtocol == 1 ? 70 : 69), + (cls.uiInterface == 2 ? "1.1" : "non-1.1")); + } + + // init for this gamestate + VM_Call(cls.ui, UI_INIT, (clc.state >= CA_AUTHORIZING && clc.state < CA_ACTIVE)); + + // show where the ui folder was loaded from + Cmd_ExecuteString("which ui/\n"); + + clc.newsString[0] = '\0'; +} + +/* +==================== +UI_GameCommand + +See if the current console command is claimed by the ui +==================== +*/ +bool UI_GameCommand(void) +{ + if (!cls.ui) return false; + + return (bool)VM_Call(cls.ui, UI_CONSOLE_COMMAND - (cls.uiInterface == 2 ? 2 : 0), cls.realtime); +} diff --git a/src/client/cl_updates.cpp b/src/client/cl_updates.cpp new file mode 100644 index 0000000..0e6a237 --- /dev/null +++ b/src/client/cl_updates.cpp @@ -0,0 +1,476 @@ +#include "cl_updates.h" + +#include +#include + +#include "nettle/rsa.h" +#include "nettle/sha2.h" +#include "rapidjson.h" +#include "restclient/connection.h" +#include "restclient/restclient.h" +#include "semantic_version.h" + +#include +#include +#include +#include + +#include "qcommon/cvar.h" +#include "qcommon/q_platform.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "qcommon/unzip.h" +#include "sys/sys_shared.h" + +#include "cl_rest.h" + +using namespace std; + +std::vector release_package; +static std::string granger_exe; +static std::string granger_main_lua; +static int nextCheckTime = 0; + +static const uint8_t release_key_pub[] = { + 0x28, 0x31, 0x30, 0x3a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, + 0x65, 0x79, 0x28, 0x39, 0x3a, 0x72, 0x73, 0x61, 0x2d, 0x70, 0x6b, 0x63, + 0x73, 0x31, 0x28, 0x31, 0x3a, 0x6e, 0x35, 0x31, 0x33, 0x3a, 0x00, 0xaa, + 0x4a, 0xe1, 0xdc, 0x08, 0xed, 0x90, 0x92, 0x66, 0x4a, 0xc2, 0x00, 0x43, + 0x3e, 0x89, 0x40, 0x4d, 0x11, 0xaa, 0x98, 0x2d, 0x55, 0x08, 0xec, 0x43, + 0x9f, 0x73, 0xfe, 0xc9, 0x67, 0xcb, 0xb0, 0x4a, 0x52, 0x89, 0x1d, 0x1a, + 0xc1, 0x86, 0x29, 0x26, 0x32, 0xb2, 0x34, 0xb6, 0xa5, 0x42, 0x16, 0x08, + 0x60, 0x8d, 0xf1, 0xe4, 0x45, 0xd4, 0x90, 0x2b, 0xfb, 0x98, 0xb2, 0x2e, + 0xa7, 0xa9, 0x79, 0xff, 0x5e, 0xaa, 0xb5, 0xd6, 0xf9, 0xf9, 0x2e, 0x67, + 0x5c, 0xb6, 0x6c, 0x36, 0x70, 0xf3, 0xea, 0x3f, 0xd8, 0xc8, 0xd0, 0xda, + 0xd8, 0xfb, 0x1b, 0x55, 0xe9, 0x69, 0x9d, 0x4a, 0xea, 0xf2, 0xb1, 0xdd, + 0x6b, 0xea, 0xb4, 0x99, 0xd7, 0x5e, 0xca, 0x5f, 0x34, 0xcf, 0xee, 0xbb, + 0xc6, 0x07, 0xa8, 0x3b, 0x59, 0xc3, 0xc1, 0x53, 0x15, 0x2b, 0xe4, 0x2f, + 0x17, 0x2d, 0x0c, 0x0f, 0x4e, 0xee, 0x2e, 0xef, 0x97, 0x9c, 0xff, 0x58, + 0xec, 0x6a, 0xf1, 0x12, 0x21, 0x5a, 0xc5, 0xbb, 0x6a, 0xef, 0xb0, 0xc8, + 0x4b, 0x7d, 0x33, 0xca, 0x53, 0x03, 0x31, 0x4a, 0xbd, 0x82, 0x01, 0x56, + 0x8f, 0x4e, 0x1d, 0x21, 0x7d, 0x2e, 0x8d, 0xaa, 0x0d, 0xcd, 0xe6, 0x19, + 0x09, 0x79, 0x76, 0x11, 0x16, 0xb5, 0xd2, 0xc1, 0x03, 0xdb, 0xaa, 0x11, + 0xd5, 0x89, 0x70, 0xc8, 0x71, 0xbf, 0x1a, 0xea, 0x9d, 0xd6, 0xaa, 0x77, + 0x97, 0xa4, 0xcc, 0x1d, 0xd6, 0x1c, 0x1a, 0x14, 0x6f, 0x35, 0x34, 0x6a, + 0x10, 0x32, 0x13, 0x33, 0x41, 0x27, 0xad, 0x1b, 0x59, 0x41, 0xff, 0xb0, + 0x1b, 0x64, 0x14, 0xea, 0x29, 0x44, 0x2b, 0x62, 0xdc, 0x63, 0xbc, 0xc7, + 0xf8, 0xac, 0xf7, 0x5c, 0x9d, 0x3c, 0xbd, 0x17, 0x84, 0xd9, 0x56, 0x09, + 0x44, 0x1b, 0xdd, 0x72, 0x16, 0xf6, 0x67, 0xc4, 0x49, 0xd3, 0x56, 0x74, + 0x42, 0x2d, 0x08, 0xe2, 0x0b, 0x0c, 0x40, 0x97, 0x62, 0x97, 0xb3, 0xd6, + 0x0c, 0x69, 0x04, 0x6a, 0xd7, 0x7b, 0xf0, 0xe1, 0x37, 0x0b, 0xe0, 0xee, + 0x6b, 0x2d, 0x79, 0xa0, 0x72, 0xa5, 0x75, 0x97, 0xb1, 0x6b, 0x01, 0xa2, + 0xb8, 0xe3, 0xc7, 0x3c, 0x10, 0x50, 0x59, 0xe4, 0xda, 0x9e, 0x8d, 0xe6, + 0x1b, 0xef, 0xa6, 0xae, 0xc4, 0xd7, 0xd8, 0x9b, 0x57, 0x4c, 0xa3, 0xd7, + 0x9f, 0x37, 0x17, 0x72, 0x7f, 0x60, 0xd6, 0x4c, 0x42, 0x14, 0x54, 0x66, + 0x88, 0xc6, 0xa2, 0x1e, 0xdf, 0x55, 0xfc, 0xef, 0xb9, 0x05, 0x63, 0x8a, + 0xc4, 0x5b, 0xe7, 0x45, 0x28, 0x0e, 0x55, 0x8e, 0xcc, 0x74, 0x25, 0xf2, + 0xea, 0x2a, 0x66, 0x51, 0xf2, 0x8f, 0x72, 0x1d, 0x97, 0x5a, 0xfb, 0x1c, + 0x95, 0x01, 0x93, 0x6b, 0x2b, 0xa5, 0x87, 0x5d, 0xd8, 0xea, 0xb2, 0x24, + 0x78, 0xb1, 0x58, 0x63, 0x47, 0x86, 0xed, 0xd9, 0xbc, 0xe9, 0xd2, 0xea, + 0xa8, 0x90, 0x5b, 0xe8, 0x82, 0x89, 0xa2, 0xe2, 0x52, 0x1f, 0x78, 0x00, + 0x93, 0x64, 0x54, 0xdb, 0x9b, 0x93, 0xc3, 0xee, 0xa2, 0x37, 0xab, 0x2e, + 0x7e, 0x8f, 0xec, 0xb1, 0x0b, 0x69, 0xad, 0x21, 0xda, 0xa9, 0xaf, 0xd2, + 0x22, 0x52, 0x2f, 0x1a, 0x6b, 0xb9, 0x21, 0x5e, 0xe9, 0x1a, 0xe1, 0x4c, + 0x33, 0x26, 0x46, 0xa1, 0xde, 0x52, 0xf3, 0x87, 0xf4, 0x8c, 0x99, 0xe4, + 0x5d, 0xe7, 0x5b, 0x76, 0x6c, 0xf5, 0xe1, 0xae, 0x5b, 0xaa, 0xbb, 0x3a, + 0xf4, 0x90, 0xa7, 0x5c, 0x5c, 0x72, 0xab, 0xb8, 0x71, 0xdc, 0x47, 0xb1, + 0x75, 0x0d, 0xc8, 0xcc, 0xfd, 0xce, 0x62, 0xbe, 0xe5, 0x9f, 0x85, 0x00, + 0x53, 0x35, 0x91, 0x44, 0x29, 0x5f, 0x64, 0x8d, 0x7c, 0x37, 0x32, 0x28, + 0x45, 0x9d, 0xa3, 0x49, 0xbb, 0xe3, 0xd3, 0x1e, 0x0a, 0xae, 0x53, 0xbd, + 0x66, 0x86, 0x8c, 0x4d, 0xa6, 0xd3, 0x85, 0x29, 0x28, 0x31, 0x3a, 0x65, + 0x33, 0x3a, 0x01, 0x00, 0x01, 0x29, 0x29, 0x29 +}; +static const size_t release_key_pub_len = 560; + +struct UpdateManager { + + static void refresh(); + static void download(); + static void validate_signature(std::string path, std::string signature_path); + static void extract(std::string extract_path, std::string path); + static void execute(); + +private: + static constexpr auto url = "https://api.github.com/repos/GrangerHub/tremulous/releases"; + static constexpr auto package_name = RELEASE_PACKAGE_NAME; + static constexpr auto signature_name = RELEASE_SIGNATURE_NAME; + static constexpr auto granger_binary_name = GRANGER_EXE; +}; + +#define AU_ACT_NIL 0 +#define AU_ACT_GET 1 +#define AU_ACT_RUN 2 + +void UpdateManager::refresh() +{ + auto currentTime = Sys_Milliseconds(); + + if ( nextCheckTime > currentTime ) + return; + + nextCheckTime = currentTime + 10000; + + Cvar_SetValue("ui_autoupdate_action", 0); + Cvar_Set("cl_latestDownload", ""); + Cvar_Set("cl_latestRelease", ""); + Cvar_Set("cl_latestSignature", ""); + + RestClient::Response r = RestClient::get(url); + if ( r.code != 200 ) + { + std::string msg { va("Server did not return OK status code: %d", r.code) }; + + Cvar_Set("cl_latestRelease", + S_COLOR_RED "ERROR:\n" + S_COLOR_WHITE "Server did not return OK status code"); + + return; + } + + rapidjson::Document d; + d.Parse( r.body.c_str() ); + + rapidjson::Value &release = d[0]; + std::string txt; + + semver::v2::Version current { PRODUCT_VERSION + 1 }; + semver::v2::Version latest { release["tag_name"].GetString() + 1 }; + + if ( current == latest ) + { + txt += "You are up to date\n\n"; + } + else if ( current < latest ) + { + txt += "A new release is available!\n\n"; + } + else if ( current > latest ) + { + txt += "Wow! You are ahead of the release.\n\n"; + } + + txt += release["tag_name"].GetString(); + if (release["prerelease"].IsTrue()) + txt += S_COLOR_YELLOW " (Prerelease)"; + + txt += '\n'; + txt += S_COLOR_CYAN "Released:" S_COLOR_WHITE; + txt += release["published_at"].GetString(); + + txt += '\n'; + txt += S_COLOR_RED "Release Notes:\n" S_COLOR_WHITE; + + if (!release["body"].IsNull()) + { + txt += '\n'; + txt += release["body"].GetString(); + } + + rapidjson::Value &assets = release["assets"]; + for ( rapidjson::SizeType i = 0; i < assets.Size(); ++i ) + { + auto &a = assets[i]; + std::string name = a["name"].GetString(); + + if ( name == package_name ) + { + std::string dl = a["browser_download_url"].GetString(); + Cvar_Set("cl_latestDownload", dl.c_str()); + Cvar_Set("cl_latestPackage", package_name); + Cvar_Set("cl_latestRelease", txt.c_str()); + Cvar_SetValue("ui_autoupdate_action", AU_ACT_GET); + } + else if ( name == signature_name ) + { + std::string dl = a["browser_download_url"].GetString(); + Cvar_Set("cl_latestSignature", dl.c_str()); + } + } +} + +void UpdateManager::extract(std::string extract_path, std::string path) +{ + // Extract the release package + auto z = unzOpen(path.c_str()); + assert( z != nullptr ); + + unz_global_info zi; + unzGetGlobalInfo(z, &zi); + + // Iterate through all files in the package + unzGoToFirstFile(z); + for (int i = 0; i < zi.number_entry; ++i) + { + unz_file_info fi; + char filename[256] = ""; // 256 == MAX_ZPATH + int err; + + err = unzGetCurrentFileInfo(z, &fi, filename, sizeof(filename), + nullptr, 0, nullptr, 0); + assert( err == UNZ_OK ); // != OK means corrupt archive + + std::string fn = { filename }; + + // FIXME stop doing dumb string stuff + std::string fullpath = extract_path; + fullpath += PATH_SEP; + fullpath += fn; + release_package.emplace_back(fn); + + // Bad assumption of a directory? + if ( !fi.compressed_size && !fi.uncompressed_size ) + { + Com_DPrintf(S_COLOR_CYAN"ARCHIVED DIR: " + S_COLOR_WHITE"%s\n", + fn.c_str()); + + MakeDir(extract_path, fn); + + unzGoToNextFile(z); + continue; + } + + // Must be a file + Com_DPrintf(S_COLOR_CYAN"ARCHIVED FILE: " + S_COLOR_WHITE"%s (%lu/%lu)\n", + filename, + fi.compressed_size, + fi.uncompressed_size); + + if ( fn.rfind(granger_binary_name) != std::string::npos ) + granger_exe = fn; + + else if ( fn.rfind("main.lua") != std::string::npos ) + granger_main_lua = fn; + + err = unzOpenCurrentFile(z); + assert( err == UNZ_OK ); + + // FIXME cleanup all this shit string stuff + std::string path = extract_path; + path += PATH_SEP; + path += fn; + + Com_DPrintf(S_COLOR_YELLOW"Extracted FILE: " + S_COLOR_WHITE"%s\n", + path.c_str()); + + std::fstream dl; + dl.open(path, fstream::out|ios::binary); + + // Extract the release package + size_t numwrote; + do { + // Extract 16k at a time + unsigned blocksiz = 16384; + + if ( blocksiz > fi.uncompressed_size - numwrote ) + blocksiz = fi.uncompressed_size - numwrote; + + uint8_t block[blocksiz]; + unzReadCurrentFile(z, static_cast(&block), blocksiz); + + dl.write((const char*)block, blocksiz); + numwrote += blocksiz; + + } while ( fi.uncompressed_size > numwrote ); + dl.close(); + + unzCloseCurrentFile(z); + unzGoToNextFile(z); + } + + unzClose(z); +} + +void UpdateManager::download() +{ + cvar_t *cl_enableSignatureCheck = Cvar_Get("cl_enableSignatureCheck", "0", CVAR_ARCHIVE | CVAR_PROTECTED); + + // Check for and download signature + std::string signature_url = Cvar_VariableString("cl_latestSignature"); + if ( signature_url.empty() && cl_enableSignatureCheck->integer) + throw exception(); + + // Download the latest release + auto url = Cvar_VariableString("cl_latestDownload"); + auto r = RestClient::get(url); + if ( r.code != 200 ) + throw exception(); + + Com_DPrintf(S_COLOR_CYAN "URL: " S_COLOR_WHITE "%s\n", url); + + // FIXME Cleanup this string bullshit + std::string extract_path, signature_path, path { Cvar_VariableString("fs_homepath") }; + path += PATH_SEP; + extract_path = path; + signature_path = path + signature_name; + path += package_name; + + MakeDir(extract_path, "extract"); + extract_path += "extract"; + + Com_DPrintf(S_COLOR_CYAN"PATH: " S_COLOR_WHITE "%s\n", + path.c_str()); + + // Write the release package to disk + { + std::fstream dl; + dl.open( path, fstream::out|ios::binary ); + dl.write( r.body.c_str(), r.body.length() ); + dl.close(); + } + + if (cl_enableSignatureCheck->integer) + { + auto r = RestClient::get(signature_url); + if ( r.code != 200 ) + throw exception(); + + std::fstream dl; + dl.open( signature_path, fstream::out|ios::binary ); + dl.write( r.body.c_str(), r.body.length() ); + dl.close(); + + // Validate the signature of the package if enabled + UpdateManager::validate_signature(path, signature_path); + } + + // Extract the contents of the release package + UpdateManager::extract(extract_path, path); + + // Delete the release package + unlink(path.c_str()); + + Cvar_SetValue("au_autoupdate_action", AU_ACT_RUN); +} + +void UpdateManager::validate_signature(std::string path, std::string signature_path) +{ + // Load public key + rsa_public_key public_key; + rsa_public_key_init(&public_key); + rsa_keypair_from_sexp(&public_key, NULL, 0, release_key_pub_len, release_key_pub); + + auto min = [](auto a, auto b) { return a < b ? a : b; }; + + // Read in signature + mpz_t signature; + { + std::ifstream f(signature_path, ios::binary); + f.seekg (0, f.end); + size_t length = f.tellg(); + f.seekg (0, f.beg); + + std::vector buffer(512, 0); + f.read(buffer.data(), min(length, buffer.size())); + nettle_mpz_init_set_str_256_u(signature, f.gcount(), (uint8_t *)buffer.data()); + } + + // Hash file + sha256_ctx ctx; + sha256_init(&ctx); + { + std::ifstream f(path, ios::binary); + f.seekg (0, f.end); + size_t length = f.tellg(); + f.seekg (0, f.beg); + + std::vector buffer(16384, 0); + while (f.read((char *)buffer.data(), min(length, buffer.size()))) { + auto nbytes = f.gcount(); + sha256_update(&ctx, nbytes, buffer.data()); + length -= nbytes; + if (length <= 0) { + break; + } + } + } + + // Verify signature + if (!rsa_sha256_verify(&public_key, &ctx, signature)) { + rsa_public_key_clear(&public_key); + mpz_clear(signature); + Com_Error( ERR_DROP, "Update signature was not verified\n" ); + return; + } + + unlink(signature_path.c_str()); + rsa_public_key_clear(&public_key); + mpz_clear(signature); +} + +extern char** environ; + +class FailInstaller : public std::exception { + std::string msg; +public: + FailInstaller(int e) + { msg = strerror(e); } + + virtual const char* what() throw() + { return msg.c_str(); } +}; + +void UpdateManager::execute() +{ + granger_exe = ""; + granger_main_lua = ""; + + if ( granger_exe == "" || granger_main_lua == "" ) + { + for ( auto const& i : release_package ) + { + Com_Printf(S_COLOR_RED " Unlink %s\n", i.c_str()); + unlink(i.c_str()); + } + + Com_Error( ERR_DROP, "Missing Granger or GrangerScript\n" ); + return; + } + + std::array argv{}; + + char* tmp = strdup(granger_exe.c_str()); + const char* dir = dirname(tmp); + + argv[0] = granger_exe.c_str(); + argv[1] = va(" -C %s", dir); + argv[2] = granger_main_lua.c_str(); + + // Dump the details + Com_Printf(S_COLOR_YELLOW"Launching " + S_COLOR_WHITE"%s %s %s\n", + argv[0], argv[1], argv[2]); + +#ifndef _WIN32 + // Fork solely to try cleanup SDL2/GL states + auto pid = fork(); + if (pid == -1) + throw FailInstaller(errno); + + if (pid == 0) + { + execve(argv[0], + const_cast(argv.data()), + environ); + + throw FailInstaller(errno); + } + else + { + Engine_Exit(""); + } +#else + // FIXME Dirty exit on Windows... + execve(argv[0], + const_cast(argv.data()), + environ); + + throw FailInstaller(errno); +#endif +} + +void CL_GetLatestRelease() { UpdateManager::refresh(); } +void CL_DownloadRelease() { UpdateManager::download(); } +void CL_ExecuteInstaller() { UpdateManager::execute(); } diff --git a/src/client/cl_updates.h b/src/client/cl_updates.h new file mode 100644 index 0000000..f85c64a --- /dev/null +++ b/src/client/cl_updates.h @@ -0,0 +1,8 @@ +#ifndef CL_UPDATES +#define CL_UPDATES + +void CL_GetLatestRelease(); +void CL_DownloadRelease(); +void CL_ExecuteInstaller(); + +#endif diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000..2474a3d --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,716 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// client.h -- primary header for client + +#ifndef _CLIENT_H_ +#define _CLIENT_H_ + +#ifdef USE_VOIP +#include +#endif + +#include "cgame/cg_public.h" +#include "qcommon/alternatePlayerstate.h" +#include "qcommon/cmd.h" +#include "qcommon/crypto.h" +#include "qcommon/cvar.h" +#include "qcommon/files.h" +#include "qcommon/huffman.h" +#include "qcommon/msg.h" +#include "qcommon/net.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "qcommon/vm.h" +#include "renderercommon/tr_public.h" +#include "sys/sys_shared.h" +#include "ui/ui_public.h" + +#include "cl_curl.h" +#include "keys.h" +#include "snd_public.h" + +struct alternateEntityState_t { + int number; // entity index + int eType; // entityType_t + int eFlags; + + trajectory_t pos; // for calculating position + trajectory_t apos; // for calculating angles + + int time; + int time2; + + vec3_t origin; + vec3_t origin2; + + vec3_t angles; + vec3_t angles2; + + int otherEntityNum; // shotgun sources, etc + int otherEntityNum2; + + int groundEntityNum; // ENTITYNUM_NONE = in air + + int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24) + int loopSound; // constantly loop this sound + + int modelindex; + int modelindex2; + int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses + int frame; + + int solid; // for client side prediction, trap_linkentity sets this properly + + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; + + // for players + int misc; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT + + int generic1; +}; + +struct alternateSnapshot_t { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + alternatePlayerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + alternateEntityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +}; + +// file full of random crap that gets used to create cl_guid +#define QKEY_FILE "qkey" +#define QKEY_SIZE 2048 + +#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits + +// snapshots are a view of the server at a given time +struct clSnapshot_t { + bool valid; // cleared if delta parsing was invalid + int snapFlags; // rate delayed and dropped commands + + int serverTime; // server time the message is valid for (in msec) + + int messageNum; // copied from netchan->incoming_sequence + int deltaNum; // messageNum the delta is from + int ping; // time from when cmdNum-1 was sent to time packet was reeceived + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + int cmdNum; // the next cmdNum the server is expecting + playerState_t ps; // complete information about the current player at this time + alternatePlayerState_t alternatePs; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + int parseEntitiesNum; // at the time of this snapshot + + int serverCommandNum; // execute all commands up to this before + // making the snapshot current +}; + +/* +============================================================================= + +the clientActive_t structure is wiped completely at every +new gamestate_t, potentially several times during an established connection + +============================================================================= +*/ + +struct outPacket_t { + int p_cmdNumber; // cl.cmdNumber when packet was sent + int p_serverTime; // usercmd->serverTime when packet was sent + int p_realtime; // cls.realtime when packet was sent +}; + +// the parseEntities array must be large enough to hold PACKET_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 (PACKET_BACKUP * MAX_SNAPSHOT_ENTITIES) + +extern int g_console_field_width; + +struct clientActive_t { + int timeoutcount; // it requres several frames in a timeout condition + // to disconnect, preventing debugging breaks from + // causing immediate disconnects on continue + clSnapshot_t snap; // latest received from server + + int serverTime; // may be paused during play + int oldServerTime; // to prevent time from flowing bakcwards + int oldFrameServerTime; // to check tournament restarts + int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta + // this value changes as net lag varies + bool extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate + // cleared when CL_AdjustTimeDelta looks at it + bool newSnapshots; // set on parse of any valid packet + + gameState_t gameState; // configstrings + char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO + + int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] + + int mouseDx[2], mouseDy[2]; // added to by mouse events + int mouseIndex; + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + // cgame communicates a few values to the client system + int cgameUserCmdValue; // current weapon to add to usercmd_t + float cgameSensitivity; + + // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last + // properly generated command + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmdNumber; // incremented each frame, because multiple + // frames may need to be packed into a single packet + + outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out + + // 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 serverId; // included in each client message so the server + // can tell if it is for a prior map_restart + // big stuff at end of structure so most offsets are 15 bits or less + clSnapshot_t snapshots[PACKET_BACKUP]; + + entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame + + entityState_t parseEntities[MAX_PARSE_ENTITIES]; +}; + +extern clientActive_t cl; + +/* +============================================================================= + +the clientConnection_t structure is wiped when disconnecting from a server, +either to go to a full screen console, play a demo, or connect to a different server + +A connection can be to either a server through the network layer or a +demo through a file. + +============================================================================= +*/ + +#define MAX_TIMEDEMO_DURATIONS 4096 + +struct clientConnection_t { + connstate_t state; // connection status + + int clientNum; + int lastPacketSentTime; // for retransmits during connection + int lastPacketTime; // for timeouts + + char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) + netadr_t serverAddress; + int connectTime; // for connection retransmits + int connectPacketCount; // for display on connection dialog + char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog + + int challenge; // from the server to use for connecting + char challenge2[33]; + bool sendSignature; + int checksumFeed; // from the server for checksum calculations + + // these are our reliable messages that go to the server + int reliableSequence; + int reliableAcknowledge; // the last one the server has executed + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // server message (unreliable) and command (reliable) sequence + // numbers are NOT cleared at level changes, but continue to + // increase as long as the connection is valid + + // message sequence is used by both the network layer and the + // delta compression layer + int serverMessageSequence; + + // reliable messages received from server + int serverCommandSequence; + int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand + char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // file transfer from server + fileHandle_t download; + char downloadTempName[MAX_OSPATH]; + char downloadName[MAX_OSPATH]; + + // XXX Refactor this -vjr + bool cURLEnabled; + bool cURLUsed; + bool cURLDisconnected; + + char downloadURL[MAX_OSPATH]; + CURL *downloadCURL; + CURLM *downloadCURLM; + bool activeCURLNotGameRelated; + + int sv_allowDownload; + char sv_dlURL[MAX_CVAR_VALUE_STRING]; + int downloadNumber; + int downloadBlock; // block we are waiting for + int downloadCount; // how many bytes we got + int downloadSize; // how many bytes we got + char downloadList[MAX_INFO_STRING]; // list of paks we need to download + bool downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + char newsString[MAX_NEWS_STRING]; + + // demo information + char demoName[MAX_QPATH]; + bool spDemoRecording; + bool demorecording; + bool demoplaying; + bool demowaiting; // don't record until a non-delta message is received + bool firstDemoFrameSkipped; + fileHandle_t demofile; + + int timeDemoFrames; // counter of rendered frames + int timeDemoStart; // cls.realtime before first frame + int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 + int timeDemoLastFrame; // time the last frame was rendered + int timeDemoMinDuration; // minimum frame duration + int timeDemoMaxDuration; // maximum frame duration + unsigned char timeDemoDurations[MAX_TIMEDEMO_DURATIONS]; // log of frame durations + + float aviVideoFrameRemainder; + float aviSoundFrameRemainder; + +#ifdef USE_VOIP + bool voipEnabled; + bool voipCodecInitialized; + + // incoming data... + // !!! FIXME: convert from parallel arrays to array of a struct. + OpusDecoder *opusDecoder[MAX_CLIENTS]; + byte voipIncomingGeneration[MAX_CLIENTS]; + int voipIncomingSequence[MAX_CLIENTS]; + float voipGain[MAX_CLIENTS]; + bool voipIgnore[MAX_CLIENTS]; + bool voipMuteAll; + + // outgoing data... + // if voipTargets[i / 8] & (1 << (i % 8)), + // then we are sending to clientnum i. + uint8_t voipTargets[(MAX_CLIENTS + 7) / 8]; + uint8_t voipFlags; + OpusEncoder *opusEncoder; + int voipOutgoingDataSize; + int voipOutgoingDataFrames; + int voipOutgoingSequence; + byte voipOutgoingGeneration; + byte voipOutgoingData[1024]; + float voipPower; +#endif + + // big stuff at end of structure so most offsets are 15 bits or less + netchan_t netchan; +}; + +extern clientConnection_t clc; + +/* +================================================================== + +the clientStatic_t structure is never wiped, and is used even when +no client connection is active at all +(except when CL_Shutdown is called) + +================================================================== +*/ + +struct ping_t { + netadr_t adr; + int start; + int time; + char info[MAX_INFO_STRING]; +}; + +#define MAX_FEATLABEL_CHARS 32 +struct serverInfo_t { + netadr_t adr; + char hostName[MAX_HOSTNAME_LENGTH]; + char mapName[MAX_NAME_LENGTH]; + char game[MAX_NAME_LENGTH]; + char label[MAX_FEATLABEL_CHARS]; // for featured servers, NULL otherwise + int netType; + int gameType; + int clients; + int maxClients; + int minPing; + int maxPing; + int ping; + bool visible; +}; + +struct clientStatic_t { + // when the server clears the hunk, all of these must be restarted + bool rendererStarted; + bool soundStarted; + bool soundRegistered; + bool uiStarted; + bool cgameStarted; + + int framecount; + int frametime; // msec since last frame + + int realtime; // ignores pause + int realFrametime; // ignoring pause, so console always works + + // master server sequence information + int numAlternateMasterPackets[3]; + unsigned int receivedAlternateMasterPackets[3]; // bitfield + + int numlocalservers; + serverInfo_t localServers[MAX_OTHER_SERVERS]; + + int numglobalservers; + serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; + // additional global servers + int numGlobalServerAddresses; + netadr_t globalServerAddresses[MAX_GLOBAL_SERVERS]; + + int numfavoriteservers; + serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; + + int pingUpdateSource; // source currently pinging or updating + + // update server info + netadr_t updateServer; + char updateChallenge[MAX_TOKEN_CHARS]; + char updateInfoString[MAX_INFO_STRING]; + + netadr_t authorizeServer; + + // rendering info + glconfig_t glconfig; + qhandle_t charSetShader; + qhandle_t whiteShader; + qhandle_t consoleShader; + + vm_t *cgame; + int cgInterface; // 0 == gpp, 2 == 1.1.0 + + vm_t *ui; + int uiInterface; + + struct { + struct rsa_public_key public_key; + struct rsa_private_key private_key; + } rsa; +}; + +extern clientStatic_t cls; + +extern char cl_oldGame[MAX_QPATH]; +extern bool cl_oldGameSet; + +//============================================================================= + +extern refexport_t re; // interface to refresh .dll + +// +// cvars +// +extern cvar_t *cl_nodelta; +extern cvar_t *cl_debugMove; +extern cvar_t *cl_noprint; +extern cvar_t *cl_timegraph; +extern cvar_t *cl_maxpackets; +extern cvar_t *cl_packetdup; +extern cvar_t *cl_shownet; +extern cvar_t *cl_showSend; +extern cvar_t *cl_timeNudge; +extern cvar_t *cl_showTimeDelta; +extern cvar_t *cl_freezeDemo; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_run; +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_sensitivity; +extern cvar_t *cl_freelook; + +extern cvar_t *cl_mouseAccel; +extern cvar_t *cl_mouseAccelOffset; +extern cvar_t *cl_mouseAccelStyle; +extern cvar_t *cl_showMouseRate; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; +extern cvar_t *m_filter; + +extern cvar_t *j_pitch; +extern cvar_t *j_yaw; +extern cvar_t *j_forward; +extern cvar_t *j_side; +extern cvar_t *j_up; +extern cvar_t *j_pitch_axis; +extern cvar_t *j_yaw_axis; +extern cvar_t *j_forward_axis; +extern cvar_t *j_side_axis; +extern cvar_t *j_up_axis; + +extern cvar_t *cl_timedemo; +extern cvar_t *cl_aviFrameRate; +extern cvar_t *cl_aviMotionJpeg; + +extern cvar_t *cl_activeAction; + +extern cvar_t *cl_allowDownload; +extern cvar_t *cl_downloadMethod; +extern cvar_t *cl_conXOffset; +extern cvar_t *cl_inGameVideo; + +extern cvar_t *cl_lanForcePackets; +extern cvar_t *cl_autoRecordDemo; + +extern cvar_t *cl_clantag; + +extern cvar_t *cl_consoleKeys; + +#ifdef USE_MUMBLE +extern cvar_t *cl_useMumble; +extern cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +// cl_voipSendTarget is a string: "all" to broadcast to everyone, "none" to +// send to no one, or a comma-separated list of client numbers: +// "0,7,2,23" ... an empty string is treated like "all". +extern cvar_t *cl_voipUseVAD; +extern cvar_t *cl_voipVADThreshold; +extern cvar_t *cl_voipSend; +extern cvar_t *cl_voipSendTarget; +extern cvar_t *cl_voipGainDuringCapture; +extern cvar_t *cl_voipCaptureMult; +extern cvar_t *cl_voipShowMeter; +extern cvar_t *cl_voip; + +// 20ms at 48k +#define VOIP_MAX_FRAME_SAMPLES (20 * 48) + +// 3 frame is 60ms of audio, the max opus will encode at once +#define VOIP_MAX_PACKET_FRAMES 3 +#define VOIP_MAX_PACKET_SAMPLES (VOIP_MAX_FRAME_SAMPLES * VOIP_MAX_PACKET_FRAMES) +#endif + +extern cvar_t *cl_rsaAuth; + +//================================================= + +// +// cl_main +// + +void CL_Init(void); +void CL_AddReliableCommand(const char *cmd, bool isDisconnectCmd); + +void CL_StartHunkUsers(bool rendererOnly); + +void CL_Disconnect_f(void); +void CL_NextDemo(void); +void CL_ReadDemoMessage(void); +demoState_t CL_DemoState(void); +int CL_DemoPos(void); +void CL_DemoName(char *buffer, int size); +void CL_StopRecord_f(void); + +void CL_InitDownloads(void); +void CL_NextDownload(void); + +void CL_GetPing(int n, char *buf, int buflen, int *pingtime); +void CL_GetPingInfo(int n, char *buf, int buflen); +void CL_ClearPing(int n); +int CL_GetPingQueueCount(void); + +bool CL_ServerStatus(char *serverAddress, char *serverStatusString, int maxLen); + +bool CL_CheckPaused(void); + +// +// cl_input +// +typedef struct { + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame if both a down and up happened + bool active; // current state + bool wasPressed; // set when down, not cleared when up +} kbutton_t; + +void CL_InitInput(void); +void CL_ShutdownInput(void); +void CL_SendCmd(void); +void CL_ClearState(void); +void CL_ReadPackets(void); + +void CL_WritePacket(void); +//void IN_CenterView(void); + +int Key_StringToKeynum(const char *str); +const char *Key_KeynumToString(int keynum); + +// +// cl_parse.c +// +extern bool cl_connectedToPureServer; +extern bool cl_connectedToCheatServer; + +#ifdef USE_VOIP +void CL_Voip_f(void); +#endif + +void CL_SystemInfoChanged(void); +void CL_ParseServerMessage(msg_t *msg); + +//==================================================================== + +bool CL_UpdateVisiblePings_f(int source); + +// +// console +// +void Con_DrawCharacter(int cx, int line, int num); + +void Con_CheckResize(void); +void Con_MessageModesInit(void); +void CL_ProtocolSpecificCommandsInit(void); +void Con_Init(void); +void Con_Shutdown(void); +void Con_Clear_f(void); +void Con_ToggleConsole_f(void); +void Con_ClearNotify(void); +void Con_RunConsole(void); +void Con_DrawConsole(void); +void Con_PageUp(void); +void Con_PageDown(void); +void Con_Top(void); +void Con_Bottom(void); +void Con_Close(void); + +void CL_LoadConsoleHistory(void); +void CL_SaveConsoleHistory(void); + +// +// cl_scrn.c +// +void SCR_Init(void); +void SCR_UpdateScreen(void); + +void SCR_DebugGraph(float value); + +int SCR_GetBigStringWidth(const char *str); // returns in virtual 640x480 coordinates + +void SCR_AdjustFrom640(float *x, float *y, float *w, float *h); +void SCR_FillRect(float x, float y, float width, float height, const float *color); +void SCR_DrawPic(float x, float y, float width, float height, qhandle_t hShader); +void SCR_DrawNamedPic(float x, float y, float width, float height, const char *picname); + +void SCR_DrawBigString(int x, int y, const char *s, float alpha, + bool noColorEscape); // draws a string with embedded color control characters with fade +void SCR_DrawBigStringColor( + int x, int y, const char *s, vec4_t color, bool noColorEscape); // ignores embedded color control characters +void SCR_DrawSmallStringExt(int x, int y, const char *string, float *setColor, bool forceColor, bool noColorEscape); +void SCR_DrawSmallChar(int x, int y, int ch); + +// +// cl_cin.c +// + +void CL_PlayCinematic_f(void); +void SCR_DrawCinematic(void); +void SCR_RunCinematic(void); +void SCR_StopCinematic(void); +int CIN_PlayCinematic(const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status CIN_StopCinematic(int handle); +e_status CIN_RunCinematic(int handle); +void CIN_DrawCinematic(int handle); +void CIN_SetExtents(int handle, int x, int y, int w, int h); +void CIN_UploadCinematic(int handle); +void CIN_CloseAllVideos(void); + +// +// cl_cgame.c +// +void CL_InitCGame(void); +void CL_ShutdownCGame(void); +bool CL_GameCommand(void); +void CL_GameConsoleText(void); +void CL_CGameRendering(stereoFrame_t stereo); +void CL_SetCGameTime(void); +void CL_FirstSnapshot(void); +void CL_ShaderStateChanged(void); + +// +// cl_ui.c +// +void CL_InitUI(void); +void CL_ShutdownUI(void); +int Key_GetCatcher(void); +void Key_SetCatcher(int catcher); +void LAN_LoadCachedServers(void); +void LAN_SaveServersToCache(void); + +// +// cl_net_chan.c +// +void CL_Netchan_Transmit(netchan_t *chan, msg_t *msg); // int length, const byte *data ); +bool CL_Netchan_Process(netchan_t *chan, msg_t *msg); + +// +// cl_avi.c +// +bool CL_OpenAVIForWriting(const char *filename); +void CL_TakeVideoFrame(void); +void CL_WriteAVIVideoFrame(const byte *imageBuffer, int size); +void CL_WriteAVIAudioFrame(const byte *pcmBuffer, int size); +bool CL_CloseAVI(void); +bool CL_VideoRecording(void); + +// +// cl_main.c +// +void CL_WriteDemoMessage(msg_t *msg, int headerBytes); +int CL_ScaledMilliseconds(void); + +#endif diff --git a/src/client/keycodes.h b/src/client/keycodes.h index ae6f189..3b2958d 100644 --- a/src/client/keycodes.h +++ b/src/client/keycodes.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // @@ -262,6 +263,39 @@ typedef enum { K_EURO, K_UNDO, + // Gamepad controls + // Ordered to match SDL2 game controller buttons and axes + // Do not change this order without also changing IN_GamepadMove() in SDL_input.c + K_PAD0_A, + K_PAD0_B, + K_PAD0_X, + K_PAD0_Y, + K_PAD0_BACK, + K_PAD0_GUIDE, + K_PAD0_START, + K_PAD0_LEFTSTICK_CLICK, + K_PAD0_RIGHTSTICK_CLICK, + K_PAD0_LEFTSHOULDER, + K_PAD0_RIGHTSHOULDER, + K_PAD0_DPAD_UP, + K_PAD0_DPAD_DOWN, + K_PAD0_DPAD_LEFT, + K_PAD0_DPAD_RIGHT, + + K_PAD0_LEFTSTICK_LEFT, + K_PAD0_LEFTSTICK_RIGHT, + K_PAD0_LEFTSTICK_UP, + K_PAD0_LEFTSTICK_DOWN, + K_PAD0_RIGHTSTICK_LEFT, + K_PAD0_RIGHTSTICK_RIGHT, + K_PAD0_RIGHTSTICK_UP, + K_PAD0_RIGHTSTICK_DOWN, + K_PAD0_LEFTTRIGGER, + K_PAD0_RIGHTTRIGGER, + + // Pseudo-key that brings the console down + K_CONSOLE, + MAX_KEYS } keyNum_t; diff --git a/src/client/keys.h b/src/client/keys.h new file mode 100644 index 0000000..5c8877f --- /dev/null +++ b/src/client/keys.h @@ -0,0 +1,66 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef _KEYS_H_ +#define _KEYS_H_ + +#include "keycodes.h" + +typedef struct { + bool down; + int repeats; // if > 1, it is autorepeating + char *binding; +} qkey_t; + +extern bool key_overstrikeMode; +extern qkey_t keys[MAX_KEYS]; + +// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h +void Field_KeyDownEvent(field_t *edit, int key); +void Field_CharEvent(field_t *edit, int ch); +void Field_Draw(field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape); +void Field_BigDraw(field_t *edit, int x, int y, int width, bool showCursor, bool noColorEscape); + +#define COMMAND_HISTORY 32 +extern field_t historyEditLines[COMMAND_HISTORY]; + +extern field_t g_consoleField; +extern field_t chatField; +extern int anykeydown; +extern bool chat_team; +extern bool chat_admins; +extern bool chat_clans; +extern int chat_playerNum; + +void Key_WriteBindings(fileHandle_t f); +void Key_SetBinding(int keynum, const char *binding); +const char *Key_GetBinding(int keynum); +bool Key_IsDown(int keynum); +bool Key_GetOverstrikeMode(void); +void Key_SetOverstrikeMode(bool state); +void Key_ClearStates(void); +int Key_GetKey(const char *binding); +void Key_KeynumToStringBuf(int keynum, char *buf, int buflen); +void Key_GetBindingBuf(int keynum, char *buf, int buflen); + +#endif diff --git a/src/client/libmumblelink.cpp b/src/client/libmumblelink.cpp new file mode 100644 index 0000000..59d59d3 --- /dev/null +++ b/src/client/libmumblelink.cpp @@ -0,0 +1,190 @@ +/* libmumblelink.c -- mumble link interface + + Copyright (C) 2008 Ludwig Nussel + Copyright (C) 2000-2013 Darklegion Development + Copyright (C) 2015-2019 GrangerHub + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#include "libmumblelink.h" + +#include +#ifdef _WIN32 +#include +#define uint32_t UINT32 +#else +#include +#ifdef __sun +#define _POSIX_C_SOURCE 199309L +#endif +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a, b) ((a)<(b)?(a):(b)) +#endif + +typedef struct +{ + uint32_t uiVersion; + uint32_t uiTick; + float fAvatarPosition[3]; + float fAvatarFront[3]; + float fAvatarTop[3]; + wchar_t name[256]; + /* new in mumble 1.2 */ + float fCameraPosition[3]; + float fCameraFront[3]; + float fCameraTop[3]; + wchar_t identity[256]; + uint32_t context_len; + unsigned char context[256]; + wchar_t description[2048]; +} LinkedMem; + +static LinkedMem *lm = NULL; + +#ifdef WIN32 +static HANDLE hMapObject = NULL; +#else +static int32_t GetTickCount(void) +{ + struct timeval tv; + gettimeofday(&tv,NULL); + + return tv.tv_usec / 1000 + tv.tv_sec * 1000; +} +#endif + +int mumble_link(const char* name) +{ +#ifdef WIN32 + if(lm) + return 0; + + hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink"); + if (hMapObject == NULL) + return -1; + + lm = (LinkedMem *) MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem)); + if (lm == NULL) { + CloseHandle(hMapObject); + hMapObject = NULL; + return -1; + } +#else + char file[256]; + int shmfd; + if(lm) + return 0; + + snprintf(file, sizeof (file), "/MumbleLink.%d", getuid()); + shmfd = shm_open(file, O_RDWR, S_IRUSR | S_IWUSR); + if(shmfd < 0) { + return -1; + } + + lm = (LinkedMem *) (mmap(NULL, sizeof(LinkedMem), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd,0)); + if (lm == (void *) (-1)) { + lm = NULL; + close(shmfd); + return -1; + } + close(shmfd); +#endif + memset(lm, 0, sizeof(LinkedMem)); + mbstowcs(lm->name, name, sizeof(lm->name) / sizeof(wchar_t)); + + return 0; +} + +void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]) +{ + mumble_update_coordinates2(fPosition, fFront, fTop, fPosition, fFront, fTop); +} + +void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3], + float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]) +{ + if (!lm) + return; + + memcpy(lm->fAvatarPosition, fAvatarPosition, sizeof(lm->fAvatarPosition)); + memcpy(lm->fAvatarFront, fAvatarFront, sizeof(lm->fAvatarFront)); + memcpy(lm->fAvatarTop, fAvatarTop, sizeof(lm->fAvatarTop)); + memcpy(lm->fCameraPosition, fCameraPosition, sizeof(lm->fCameraPosition)); + memcpy(lm->fCameraFront, fCameraFront, sizeof(lm->fCameraFront)); + memcpy(lm->fCameraTop, fCameraTop, sizeof(lm->fCameraTop)); + lm->uiVersion = 2; + lm->uiTick = GetTickCount(); +} + +void mumble_set_identity(const char* identity) +{ + size_t len; + if (!lm) + return; + len = MIN(sizeof(lm->identity)/sizeof(wchar_t), strlen(identity)+1); + mbstowcs(lm->identity, identity, len); +} + +void mumble_set_context(const unsigned char* context, size_t len) +{ + if (!lm) + return; + len = MIN(sizeof(lm->context), len); + lm->context_len = len; + memcpy(lm->context, context, len); +} + +void mumble_set_description(const char* description) +{ + size_t len; + if (!lm) + return; + len = MIN(sizeof(lm->description)/sizeof(wchar_t), strlen(description)+1); + mbstowcs(lm->description, description, len); +} + +void mumble_unlink() +{ + if(!lm) + return; +#ifdef WIN32 + UnmapViewOfFile(lm); + CloseHandle(hMapObject); + hMapObject = NULL; +#else + munmap(lm, sizeof(LinkedMem)); +#endif + lm = NULL; +} + +int mumble_islinked(void) +{ + return lm != NULL; +} diff --git a/src/client/libmumblelink.h b/src/client/libmumblelink.h new file mode 100644 index 0000000..aa8e2ce --- /dev/null +++ b/src/client/libmumblelink.h @@ -0,0 +1,41 @@ +/* libmumblelink.h -- mumble link interface + + Copyright (C) 2008 Ludwig Nussel + Copyright (C) 2000-2013 Darklegion Development + Copyright (C) 2015-2019 GrangerHub + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ +#ifndef _LIBMUMBLELINK_H_ +#define _LIBMUMBLELINK_H_ + +int mumble_link(const char* name); +int mumble_islinked(void); +void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]); + +/* new for mumble 1.2: also set camera position */ +void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3], + float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]); + +void mumble_set_description(const char* description); +void mumble_set_context(const unsigned char* context, size_t len); +void mumble_set_identity(const char* identity); + +void mumble_unlink(void); + +#endif // _LIBMUMBLELINK_H_ diff --git a/src/client/qal.cpp b/src/client/qal.cpp new file mode 100644 index 0000000..466a45e --- /dev/null +++ b/src/client/qal.cpp @@ -0,0 +1,337 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// Dynamically loads OpenAL + +#ifdef USE_OPENAL + +#include "qal.h" + +#ifdef USE_OPENAL_DLOPEN +#include "sys/sys_loadlib.h" + +LPALENABLE qalEnable; +LPALDISABLE qalDisable; +LPALISENABLED qalIsEnabled; +LPALGETSTRING qalGetString; +LPALGETBOOLEANV qalGetBooleanv; +LPALGETINTEGERV qalGetIntegerv; +LPALGETFLOATV qalGetFloatv; +LPALGETDOUBLEV qalGetDoublev; +LPALGETBOOLEAN qalGetBoolean; +LPALGETINTEGER qalGetInteger; +LPALGETFLOAT qalGetFloat; +LPALGETDOUBLE qalGetDouble; +LPALGETERROR qalGetError; +LPALISEXTENSIONPRESENT qalIsExtensionPresent; +LPALGETPROCADDRESS qalGetProcAddress; +LPALGETENUMVALUE qalGetEnumValue; +LPALLISTENERF qalListenerf; +LPALLISTENER3F qalListener3f; +LPALLISTENERFV qalListenerfv; +LPALLISTENERI qalListeneri; +LPALGETLISTENERF qalGetListenerf; +LPALGETLISTENER3F qalGetListener3f; +LPALGETLISTENERFV qalGetListenerfv; +LPALGETLISTENERI qalGetListeneri; +LPALGENSOURCES qalGenSources; +LPALDELETESOURCES qalDeleteSources; +LPALISSOURCE qalIsSource; +LPALSOURCEF qalSourcef; +LPALSOURCE3F qalSource3f; +LPALSOURCEFV qalSourcefv; +LPALSOURCEI qalSourcei; +LPALGETSOURCEF qalGetSourcef; +LPALGETSOURCE3F qalGetSource3f; +LPALGETSOURCEFV qalGetSourcefv; +LPALGETSOURCEI qalGetSourcei; +LPALSOURCEPLAYV qalSourcePlayv; +LPALSOURCESTOPV qalSourceStopv; +LPALSOURCEREWINDV qalSourceRewindv; +LPALSOURCEPAUSEV qalSourcePausev; +LPALSOURCEPLAY qalSourcePlay; +LPALSOURCESTOP qalSourceStop; +LPALSOURCEREWIND qalSourceRewind; +LPALSOURCEPAUSE qalSourcePause; +LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers; +LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers; +LPALGENBUFFERS qalGenBuffers; +LPALDELETEBUFFERS qalDeleteBuffers; +LPALISBUFFER qalIsBuffer; +LPALBUFFERDATA qalBufferData; +LPALGETBUFFERF qalGetBufferf; +LPALGETBUFFERI qalGetBufferi; +LPALDOPPLERFACTOR qalDopplerFactor; +LPALSPEEDOFSOUND qalSpeedOfSound; +LPALDISTANCEMODEL qalDistanceModel; + +LPALCCREATECONTEXT qalcCreateContext; +LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent; +LPALCPROCESSCONTEXT qalcProcessContext; +LPALCSUSPENDCONTEXT qalcSuspendContext; +LPALCDESTROYCONTEXT qalcDestroyContext; +LPALCGETCURRENTCONTEXT qalcGetCurrentContext; +LPALCGETCONTEXTSDEVICE qalcGetContextsDevice; +LPALCOPENDEVICE qalcOpenDevice; +LPALCCLOSEDEVICE qalcCloseDevice; +LPALCGETERROR qalcGetError; +LPALCISEXTENSIONPRESENT qalcIsExtensionPresent; +LPALCGETPROCADDRESS qalcGetProcAddress; +LPALCGETENUMVALUE qalcGetEnumValue; +LPALCGETSTRING qalcGetString; +LPALCGETINTEGERV qalcGetIntegerv; +LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice; +LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice; +LPALCCAPTURESTART qalcCaptureStart; +LPALCCAPTURESTOP qalcCaptureStop; +LPALCCAPTURESAMPLES qalcCaptureSamples; + +static void *OpenALLib = NULL; + +static bool alinit_fail = false; + +/* +================= +GPA +================= +*/ +static void *GPA(const char *str) +{ + void *rv; + + rv = Sys_LoadFunction(OpenALLib, str); + if(!rv) + { + Com_Printf( " Can't load symbol %s\n", str); + alinit_fail = true; + return NULL; + } + else + { + Com_DPrintf( " Loaded symbol %s (%p)\n", str, rv); + return rv; + } +} + +/* +================= +QAL_Init +================= +*/ +bool QAL_Init(const char *libname) +{ + if(OpenALLib) + return true; + + if(!(OpenALLib = Sys_LoadDll(libname, true))) + return false; + + alinit_fail = false; + + qalEnable = (LPALENABLE)GPA("alEnable"); + qalDisable = (LPALDISABLE)GPA("alDisable"); + qalIsEnabled = (LPALISENABLED)GPA("alIsEnabled"); + qalGetString = (LPALGETSTRING)GPA("alGetString"); + qalGetBooleanv = (LPALGETBOOLEANV)GPA("alGetBooleanv"); + qalGetIntegerv = (LPALGETINTEGERV)GPA("alGetIntegerv"); + qalGetFloatv = (LPALGETFLOATV)GPA("alGetFloatv"); + qalGetDoublev = (LPALGETDOUBLEV)GPA("alGetDoublev"); + qalGetBoolean = (LPALGETBOOLEAN)GPA("alGetBoolean"); + qalGetInteger = (LPALGETINTEGER)GPA("alGetInteger"); + qalGetFloat = (LPALGETFLOAT)GPA("alGetFloat"); + qalGetDouble = (LPALGETDOUBLE)GPA("alGetDouble"); + qalGetError = (LPALGETERROR)GPA("alGetError"); + qalIsExtensionPresent = (LPALISEXTENSIONPRESENT)GPA("alIsExtensionPresent"); + qalGetProcAddress = (LPALGETPROCADDRESS)GPA("alGetProcAddress"); + qalGetEnumValue = (LPALGETENUMVALUE)GPA("alGetEnumValue"); + qalListenerf = (LPALLISTENERF)GPA("alListenerf"); + qalListener3f = (LPALLISTENER3F)GPA("alListener3f"); + qalListenerfv = (LPALLISTENERFV)GPA("alListenerfv"); + qalListeneri = (LPALLISTENERI)GPA("alListeneri"); + qalGetListenerf = (LPALGETLISTENERF)GPA("alGetListenerf"); + qalGetListener3f = (LPALGETLISTENER3F)GPA("alGetListener3f"); + qalGetListenerfv = (LPALGETLISTENERFV)GPA("alGetListenerfv"); + qalGetListeneri = (LPALGETLISTENERI)GPA("alGetListeneri"); + qalGenSources = (LPALGENSOURCES)GPA("alGenSources"); + qalDeleteSources = (LPALDELETESOURCES)GPA("alDeleteSources"); + qalIsSource = (LPALISSOURCE)GPA("alIsSource"); + qalSourcef = (LPALSOURCEF)GPA("alSourcef"); + qalSource3f = (LPALSOURCE3F)GPA("alSource3f"); + qalSourcefv = (LPALSOURCEFV)GPA("alSourcefv"); + qalSourcei = (LPALSOURCEI)GPA("alSourcei"); + qalGetSourcef = (LPALGETSOURCEF)GPA("alGetSourcef"); + qalGetSource3f = (LPALGETSOURCE3F)GPA("alGetSource3f"); + qalGetSourcefv = (LPALGETSOURCEFV)GPA("alGetSourcefv"); + qalGetSourcei = (LPALGETSOURCEI)GPA("alGetSourcei"); + qalSourcePlayv = (LPALSOURCEPLAYV)GPA("alSourcePlayv"); + qalSourceStopv = (LPALSOURCESTOPV)GPA("alSourceStopv"); + qalSourceRewindv = (LPALSOURCEREWINDV)GPA("alSourceRewindv"); + qalSourcePausev = (LPALSOURCEPAUSEV)GPA("alSourcePausev"); + qalSourcePlay = (LPALSOURCEPLAY)GPA("alSourcePlay"); + qalSourceStop = (LPALSOURCESTOP)GPA("alSourceStop"); + qalSourceRewind = (LPALSOURCEREWIND)GPA("alSourceRewind"); + qalSourcePause = (LPALSOURCEPAUSE)GPA("alSourcePause"); + qalSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)GPA("alSourceQueueBuffers"); + qalSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)GPA("alSourceUnqueueBuffers"); + qalGenBuffers = (LPALGENBUFFERS)GPA("alGenBuffers"); + qalDeleteBuffers = (LPALDELETEBUFFERS)GPA("alDeleteBuffers"); + qalIsBuffer = (LPALISBUFFER)GPA("alIsBuffer"); + qalBufferData = (LPALBUFFERDATA)GPA("alBufferData"); + qalGetBufferf = (LPALGETBUFFERF)GPA("alGetBufferf"); + qalGetBufferi = (LPALGETBUFFERI)GPA("alGetBufferi"); + qalDopplerFactor = (LPALDOPPLERFACTOR)GPA("alDopplerFactor"); + qalSpeedOfSound = (LPALSPEEDOFSOUND)GPA("alSpeedOfSound"); + qalDistanceModel = (LPALDISTANCEMODEL)GPA("alDistanceModel"); + + qalcCreateContext = (LPALCCREATECONTEXT)GPA("alcCreateContext"); + qalcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)GPA("alcMakeContextCurrent"); + qalcProcessContext = (LPALCPROCESSCONTEXT)GPA("alcProcessContext"); + qalcSuspendContext = (LPALCSUSPENDCONTEXT)GPA("alcSuspendContext"); + qalcDestroyContext = (LPALCDESTROYCONTEXT)GPA("alcDestroyContext"); + qalcGetCurrentContext = (LPALCGETCURRENTCONTEXT)GPA("alcGetCurrentContext"); + qalcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)GPA("alcGetContextsDevice"); + qalcOpenDevice = (LPALCOPENDEVICE)GPA("alcOpenDevice"); + qalcCloseDevice = (LPALCCLOSEDEVICE)GPA("alcCloseDevice"); + qalcGetError = (LPALCGETERROR)GPA("alcGetError"); + qalcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)GPA("alcIsExtensionPresent"); + qalcGetProcAddress = (LPALCGETPROCADDRESS)GPA("alcGetProcAddress"); + qalcGetEnumValue = (LPALCGETENUMVALUE)GPA("alcGetEnumValue"); + qalcGetString = (LPALCGETSTRING)GPA("alcGetString"); + qalcGetIntegerv = (LPALCGETINTEGERV)GPA("alcGetIntegerv"); + qalcCaptureOpenDevice = (LPALCCAPTUREOPENDEVICE)GPA("alcCaptureOpenDevice"); + qalcCaptureCloseDevice = (LPALCCAPTURECLOSEDEVICE)GPA("alcCaptureCloseDevice"); + qalcCaptureStart = (LPALCCAPTURESTART)GPA("alcCaptureStart"); + qalcCaptureStop = (LPALCCAPTURESTOP)GPA("alcCaptureStop"); + qalcCaptureSamples = (LPALCCAPTURESAMPLES)GPA("alcCaptureSamples"); + + if(alinit_fail) + { + QAL_Shutdown(); + Com_Printf( " One or more symbols not found\n"); + return false; + } + + return true; +} + +/* +================= +QAL_Shutdown +================= +*/ +void QAL_Shutdown( void ) +{ + if(OpenALLib) + { + Sys_UnloadLibrary(OpenALLib); + OpenALLib = NULL; + } + + qalEnable = NULL; + qalDisable = NULL; + qalIsEnabled = NULL; + qalGetString = NULL; + qalGetBooleanv = NULL; + qalGetIntegerv = NULL; + qalGetFloatv = NULL; + qalGetDoublev = NULL; + qalGetBoolean = NULL; + qalGetInteger = NULL; + qalGetFloat = NULL; + qalGetDouble = NULL; + qalGetError = NULL; + qalIsExtensionPresent = NULL; + qalGetProcAddress = NULL; + qalGetEnumValue = NULL; + qalListenerf = NULL; + qalListener3f = NULL; + qalListenerfv = NULL; + qalListeneri = NULL; + qalGetListenerf = NULL; + qalGetListener3f = NULL; + qalGetListenerfv = NULL; + qalGetListeneri = NULL; + qalGenSources = NULL; + qalDeleteSources = NULL; + qalIsSource = NULL; + qalSourcef = NULL; + qalSource3f = NULL; + qalSourcefv = NULL; + qalSourcei = NULL; + qalGetSourcef = NULL; + qalGetSource3f = NULL; + qalGetSourcefv = NULL; + qalGetSourcei = NULL; + qalSourcePlayv = NULL; + qalSourceStopv = NULL; + qalSourceRewindv = NULL; + qalSourcePausev = NULL; + qalSourcePlay = NULL; + qalSourceStop = NULL; + qalSourceRewind = NULL; + qalSourcePause = NULL; + qalSourceQueueBuffers = NULL; + qalSourceUnqueueBuffers = NULL; + qalGenBuffers = NULL; + qalDeleteBuffers = NULL; + qalIsBuffer = NULL; + qalBufferData = NULL; + qalGetBufferf = NULL; + qalGetBufferi = NULL; + qalDopplerFactor = NULL; + qalSpeedOfSound = NULL; + qalDistanceModel = NULL; + + qalcCreateContext = NULL; + qalcMakeContextCurrent = NULL; + qalcProcessContext = NULL; + qalcSuspendContext = NULL; + qalcDestroyContext = NULL; + qalcGetCurrentContext = NULL; + qalcGetContextsDevice = NULL; + qalcOpenDevice = NULL; + qalcCloseDevice = NULL; + qalcGetError = NULL; + qalcIsExtensionPresent = NULL; + qalcGetProcAddress = NULL; + qalcGetEnumValue = NULL; + qalcGetString = NULL; + qalcGetIntegerv = NULL; + qalcCaptureOpenDevice = NULL; + qalcCaptureCloseDevice = NULL; + qalcCaptureStart = NULL; + qalcCaptureStop = NULL; + qalcCaptureSamples = NULL; +} +#else +bool QAL_Init(const char *libname) +{ + return true; +} +void QAL_Shutdown( void ) +{ +} +#endif +#endif diff --git a/src/client/qal.h b/src/client/qal.h new file mode 100644 index 0000000..41d6244 --- /dev/null +++ b/src/client/qal.h @@ -0,0 +1,252 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __QAL_H__ +#define __QAL_H__ + +#ifdef USE_LOCAL_HEADERS +# include "AL/al.h" +# include "AL/alc.h" +#else +# if defined(_MSC_VER) || defined(__APPLE__) +// MSVC users must install the OpenAL SDK which doesn't use the AL/*.h scheme. +// OSX framework also needs this +# include +# include +# else +# include +# include +# endif +#endif + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +#ifdef USE_OPENAL_DLOPEN +#define AL_NO_PROTOTYPES +#define ALC_NO_PROTOTYPES +#endif + +/* Hack to enable compiling both on OpenAL SDK and OpenAL-soft. */ +#ifndef ALC_ENUMERATE_ALL_EXT +# define ALC_ENUMERATE_ALL_EXT 1 +# define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 +# define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +#ifdef USE_OPENAL_DLOPEN +extern LPALENABLE qalEnable; +extern LPALDISABLE qalDisable; +extern LPALISENABLED qalIsEnabled; +extern LPALGETSTRING qalGetString; +extern LPALGETBOOLEANV qalGetBooleanv; +extern LPALGETINTEGERV qalGetIntegerv; +extern LPALGETFLOATV qalGetFloatv; +extern LPALGETDOUBLEV qalGetDoublev; +extern LPALGETBOOLEAN qalGetBoolean; +extern LPALGETINTEGER qalGetInteger; +extern LPALGETFLOAT qalGetFloat; +extern LPALGETDOUBLE qalGetDouble; +extern LPALGETERROR qalGetError; +extern LPALISEXTENSIONPRESENT qalIsExtensionPresent; +extern LPALGETPROCADDRESS qalGetProcAddress; +extern LPALGETENUMVALUE qalGetEnumValue; +extern LPALLISTENERF qalListenerf; +extern LPALLISTENER3F qalListener3f; +extern LPALLISTENERFV qalListenerfv; +extern LPALLISTENERI qalListeneri; +extern LPALLISTENER3I qalListener3i; +extern LPALLISTENERIV qalListeneriv; +extern LPALGETLISTENERF qalGetListenerf; +extern LPALGETLISTENER3F qalGetListener3f; +extern LPALGETLISTENERFV qalGetListenerfv; +extern LPALGETLISTENERI qalGetListeneri; +extern LPALGETLISTENER3I qalGetListener3i; +extern LPALGETLISTENERIV qalGetListeneriv; +extern LPALGENSOURCES qalGenSources; +extern LPALDELETESOURCES qalDeleteSources; +extern LPALISSOURCE qalIsSource; +extern LPALSOURCEF qalSourcef; +extern LPALSOURCE3F qalSource3f; +extern LPALSOURCEFV qalSourcefv; +extern LPALSOURCEI qalSourcei; +extern LPALSOURCE3I qalSource3i; +extern LPALSOURCEIV qalSourceiv; +extern LPALGETSOURCEF qalGetSourcef; +extern LPALGETSOURCE3F qalGetSource3f; +extern LPALGETSOURCEFV qalGetSourcefv; +extern LPALGETSOURCEI qalGetSourcei; +extern LPALGETSOURCE3I qalGetSource3i; +extern LPALGETSOURCEIV qalGetSourceiv; +extern LPALSOURCEPLAYV qalSourcePlayv; +extern LPALSOURCESTOPV qalSourceStopv; +extern LPALSOURCEREWINDV qalSourceRewindv; +extern LPALSOURCEPAUSEV qalSourcePausev; +extern LPALSOURCEPLAY qalSourcePlay; +extern LPALSOURCESTOP qalSourceStop; +extern LPALSOURCEREWIND qalSourceRewind; +extern LPALSOURCEPAUSE qalSourcePause; +extern LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers; +extern LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers; +extern LPALGENBUFFERS qalGenBuffers; +extern LPALDELETEBUFFERS qalDeleteBuffers; +extern LPALISBUFFER qalIsBuffer; +extern LPALBUFFERDATA qalBufferData; +extern LPALBUFFERF qalBufferf; +extern LPALBUFFER3F qalBuffer3f; +extern LPALBUFFERFV qalBufferfv; +extern LPALBUFFERF qalBufferi; +extern LPALBUFFER3F qalBuffer3i; +extern LPALBUFFERFV qalBufferiv; +extern LPALGETBUFFERF qalGetBufferf; +extern LPALGETBUFFER3F qalGetBuffer3f; +extern LPALGETBUFFERFV qalGetBufferfv; +extern LPALGETBUFFERI qalGetBufferi; +extern LPALGETBUFFER3I qalGetBuffer3i; +extern LPALGETBUFFERIV qalGetBufferiv; +extern LPALDOPPLERFACTOR qalDopplerFactor; +extern LPALSPEEDOFSOUND qalSpeedOfSound; +extern LPALDISTANCEMODEL qalDistanceModel; + +extern LPALCCREATECONTEXT qalcCreateContext; +extern LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent; +extern LPALCPROCESSCONTEXT qalcProcessContext; +extern LPALCSUSPENDCONTEXT qalcSuspendContext; +extern LPALCDESTROYCONTEXT qalcDestroyContext; +extern LPALCGETCURRENTCONTEXT qalcGetCurrentContext; +extern LPALCGETCONTEXTSDEVICE qalcGetContextsDevice; +extern LPALCOPENDEVICE qalcOpenDevice; +extern LPALCCLOSEDEVICE qalcCloseDevice; +extern LPALCGETERROR qalcGetError; +extern LPALCISEXTENSIONPRESENT qalcIsExtensionPresent; +extern LPALCGETPROCADDRESS qalcGetProcAddress; +extern LPALCGETENUMVALUE qalcGetEnumValue; +extern LPALCGETSTRING qalcGetString; +extern LPALCGETINTEGERV qalcGetIntegerv; +extern LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice; +extern LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice; +extern LPALCCAPTURESTART qalcCaptureStart; +extern LPALCCAPTURESTOP qalcCaptureStop; +extern LPALCCAPTURESAMPLES qalcCaptureSamples; +#else +#define qalEnable alEnable +#define qalDisable alDisable +#define qalIsEnabled alIsEnabled +#define qalGetString alGetString +#define qalGetBooleanv alGetBooleanv +#define qalGetIntegerv alGetIntegerv +#define qalGetFloatv alGetFloatv +#define qalGetDoublev alGetDoublev +#define qalGetBoolean alGetBoolean +#define qalGetInteger alGetInteger +#define qalGetFloat alGetFloat +#define qalGetDouble alGetDouble +#define qalGetError alGetError +#define qalIsExtensionPresent alIsExtensionPresent +#define qalGetProcAddress alGetProcAddress +#define qalGetEnumValue alGetEnumValue +#define qalListenerf alListenerf +#define qalListener3f alListener3f +#define qalListenerfv alListenerfv +#define qalListeneri alListeneri +#define qalListener3i alListener3i +#define qalListeneriv alListeneriv +#define qalGetListenerf alGetListenerf +#define qalGetListener3f alGetListener3f +#define qalGetListenerfv alGetListenerfv +#define qalGetListeneri alGetListeneri +#define qalGetListener3i alGetListener3i +#define qalGetListeneriv alGetListeneriv +#define qalGenSources alGenSources +#define qalDeleteSources alDeleteSources +#define qalIsSource alIsSource +#define qalSourcef alSourcef +#define qalSource3f alSource3f +#define qalSourcefv alSourcefv +#define qalSourcei alSourcei +#define qalSource3i alSource3i +#define qalSourceiv alSourceiv +#define qalGetSourcef alGetSourcef +#define qalGetSource3f alGetSource3f +#define qalGetSourcefv alGetSourcefv +#define qalGetSourcei alGetSourcei +#define qalGetSource3i alGetSource3i +#define qalGetSourceiv alGetSourceiv +#define qalSourcePlayv alSourcePlayv +#define qalSourceStopv alSourceStopv +#define qalSourceRewindv alSourceRewindv +#define qalSourcePausev alSourcePausev +#define qalSourcePlay alSourcePlay +#define qalSourceStop alSourceStop +#define qalSourceRewind alSourceRewind +#define qalSourcePause alSourcePause +#define qalSourceQueueBuffers alSourceQueueBuffers +#define qalSourceUnqueueBuffers alSourceUnqueueBuffers +#define qalGenBuffers alGenBuffers +#define qalDeleteBuffers alDeleteBuffers +#define qalIsBuffer alIsBuffer +#define qalBufferData alBufferData +#define qalBufferf alBufferf +#define qalBuffer3f alBuffer3f +#define qalBufferfv alBufferfv +#define qalBufferi alBufferi +#define qalBuffer3i alBuffer3i +#define qalBufferiv alBufferiv +#define qalGetBufferf alGetBufferf +#define qalGetBuffer3f alGetBuffer3f +#define qalGetBufferfv alGetBufferfv +#define qalGetBufferi alGetBufferi +#define qalGetBuffer3i alGetBuffer3i +#define qalGetBufferiv alGetBufferiv +#define qalDopplerFactor alDopplerFactor +#define qalSpeedOfSound alSpeedOfSound +#define qalDistanceModel alDistanceModel + +#define qalcCreateContext alcCreateContext +#define qalcMakeContextCurrent alcMakeContextCurrent +#define qalcProcessContext alcProcessContext +#define qalcSuspendContext alcSuspendContext +#define qalcDestroyContext alcDestroyContext +#define qalcGetCurrentContext alcGetCurrentContext +#define qalcGetContextsDevice alcGetContextsDevice +#define qalcOpenDevice alcOpenDevice +#define qalcCloseDevice alcCloseDevice +#define qalcGetError alcGetError +#define qalcIsExtensionPresent alcIsExtensionPresent +#define qalcGetProcAddress alcGetProcAddress +#define qalcGetEnumValue alcGetEnumValue +#define qalcGetString alcGetString +#define qalcGetIntegerv alcGetIntegerv +#define qalcCaptureOpenDevice alcCaptureOpenDevice +#define qalcCaptureCloseDevice alcCaptureCloseDevice +#define qalcCaptureStart alcCaptureStart +#define qalcCaptureStop alcCaptureStop +#define qalcCaptureSamples alcCaptureSamples +#endif + +bool QAL_Init(const char *libname); +void QAL_Shutdown( void ); + +#endif // __QAL_H__ diff --git a/src/client/snd_adpcm.cpp b/src/client/snd_adpcm.cpp new file mode 100644 index 0000000..d5d9930 --- /dev/null +++ b/src/client/snd_adpcm.cpp @@ -0,0 +1,329 @@ +/*********************************************************** +Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The +Netherlands. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +******************************************************************/ + +/* +** Intel/DVI ADPCM coder/decoder. +** +** The algorithm for this coder was taken from the IMA Compatability Project +** proceedings, Vol 2, Number 2; May 1992. +** +** Version 1.2, 18-Dec-92. +*/ + +#include "snd_local.h" + +/* Intel ADPCM step variation table */ +static int indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +static int stepsizeTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + + +void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { + short *inp; /* Input buffer pointer */ + signed char *outp; /* output buffer pointer */ + int val; /* Current input sample value */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int diff; /* Difference between val and sample */ + int step; /* Stepsize */ + int valpred; /* Predicted output value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int outputbuffer; /* place to keep previous 4-bit value */ + int bufferstep; /* toggle between outputbuffer/output */ + + outp = (signed char *)outdata; + inp = indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + outputbuffer = 0; // quiet a compiler warning + bufferstep = 1; + + for ( ; len > 0 ; len-- ) { + val = *inp++; + + /* Step 1 - compute difference with previous value */ + diff = val - valpred; + sign = (diff < 0) ? 8 : 0; + if ( sign ) diff = (-diff); + + /* Step 2 - Divide and clamp */ + /* Note: + ** This code *approximately* computes: + ** delta = diff*4/step; + ** vpdiff = (delta+0.5)*step/4; + ** but in shift step bits are dropped. The net result of this is + ** that even if you have fast mul/div hardware you cannot put it to + ** good use since the fixup would be too expensive. + */ + delta = 0; + vpdiff = (step >> 3); + + if ( diff >= step ) { + delta = 4; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 2; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 1; + vpdiff += step; + } + + /* Step 3 - Update previous value */ + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 4 - Clamp previous value to 16 bits */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 5 - Assemble value, update index and step values */ + delta |= sign; + + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + step = stepsizeTable[index]; + + /* Step 6 - Output value */ + if ( bufferstep ) { + outputbuffer = (delta << 4) & 0xf0; + } else { + *outp++ = (delta & 0x0f) | outputbuffer; + } + bufferstep = !bufferstep; + } + + /* Output last step, if needed */ + if ( !bufferstep ) + *outp++ = outputbuffer; + + state->sample = valpred; + state->index = index; +} + + +/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { + signed char *inp; /* Input buffer pointer */ + int outp; /* output buffer pointer */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int step; /* Stepsize */ + int valpred; /* Predicted value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int inputbuffer; /* place to keep next 4-bit value */ + int bufferstep; /* toggle between inputbuffer/input */ + + outp = 0; + inp = (signed char *)indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 0; + inputbuffer = 0; // quiet a compiler warning + for ( ; len > 0 ; len-- ) { + + /* Step 1 - get the delta value */ + if ( bufferstep ) { + delta = inputbuffer & 0xf; + } else { + inputbuffer = *inp++; + delta = (inputbuffer >> 4) & 0xf; + } + bufferstep = !bufferstep; + + /* Step 2 - Find new index value (for later) */ + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + + /* Step 3 - Separate sign and magnitude */ + sign = delta & 8; + delta = delta & 7; + + /* Step 4 - Compute difference and new predicted value */ + /* + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment + ** in adpcm_coder. + */ + vpdiff = step >> 3; + if ( delta & 4 ) vpdiff += step; + if ( delta & 2 ) vpdiff += step>>1; + if ( delta & 1 ) vpdiff += step>>2; + + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 5 - clamp output value */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 6 - Update step value */ + step = stepsizeTable[index]; + + /* Step 7 - Output value */ + outdata[outp] = valpred; + outp++; + } + + state->sample = valpred; + state->index = index; +} + + +/* +==================== +S_AdpcmMemoryNeeded + +Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format +==================== +*/ +int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { + float scale; + int scaledSampleCount; + int sampleMemory; + int blockCount; + int headerMemory; + + // determine scale to convert from input sampling rate to desired sampling rate + scale = (float)info->rate / dma.speed; + + // calc number of samples at playback sampling rate + scaledSampleCount = info->samples / scale; + + // calc memory need to store those samples using ADPCM at 4 bits per sample + sampleMemory = scaledSampleCount / 2; + + // calc number of sample blocks needed of PAINTBUFFER_SIZE + blockCount = scaledSampleCount / PAINTBUFFER_SIZE; + if( scaledSampleCount % PAINTBUFFER_SIZE ) { + blockCount++; + } + + // calc memory needed to store the block headers + headerMemory = blockCount * sizeof(adpcm_state_t); + + return sampleMemory + headerMemory; +} + + +/* +==================== +S_AdpcmGetSamples +==================== +*/ +void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { + adpcm_state_t state; + byte *out; + + // get the starting state from the block header + state.index = chunk->adpcm.index; + state.sample = chunk->adpcm.sample; + + out = (byte *)chunk->sndChunk; + // get samples + S_AdpcmDecode((char *) out, to, SND_CHUNK_SIZE_BYTE*2, &state ); +} + + +/* +==================== +S_AdpcmEncodeSound +==================== +*/ +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { + adpcm_state_t state; + int inOffset; + int count; + int n; + sndBuffer *newchunk, *chunk; + byte *out; + + inOffset = 0; + count = sfx->soundLength; + state.index = 0; + state.sample = samples[0]; + + chunk = NULL; + while( count ) { + n = count; + if( n > SND_CHUNK_SIZE_BYTE*2 ) { + n = SND_CHUNK_SIZE_BYTE*2; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else if (chunk != NULL) { + chunk->next = newchunk; + } + chunk = newchunk; + + // output the header + chunk->adpcm.index = state.index; + chunk->adpcm.sample = state.sample; + + out = (byte *)chunk->sndChunk; + + // encode the samples + S_AdpcmEncode( samples + inOffset, (char *) out, n, &state ); + + inOffset += n; + count -= n; + } +} diff --git a/src/client/snd_codec.cpp b/src/client/snd_codec.cpp new file mode 100644 index 0000000..a794b90 --- /dev/null +++ b/src/client/snd_codec.cpp @@ -0,0 +1,239 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +static snd_codec_t *codecs; + +/* +================= +S_CodecGetSound + +Opens/loads a sound, tries codec based on the sound's file extension +then tries all supported codecs. +================= +*/ +static void *S_CodecGetSound(const char *filename, snd_info_t *info) +{ + snd_codec_t *codec; + snd_codec_t *orgCodec = NULL; + bool orgNameFailed = false; + char localName[ MAX_QPATH ]; + const char *ext; + char altName[ MAX_QPATH ]; + void *rtn = NULL; + + Q_strncpyz(localName, filename, MAX_QPATH); + + ext = COM_GetExtension(localName); + + if( *ext ) + { + // Look for the correct loader and use it + for( codec = codecs; codec; codec = codec->next ) + { + if( !Q_stricmp( ext, codec->ext ) ) + { + // Load + if( info ) + rtn = codec->load(localName, info); + else + rtn = codec->open(localName); + break; + } + } + + // A loader was found + if( codec ) + { + if( !rtn ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = true; + orgCodec = codec; + COM_StripExtension( filename, localName, MAX_QPATH ); + } + else + { + // Something loaded + return rtn; + } + } + } + + // Try and find a suitable match using all + // the sound codecs supported + for( codec = codecs; codec; codec = codec->next ) + { + if( codec == orgCodec ) + continue; + + Com_sprintf( altName, sizeof (altName), "%s.%s", localName, codec->ext ); + + // Load + if( info ) + rtn = codec->load(altName, info); + else + rtn = codec->open(altName); + + if( rtn ) + { + if( orgNameFailed ) + { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s not present, using %s instead\n", + filename, altName ); + } + + return rtn; + } + } + + Com_Printf(S_COLOR_YELLOW "WARNING: Failed to %s sound %s!\n", info ? "load" : "open", filename); + + return NULL; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit() +{ + codecs = NULL; + +#ifdef USE_CODEC_OPUS + S_CodecRegister(&opus_codec); +#endif + +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&ogg_codec); +#endif + +// Register wav codec last so that it is always tried first when a file extension was not found + S_CodecRegister(&wav_codec); +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown() +{ + codecs = NULL; +} + +/* +================= +S_CodecRegister +================= +*/ +void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecLoad +================= +*/ +void *S_CodecLoad(const char *filename, snd_info_t *info) +{ + return S_CodecGetSound(filename, info); +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStream(const char *filename) +{ + return (snd_stream_t*)S_CodecGetSound(filename, NULL); +} + +void S_CodecCloseStream(snd_stream_t *stream) +{ + stream->codec->close(stream); +} + +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->read(stream, bytes, buffer); +} + +//======================================================================= +// Util functions (used by codecs) + +/* +================= +S_CodecUtilOpen +================= +*/ +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +{ + snd_stream_t *stream; + fileHandle_t hnd; + int length; + + // Try to open the file + length = FS_FOpenFileRead(filename, &hnd, true); + if(!hnd) + { + Com_DPrintf("Can't read sound file %s\n", filename); + return NULL; + } + + // Allocate a stream + stream = (snd_stream_t*)Z_Malloc(sizeof(snd_stream_t)); + if(!stream) + { + FS_FCloseFile(hnd); + return NULL; + } + + // Copy over, return + stream->codec = codec; + stream->file = hnd; + stream->length = length; + return stream; +} + +/* +================= +S_CodecUtilClose +================= +*/ +void S_CodecUtilClose(snd_stream_t **stream) +{ + FS_FCloseFile((*stream)->file); + Z_Free(*stream); + *stream = NULL; +} diff --git a/src/client/snd_codec.h b/src/client/snd_codec.h new file mode 100644 index 0000000..a5f4358 --- /dev/null +++ b/src/client/snd_codec.h @@ -0,0 +1,109 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +typedef struct snd_info_s +{ + int rate; + int width; + int channels; + int samples; + int size; + int dataofs; +} snd_info_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + snd_codec_t *codec; + fileHandle_t file; + snd_info_t info; + int length; + int pos; + void *ptr; +} snd_stream_t; + +// Codec functions +typedef void *(*CODEC_LOAD)(const char *filename, snd_info_t *info); +typedef snd_stream_t *(*CODEC_OPEN)(const char *filename); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +// Codec data structure +struct snd_codec_s +{ + const char *ext; + CODEC_LOAD load; + CODEC_OPEN open; + CODEC_READ read; + CODEC_CLOSE close; + snd_codec_t *next; +}; + +// Codec management +void S_CodecInit( void ); +void S_CodecShutdown( void ); +void S_CodecRegister(snd_codec_t *codec); +void *S_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_CodecOpenStream(const char *filename); +void S_CodecCloseStream(snd_stream_t *stream); +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Util functions (used by codecs) +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +void S_CodecUtilClose(snd_stream_t **stream); + +// WAV Codec +extern snd_codec_t wav_codec; +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_WAV_CodecOpenStream(const char *filename); +void S_WAV_CodecCloseStream(snd_stream_t *stream); +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Ogg Vorbis codec +#ifdef USE_CODEC_VORBIS +extern snd_codec_t ogg_codec; +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OGG_CodecOpenStream(const char *filename); +void S_OGG_CodecCloseStream(snd_stream_t *stream); +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_VORBIS + +// Ogg Opus codec +#ifdef USE_CODEC_OPUS +extern snd_codec_t opus_codec; +void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename); +void S_OggOpus_CodecCloseStream(snd_stream_t *stream); +int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_OPUS + +#endif // !_SND_CODEC_H_ diff --git a/src/client/snd_codec_ogg.cpp b/src/client/snd_codec_ogg.cpp new file mode 100644 index 0000000..15fcd37 --- /dev/null +++ b/src/client/snd_codec_ogg.cpp @@ -0,0 +1,479 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// OGG support is enabled by this define +#ifdef USE_CODEC_VORBIS + +// includes for the Q3 sound system +#include "client.h" + +// includes for the OGG codec +#define OV_EXCLUDE_STATIC_CALLBACKS +#include + +#include + +#include "snd_codec.h" + +// The OGG codec can return the samples in a number of different formats, +// we use the standard signed short format. +#define OGG_SAMPLEWIDTH 2 + +// Q3 OGG codec +snd_codec_t ogg_codec = +{ + "ogg", + S_OGG_CodecLoad, + S_OGG_CodecOpenStream, + S_OGG_CodecReadStream, + S_OGG_CodecCloseStream, + NULL +}; + +// callbacks for vobisfile + +// fread() replacement +size_t S_OGG_Callback_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + snd_stream_t *stream; + int byteSize = 0; + int bytesRead = 0; + size_t nMembRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return 0; + } + + if(!(size && nmemb)) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if(!datasource) + { + errno = EBADF; + return 0; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // FS_Read does not support multi-byte elements + byteSize = nmemb * size; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, byteSize, stream->file); + + // update the file position + stream->pos += bytesRead; + + // this function returns the number of elements read not the number of bytes + nMembRead = bytesRead / size; + + // even if the last member is only read partially + // it is counted as a whole in the return value + if(bytesRead % size) + { + nMembRead++; + } + + return nMembRead; +} + +// fseek() replacement +int S_OGG_Callback_seek(void *datasource, ogg_int64_t offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_END); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OGG_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OGG_CodecCloseStream() + return 0; +} + +// ftell() replacement +long S_OGG_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (long) FS_FTell(stream->file); +} + +// the callback structure +const ov_callbacks S_OGG_Callbacks = +{ + &S_OGG_Callback_read, + &S_OGG_Callback_seek, + &S_OGG_Callback_close, + &S_OGG_Callback_tell +}; + +/* +================= +S_OGG_CodecOpenStream +================= +*/ +snd_stream_t *S_OGG_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // OGG codec control structure + OggVorbis_File *vf; + + // some variables used to get informations about the OGG + vorbis_info *OGGInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &ogg_codec); + if(!stream) + { + return NULL; + } + + // alloctate the OggVorbis_File + vf = (OggVorbis_File*)Z_Malloc(sizeof(OggVorbis_File)); + if(!vf) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + if(ov_open_callbacks(stream, vf, NULL, 0, S_OGG_Callbacks) != 0) + { + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!ov_seekable(vf)) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // we only support OGGs with one substream + if(ov_streams(vf) != 1) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + OGGInfo = ov_info(vf, 0); + if(!OGGInfo) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the number of sample-frames in the OGG + numSamples = ov_pcm_total(vf, 0); + + // fill in the info-structure in the stream + stream->info.rate = OGGInfo->rate; + stream->info.width = OGG_SAMPLEWIDTH; + stream->info.channels = OGGInfo->channels; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the OGG codec control structure + stream->ptr = vf; + + return stream; +} + +/* +================= +S_OGG_CodecCloseStream +================= +*/ +void S_OGG_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the OGG codec cleanup its stuff + ov_clear((OggVorbis_File *) stream->ptr); + + // free the OGG codec control struct + Z_Free(stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OGG_CodecReadStream +================= +*/ +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int bytesRead, bytesLeft, c; + char *bufPtr; + + // Bitstream for the decoder + int BS = 0; + + // big endian machines want their samples in big endian order + int IsBigEndian = 0; + +# ifdef Q3_BIG_ENDIAN + IsBigEndian = 1; +# endif // Q3_BIG_ENDIAN + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + bytesRead = 0; + bytesLeft = bytes; + bufPtr = (char*)buffer; + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some bytes from the OGG codec + c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS); + + // no more bytes are left + if(c <= 0) + { + break; + } + + bytesRead += c; + bytesLeft -= c; + bufPtr += c; + + // we have enough bytes + if(bytesLeft <= 0) + { + break; + } + } + + return bytesRead; +} + +/* +===================================================================== +S_OGG_CodecLoad + +We handle S_OGG_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OGG_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = (byte*)Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OGG_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Hunk_FreeTempMemory(buffer); + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + S_OGG_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_VORBIS diff --git a/src/client/snd_codec_opus.cpp b/src/client/snd_codec_opus.cpp new file mode 100644 index 0000000..a8ffba5 --- /dev/null +++ b/src/client/snd_codec_opus.cpp @@ -0,0 +1,452 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// Ogg Opus support is enabled by this define +#ifdef USE_CODEC_OPUS + +// includes for the Q3 sound system +#include "client.h" + +// includes for the Ogg Opus codec +#include + +#include + +#include "snd_codec.h" + +// samples are 16 bit +#define OPUS_SAMPLEWIDTH 2 + +// Q3 Ogg Opus codec +snd_codec_t opus_codec = +{ + "opus", + S_OggOpus_CodecLoad, + S_OggOpus_CodecOpenStream, + S_OggOpus_CodecReadStream, + S_OggOpus_CodecCloseStream, + NULL +}; + +// callbacks for opusfile + +// fread() replacement +int S_OggOpus_Callback_read(void *datasource, unsigned char *ptr, int size ) +{ + snd_stream_t *stream; + int bytesRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return -1; + } + + if(!size) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if (size < 0) + { + errno = EINVAL; + return -1; + } + + if(!datasource) + { + errno = EBADF; + return -1; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, size, stream->file); + + // update the file position + stream->pos += bytesRead; + + return bytesRead; +} + +// fseek() replacement +int S_OggOpus_Callback_seek(void *datasource, opus_int64 offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_END); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OggOpus_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OggOpus_CodecCloseStream() + return 0; +} + +// ftell() replacement +opus_int64 S_OggOpus_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (opus_int64) FS_FTell(stream->file); +} + +// the callback structure +const OpusFileCallbacks S_OggOpus_Callbacks = +{ + &S_OggOpus_Callback_read, + &S_OggOpus_Callback_seek, + &S_OggOpus_Callback_tell, + &S_OggOpus_Callback_close +}; + +/* +================= +S_OggOpus_CodecOpenStream +================= +*/ +snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // Opus codec control structure + OggOpusFile *of; + + // some variables used to get informations about the file + const OpusHead *opusInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &opus_codec); + if(!stream) + { + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + of = op_open_callbacks(stream, &S_OggOpus_Callbacks, NULL, 0, NULL ); + if (!of) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!op_seekable(of)) + { + op_free(of); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + opusInfo = op_head(of, -1); + if(!opusInfo) + { + op_free(of); + + S_CodecUtilClose(&stream); + + return NULL; + } + + if(opusInfo->stream_count != 1) + { + op_free(of); + + S_CodecUtilClose(&stream); + + Com_Printf("Only Ogg Opus files with one stream are support\n"); + return NULL; + } + + if(opusInfo->channel_count != 1 && opusInfo->channel_count != 2) + { + op_free(of); + + S_CodecUtilClose(&stream); + + Com_Printf("Only mono and stereo Ogg Opus files are supported\n"); + return NULL; + } + + // get the number of sample-frames in the file + numSamples = op_pcm_total(of, -1); + + // fill in the info-structure in the stream + stream->info.rate = 48000; + stream->info.width = OPUS_SAMPLEWIDTH; + stream->info.channels = opusInfo->channel_count; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the opus codec control structure + stream->ptr = of; + + return stream; +} + +/* +================= +S_OggOpus_CodecCloseStream +================= +*/ +void S_OggOpus_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the opus codec cleanup its stuff + op_free((OggOpusFile *) stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OggOpus_CodecReadStream +================= +*/ +int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int samplesRead, samplesLeft, c; + opus_int16 *bufPtr; + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + samplesRead = 0; + samplesLeft = bytes / stream->info.channels / stream->info.width; + bufPtr = (opus_int16*)buffer; + + if(samplesLeft <= 0) + { + return 0; + } + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some samples from the opus codec + c = op_read((OggOpusFile *) stream->ptr, bufPtr + samplesRead * stream->info.channels, samplesLeft * stream->info.channels, NULL); + + // no more samples are left + if(c <= 0) + { + break; + } + + samplesRead += c; + samplesLeft -= c; + + // we have enough samples + if(samplesLeft <= 0) + { + break; + } + } + + return samplesRead * stream->info.channels * stream->info.width; +} + +/* +===================================================================== +S_OggOpus_CodecLoad + +We handle S_OggOpus_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OggOpus_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = (byte*)Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + S_OggOpus_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OggOpus_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Hunk_FreeTempMemory(buffer); + S_OggOpus_CodecCloseStream(stream); + + return NULL; + } + + S_OggOpus_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_OPUS diff --git a/src/client/snd_codec_wav.cpp b/src/client/snd_codec_wav.cpp new file mode 100644 index 0000000..a4016e2 --- /dev/null +++ b/src/client/snd_codec_wav.cpp @@ -0,0 +1,293 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof(v), f ); + + return LittleLong( v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof(v), f ); + + return LittleShort( v); +} + +/* +================= +S_ReadChunkInfo +================= +*/ +static int S_ReadChunkInfo(fileHandle_t f, char *name) +{ + int len, r; + + name[4] = 0; + + r = FS_Read(name, 4, f); + if(r != 4) + return -1; + + len = FGetLittleLong(f); + if( len < 0 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Negative chunk length\n" ); + return -1; + } + + return len; +} + +/* +================= +S_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int S_FindRIFFChunk( fileHandle_t f, const char *chunk ) { + char name[5]; + int len; + + while( ( len = S_ReadChunkInfo(f, name) ) >= 0 ) + { + // If this is the right chunk, return + if( !Q_strncmp( name, chunk, 4 ) ) + return len; + + len = PAD( len, 2 ); + + // Not the right chunk - skip it + FS_Seek( f, len, FS_SEEK_CUR ); + } + + return -1; +} + +/* +================= +S_ByteSwapRawSamples +================= +*/ +static void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +================= +S_ReadRIFFHeader +================= +*/ +static bool S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info) +{ + char dump[16]; + int bits; + int fmtlen = 0; + + // skip the riff wav header + FS_Read(dump, 12, file); + + // Scan for the format chunk + if((fmtlen = S_FindRIFFChunk(file, "fmt ")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"fmt\" chunk\n"); + return false; + } + + // Save the parameters + FGetLittleShort(file); // wav_format + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + bits = FGetLittleShort(file); + + if( bits < 8 ) + { + Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n"); + return false; + } + + info->width = bits / 8; + info->dataofs = 0; + + // Skip the rest of the format chunk if required + if(fmtlen > 16) + { + fmtlen -= 16; + FS_Seek( file, fmtlen, FS_SEEK_CUR ); + } + + // Scan for the data chunk + if( (info->size = S_FindRIFFChunk(file, "data")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"data\" chunk\n"); + return false; + } + info->samples = (info->size / info->width) / info->channels; + + return true; +} + +// WAV codec +snd_codec_t wav_codec = +{ + "wav", + S_WAV_CodecLoad, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecCloseStream, + NULL +}; + +/* +================= +S_WAV_CodecLoad +================= +*/ +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info) +{ + fileHandle_t file; + void *buffer; + + // Try to open the file + FS_FOpenFileRead(filename, &file, true); + if(!file) + { + return NULL; + } + + // Read the RIFF header + if(!S_ReadRIFFHeader(file, info)) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Incorrect/unsupported format in \"%s\"\n", + filename); + return NULL; + } + + // Allocate some memory + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Out of memory reading \"%s\"\n", + filename); + return NULL; + } + + // Read, byteswap + FS_Read(buffer, info->size, file); + S_ByteSwapRawSamples(info->samples, info->width, info->channels, (byte *)buffer); + + // Close and return + FS_FCloseFile(file); + return buffer; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +snd_stream_t *S_WAV_CodecOpenStream(const char *filename) +{ + snd_stream_t *rv; + + // Open + rv = S_CodecUtilOpen(filename, &wav_codec); + if(!rv) + return NULL; + + // Read the RIFF header + if(!S_ReadRIFFHeader(rv->file, &rv->info)) + { + S_CodecUtilClose(&rv); + return NULL; + } + + return rv; +} + +/* +================= +S_WAV_CodecCloseStream +================= +*/ +void S_WAV_CodecCloseStream(snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->pos; + int samples; + + if(remaining <= 0) + return 0; + if(bytes > remaining) + bytes = remaining; + stream->pos += bytes; + samples = (bytes / stream->info.width) / stream->info.channels; + FS_Read(buffer, bytes, stream->file); + S_ByteSwapRawSamples(samples, stream->info.width, stream->info.channels, (byte*)buffer); + return bytes; +} diff --git a/src/client/snd_dma.cpp b/src/client/snd_dma.cpp new file mode 100644 index 0000000..bef2b69 --- /dev/null +++ b/src/client/snd_dma.cpp @@ -0,0 +1,1644 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * $Archive: /MissionPack/code/client/snd_dma.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +void S_Update_( void ); +void S_Base_StopAllSounds(void); +void S_Base_StopBackgroundTrack( void ); + +snd_stream_t *s_backgroundStream = NULL; +static char s_backgroundLoop[MAX_QPATH]; +//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 80 + +#define SOUND_ATTENUATE 0.0008f + +channel_t s_channels[MAX_CHANNELS]; +channel_t loop_channels[MAX_CHANNELS]; +int numLoopChannels; + +static bool s_soundStarted; +static bool s_soundMuted; + +dma_t dma; + +static int listener_number; +static vec3_t listener_origin; +static vec3_t listener_axis[3]; + +int s_soundtime; // sample PAIRS +int s_paintedtime; // sample PAIRS + +// MAX_SFX may be larger than MAX_SOUNDS because +// of custom player sounds +#define MAX_SFX 4096 +sfx_t s_knownSfx[MAX_SFX]; +int s_numSfx = 0; + +#define LOOP_HASH 128 +static sfx_t *sfxHash[LOOP_HASH]; + +cvar_t *s_testsound; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_mixPreStep; + +static loopSound_t loopSounds[MAX_GENTITIES]; +static channel_t *freelist = NULL; + +int s_rawend[MAX_RAW_STREAMS]; +portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; + + +// ==================================================================== +// User-setable variables +// ==================================================================== + + +void S_Base_SoundInfo(void) { + Com_Printf("----- Sound Info -----\n" ); + if (!s_soundStarted) { + Com_Printf ("sound system not started\n"); + } else { + Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d samples\n", dma.samples); + 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("%p dma buffer\n", dma.buffer); + if ( s_backgroundStream ) { + Com_Printf("Background file: %s\n", s_backgroundLoop ); + } else { + Com_Printf("No background file.\n" ); + } + + } + Com_Printf("----------------------\n" ); +} + + +#ifdef USE_VOIP +static +void S_Base_StartCapture( void ) +{ + // !!! FIXME: write me. +} + +static +int S_Base_AvailableCaptureSamples( void ) +{ + // !!! FIXME: write me. + return 0; +} + +static +void S_Base_Capture( int samples, byte *data ) +{ + // !!! FIXME: write me. +} + +static +void S_Base_StopCapture( void ) +{ + // !!! FIXME: write me. +} + +static +void S_Base_MasterGain( float val ) +{ + // !!! FIXME: write me. +} +#endif + + + +/* +================= +S_Base_SoundList +================= +*/ +void S_Base_SoundList( void ) { + int i; + sfx_t *sfx; + int size, total; + char type[4][16]; + char mem[2][16]; + + strcpy(type[0], "16bit"); + strcpy(type[1], "adpcm"); + strcpy(type[2], "daub4"); + strcpy(type[3], "mulaw"); + strcpy(mem[0], "paged out"); + strcpy(mem[1], "resident "); + total = 0; + for (sfx=s_knownSfx, i=0 ; isoundLength; + total += size; + Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], + sfx->soundName, mem[sfx->inMemory] ); + } + Com_Printf ("Total resident: %i\n", total); + S_DisplayFreeMemory(); +} + + + +void S_ChannelFree(channel_t *v) { + v->thesfx = NULL; + *(channel_t **)v = freelist; + freelist = (channel_t*)v; +} + +channel_t* S_ChannelMalloc( void ) { + channel_t *v; + if (freelist == NULL) { + return NULL; + } + v = freelist; + freelist = *(channel_t **)freelist; + v->allocTime = Com_Milliseconds(); + return v; +} + +void S_ChannelSetup( void ) { + channel_t *p, *q; + + // clear all the sounds so they don't + ::memset( s_channels, 0, sizeof( s_channels ) ); + + p = s_channels;; + q = p + MAX_CHANNELS; + while (--q > p) { + *(channel_t **)q = q-1; + } + + *(channel_t **)q = NULL; + freelist = p + MAX_CHANNELS - 1; + Com_DPrintf("Channel memory manager started\n"); +} + + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================ +return a hash value for the sfx name +================ +*/ +static long S_HashSFXName(const char *name) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (name[i] != '\0') { + letter = tolower(name[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (LOOP_HASH-1); + return hash; +} + +/* +================== +S_FindName + +Will allocate a new sfx if it isn't found +================== +*/ +static sfx_t *S_FindName( const char *name ) { + int i; + int hash; + + sfx_t *sfx; + + if (!name) { + Com_Error(ERR_FATAL, "Sound name is NULL"); + } + + if (!name[0]) { + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" ); + return NULL; + } + + if (strlen(name) >= MAX_QPATH) { + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", name ); + return NULL; + } + + if (name[0] == '*') { + Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", name ); + return NULL; + } + + hash = S_HashSFXName(name); + + sfx = sfxHash[hash]; + // see if already loaded + while (sfx) { + if (!Q_stricmp(sfx->soundName, name) ) { + return sfx; + } + sfx = sfx->next; + } + + // find a free sfx + for (i=0 ; i < s_numSfx ; i++) { + if (!s_knownSfx[i].soundName[0]) { + break; + } + } + + if (i == s_numSfx) { + if (s_numSfx == MAX_SFX) { + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + } + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + ::memset (sfx, 0, sizeof(*sfx)); + strcpy (sfx->soundName, name); + + sfx->next = sfxHash[hash]; + sfxHash[hash] = sfx; + + return sfx; +} + +/* +================= +S_DefaultSound +================= +*/ +void S_DefaultSound( sfx_t *sfx ) { + + int i; + + sfx->soundLength = 512; + sfx->soundData = SND_malloc(); + sfx->soundData->next = NULL; + + + for ( i = 0 ; i < sfx->soundLength ; i++ ) { + sfx->soundData->sndChunk[i] = i; + } +} + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_Base_DisableSounds( void ) { + S_Base_StopAllSounds(); + s_soundMuted = true; +} + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_Base_RegisterSound( const char *name, bool compressed ) { + sfx_t *sfx; + + compressed = false; + if (!s_soundStarted) { + return 0; + } + + sfx = S_FindName( name ); + if ( !sfx ) { + return 0; + } + + if ( sfx->soundData ) { + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + return sfx - s_knownSfx; + } + + sfx->inMemory = false; + sfx->soundCompressed = compressed; + + S_memoryLoad(sfx); + + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + + return sfx - s_knownSfx; +} + +/* +================== +S_Base_SoundDuration +================== +*/ +static int S_Base_SoundDuration( sfxHandle_t handle ) { + if ( handle < 0 || handle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_Base_SoundDuration: handle %i out of range\n", handle ); + return 0; + } + return s_knownSfx[ handle ].duration; +} + + + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_Base_BeginRegistration( void ) { + s_soundMuted = false; // we can play again + + if (s_numSfx == 0) { + SND_setup(); + + ::memset(s_knownSfx, '\0', sizeof(s_knownSfx)); + ::memset(sfxHash, '\0', sizeof(sfx_t *) * LOOP_HASH); + + S_Base_RegisterSound("sound/feedback/hit.wav", false); // changed to a sound in baseq3 + } +} + +void S_memoryLoad(sfx_t *sfx) { + // load the sound file + if ( !S_LoadSound ( sfx ) ) { +// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); + sfx->defaultSound = true; + } + sfx->inMemory = true; +} + +//============================================================================= + +/* +================= +S_SpatializeOrigin + +Used for spatializing s_channels +================= +*/ +void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + vec3_t vec; + + const float dist_mult = SOUND_ATTENUATE; + + // 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 + + VectorRotate( source_vec, listener_axis, vec ); + + dot = -vec[1]; + + if (dma.channels == 1) + { // no attenuation = no spatialization + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 0.5 * (1.0 + dot); + lscale = 0.5 * (1.0 - dot); + if ( rscale < 0 ) { + rscale = 0; + } + if ( lscale < 0 ) { + lscale = 0; + } + } + + // add in distance effect + scale = (1.0 - dist) * rscale; + *right_vol = (master_vol * scale); + if (*right_vol < 0) + *right_vol = 0; + + scale = (1.0 - dist) * lscale; + *left_vol = (master_vol * scale); + if (*left_vol < 0) + *left_vol = 0; +} + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +/* +================= +S_Base_HearingThroughEntity + +Also see S_AL_HearingThroughEntity +================= +*/ +static bool S_Base_HearingThroughEntity( int entityNum, vec3_t origin ) +{ + float distanceSq; + vec3_t sorigin; + + if (origin) + VectorCopy(origin, sorigin); + else + VectorCopy(loopSounds[entityNum].origin, sorigin); + + if( listener_number == entityNum ) + { + // This is an outrageous hack to detect + // whether or not the player is rendering in third person or not. We can't + // ask the renderer because the renderer has no notion of entities and we + // can't ask cgame since that would involve changing the API and hence mod + // compatibility. I don't think there is any way around this, but I'll leave + // the FIXME just in case anyone has a bright idea. + distanceSq = DistanceSquared( + sorigin, + listener_origin ); + + if( distanceSq > THIRD_PERSON_THRESHOLD_SQ ) + return false; //we're the player, but third person + else + return true; //we're the player + } + else + return false; //not the player +} + +/* +==================== +S_Base_StartSoundEx + +Validates the parms and ques the sound up +if origin is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +static void S_Base_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, bool localSound ) { + channel_t *ch; + sfx_t *sfx; + int i, oldest, chosen, time; + int inplay, allowed; + bool fullVolume; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum >= MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == false) { + S_memoryLoad(sfx); + } + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); + } + + time = Com_Milliseconds(); + +// Com_Printf("playing %s\n", sfx->soundName); + // pick a channel to play on + + allowed = 4; + if (entityNum == listener_number) { + allowed = 8; + } + + fullVolume = false; + if (localSound || S_Base_HearingThroughEntity(entityNum, origin)) { + fullVolume = true; + } + + ch = s_channels; + inplay = 0; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum == entityNum && ch->thesfx == sfx) { + if (time - ch->allocTime < 50) { +// if (Cvar_VariableValue( "cg_showmiss" )) { +// Com_Printf("double sound start\n"); +// } + return; + } + inplay++; + } + } + + if (inplay>allowed) { + return; + } + + sfx->lastTimeUsed = time; + + ch = S_ChannelMalloc(); // entityNum, entchannel); + if (!ch) { + ch = s_channels; + + oldest = sfx->lastTimeUsed; + chosen = -1; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + if (ch->entnum == listener_number) { + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->allocTimeallocTime; + chosen = i; + } + } + } + if (chosen == -1) { + Com_DPrintf("dropping sound\n"); + return; + } + } + } + ch = &s_channels[chosen]; + ch->allocTime = sfx->lastTimeUsed; + } + + if (origin) { + VectorCopy (origin, ch->origin); + ch->fixed_origin = true; + } else { + ch->fixed_origin = false; + } + + ch->master_vol = 127; + ch->entnum = entityNum; + ch->thesfx = sfx; + ch->startSample = START_SAMPLE_IMMEDIATE; + ch->entchannel = entchannel; + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + ch->doppler = false; + ch->fullVolume = fullVolume; +} + +/* +==================== +S_StartSound + +if origin is NULL, the sound will be dynamically sourced from the entity +==================== +*/ +void S_Base_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { + S_Base_StartSoundEx( origin, entityNum, entchannel, sfxHandle, false ); +} + +/* +================== +S_StartLocalSound +================== +*/ +void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle ); + return; + } + + S_Base_StartSoundEx( NULL, listener_number, channelNum, sfxHandle, true ); +} + + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_Base_ClearSoundBuffer( void ) { + int clear; + + if (!s_soundStarted) + return; + + // stop looping sounds + ::memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); + ::memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); + numLoopChannels = 0; + + S_ChannelSetup(); + + ::memset(s_rawend, '\0', sizeof (s_rawend)); + + 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_Base_StopAllSounds(void) { + if ( !s_soundStarted ) { + return; + } + + // stop the background music + S_Base_StopBackgroundTrack(); + + S_Base_ClearSoundBuffer (); +} + +/* +============================================================== + +continuous looping sounds are added each frame + +============================================================== +*/ + +void S_Base_StopLoopingSound(int entityNum) { + loopSounds[entityNum].active = false; +// loopSounds[entityNum].sfx = 0; + loopSounds[entityNum].kill = false; +} + +/* +================== +S_ClearLoopingSounds + +================== +*/ +void S_Base_ClearLoopingSounds( bool killall ) +{ + int i; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + if (killall || loopSounds[i].kill == true || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { + S_Base_StopLoopingSound(i); + } + } + numLoopChannels = 0; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == false) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].active = true; + loopSounds[entityNum].kill = true; + loopSounds[entityNum].doppler = false; + loopSounds[entityNum].oldDopplerScale = 1.0; + loopSounds[entityNum].dopplerScale = 1.0; + loopSounds[entityNum].sfx = sfx; + + if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) { + vec3_t out; + float lena, lenb; + + loopSounds[entityNum].doppler = true; + lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); + VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); + lenb = DistanceSquared(loopSounds[listener_number].origin, out); + if ((loopSounds[entityNum].framenum+1) != cls.framecount) { + loopSounds[entityNum].oldDopplerScale = 1.0; + } else { + loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; + } + loopSounds[entityNum].dopplerScale = lenb/(lena*100); + if (loopSounds[entityNum].dopplerScale<=1.0) { + loopSounds[entityNum].doppler = false; // don't bother doing the math + } else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) { + loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE; + } + } + + loopSounds[entityNum].framenum = cls.framecount; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == false) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].sfx = sfx; + loopSounds[entityNum].active = true; + loopSounds[entityNum].kill = false; + loopSounds[entityNum].doppler = false; +} + + + +/* +================== +S_AddLoopSounds + +Spatialize all of the looping sounds. +All sounds are on the same cycle, so any duplicates can just +sum up the channel multipliers. +================== +*/ +void S_AddLoopSounds (void) { + int i, j, time; + int left_total, right_total, left, right; + channel_t *ch; + loopSound_t *loop, *loop2; + static int loopFrame; + + + numLoopChannels = 0; + + time = Com_Milliseconds(); + + loopFrame++; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + loop = &loopSounds[i]; + if ( !loop->active || loop->mergeFrame == loopFrame ) { + continue; // already merged into an earlier sound + } + + if (loop->kill) { + S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d + } else { + S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere + } + + loop->sfx->lastTimeUsed = time; + + for (j=(i+1); j< MAX_GENTITIES ; j++) { + loop2 = &loopSounds[j]; + if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) { + continue; + } + loop2->mergeFrame = loopFrame; + + if (loop2->kill) { + S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d + } else { + S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere + } + + loop2->sfx->lastTimeUsed = time; + left_total += left; + right_total += right; + } + if (left_total == 0 && right_total == 0) { + continue; // not audible + } + + // allocate a channel + ch = &loop_channels[numLoopChannels]; + + if (left_total > 255) { + left_total = 255; + } + if (right_total > 255) { + right_total = 255; + } + + ch->master_vol = 127; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->thesfx = loop->sfx; + ch->doppler = loop->doppler; + ch->dopplerScale = loop->dopplerScale; + ch->oldDopplerScale = loop->oldDopplerScale; + ch->fullVolume = false; + numLoopChannels++; + if (numLoopChannels == MAX_CHANNELS) { + return; + } + } +} + +//============================================================================= + +/* +================= +S_ByteSwapRawSamples + +If raw data has been loaded in little endien binary form, this must be done. +If raw data was calculated, as with ADPCM, this should not be called. +================= +*/ +void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +============ +S_Base_RawSamples + +Music streaming +============ +*/ +void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum) +{ + int i; + int src, dst; + float scale; + int intVolumeLeft, intVolumeRight; + portable_samplepair_t *rawsamples; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) { + return; + } + + rawsamples = s_rawsamples[stream]; + + if ( s_muted->integer ) { + intVolumeLeft = intVolumeRight = 0; + } else { + int leftvol, rightvol; + + if ( entityNum >= 0 && entityNum < MAX_GENTITIES ) { + // support spatialized raw streams, e.g. for VoIP + S_SpatializeOrigin( loopSounds[ entityNum ].origin, 256, &leftvol, &rightvol ); + } else { + leftvol = rightvol = 256; + } + + intVolumeLeft = leftvol * volume * s_volume->value; + intVolumeRight = rightvol * volume * s_volume->value; + } + + if ( s_rawend[stream] < s_soundtime ) { + Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime ); + s_rawend[stream] = s_soundtime; + } + + scale = (float)rate / dma.speed; + +//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]); + if (s_channels == 2 && width == 2) + { + if (scale == 1.0) + { // optimized case + for (i=0 ; i= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src*2] * intVolumeLeft; + rawsamples[dst].right = ((short *)data)[src*2+1] * intVolumeRight; + } + } + } + else if (s_channels == 1 && width == 2) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src] * intVolumeLeft; + rawsamples[dst].right = ((short *)data)[src] * intVolumeRight; + } + } + else if (s_channels == 2 && width == 1) + { + intVolumeLeft *= 256; + intVolumeRight *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((char *)data)[src*2] * intVolumeLeft; + rawsamples[dst].right = ((char *)data)[src*2+1] * intVolumeRight; + } + } + else if (s_channels == 1 && width == 1) + { + intVolumeLeft *= 256; + intVolumeRight *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = (((byte *)data)[src]-128) * intVolumeLeft; + rawsamples[dst].right = (((byte *)data)[src]-128) * intVolumeRight; + } + } + + if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime ); + } +} + +//============================================================================= + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); +} + + +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { + int i; + channel_t *ch; + vec3_t origin; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + listener_number = entityNum; + VectorCopy(head, listener_origin); + VectorCopy(axis[0], listener_axis[0]); + VectorCopy(axis[1], listener_axis[1]); + VectorCopy(axis[2], listener_axis[2]); + + // update spatialization for dynamic sounds + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + // local and first person sounds will always be full volume + if (ch->fullVolume) { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + } else { + if (ch->fixed_origin) { + VectorCopy( ch->origin, origin ); + } else { + VectorCopy( loopSounds[ ch->entnum ].origin, origin ); + } + + S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); + } + } + + // add loopsounds + S_AddLoopSounds (); +} + + +/* +======================== +S_ScanChannelStarts + +Returns true if any new sounds were started since the last mix +======================== +*/ +bool S_ScanChannelStarts( void ) { + channel_t *ch; + int i; + bool newSamples; + + newSamples = false; + ch = s_channels; + + for (i=0; ithesfx ) { + continue; + } + // if this channel was just started this frame, + // set the sample count to it begins mixing + // into the very first sample + if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { + ch->startSample = s_paintedtime; + newSamples = true; + continue; + } + + // if it is completely finished by now, clear it + if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) { + S_ChannelFree(ch); + } + } + + return newSamples; +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Base_Update( void ) { + int i; + int total; + channel_t *ch; + + if ( !s_soundStarted || s_soundMuted ) { +// Com_DPrintf ("not started or muted\n"); + return; + } + + // + // debugging output + // + if ( s_show->integer == 2 ) { + total = 0; + ch = s_channels; + for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { + Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); + total++; + } + } + + Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); + } + + // add raw data from streamed samples + S_UpdateBackgroundTrack(); + + // mix some sound + S_Update_(); +} + +void S_GetSoundtime(void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.channels; + + if( CL_VideoRecording( ) ) + { + float fps = MIN(cl_aviFrameRate->value, 1000.0f); + float frameDuration = MAX(dma.speed / fps, 1.0f) + clc.aviSoundFrameRemainder; + + int msec = (int)frameDuration; + s_soundtime += msec; + clc.aviSoundFrameRemainder = frameDuration - msec; + + return; + } + + // 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 (s_paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + s_paintedtime = fullsamples; + S_Base_StopAllSounds (); + } + } + oldsamplepos = samplepos; + + s_soundtime = buffers*fullsamples + samplepos/dma.channels; + +#if 0 +// check to make sure that we haven't overshot + if (s_paintedtime < s_soundtime) + { + Com_DPrintf ("S_Update_ : overflow\n"); + s_paintedtime = s_soundtime; + } +#endif + + if ( dma.submission_chunk < 256 ) { + s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; + } else { + s_paintedtime = s_soundtime + dma.submission_chunk; + } +} + + +void S_Update_(void) { + unsigned endtime; + int samps; + static float lastTime = 0.0f; + float ma, op; + float thisTime, sane; + static int ot = -1; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + thisTime = Com_Milliseconds(); + + // Updates s_soundtime + S_GetSoundtime(); + + if (s_soundtime == ot) { + return; + } + ot = s_soundtime; + + // clear any sound effects that end before the current time, + // and start any new sounds + S_ScanChannelStarts(); + + sane = thisTime - lastTime; + if (sane<11) { + sane = 11; // 85hz + } + + ma = s_mixahead->value * dma.speed; + op = s_mixPreStep->value + sane*dma.speed*0.01; + + if (op < ma) { + ma = op; + } + + // mix ahead of current position + endtime = s_soundtime + ma; + + // mix to an even submission block size + endtime = (endtime + dma.submission_chunk-1) + & ~(dma.submission_chunk-1); + + // never mix more than the complete buffer + samps = dma.samples >> (dma.channels-1); + if (endtime - s_soundtime > samps) + endtime = s_soundtime + samps; + + + + SNDDMA_BeginPainting (); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); + + lastTime = thisTime; +} + + + +/* +=============================================================================== + +background music functions + +=============================================================================== +*/ + +/* +====================== +S_StopBackgroundTrack +====================== +*/ +void S_Base_StopBackgroundTrack( void ) { + if(!s_backgroundStream) + return; + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + s_rawend[0] = 0; +} + +/* +====================== +S_OpenBackgroundStream +====================== +*/ +static void S_OpenBackgroundStream( const char *filename ) { + // close the background track, but DON'T reset s_rawend + // if restarting the same back ground track + if(s_backgroundStream) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + } + + // Open stream + s_backgroundStream = S_CodecOpenStream(filename); + if(!s_backgroundStream) { + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", filename ); + return; + } + + if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) { + Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", filename ); + } +} + +/* +====================== +S_StartBackgroundTrack +====================== +*/ +void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){ + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); + + if(!*intro) + { + S_Base_StopBackgroundTrack(); + return; + } + + Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + S_OpenBackgroundStream( intro ); +} + +/* +====================== +S_UpdateBackgroundTrack +====================== +*/ +void S_UpdateBackgroundTrack( void ) { + int bufferSamples; + int fileSamples; + byte raw[30000]; // just enough to fit in a mac stack frame + int fileBytes; + int r; + + if(!s_backgroundStream) { + return; + } + + // don't bother playing anything if musicvolume is 0 + if ( s_musicVolume->value <= 0 ) { + return; + } + + // see how many samples should be copied into the raw buffer + if ( s_rawend[0] < s_soundtime ) { + s_rawend[0] = s_soundtime; + } + + while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed; + + if (!fileSamples) + return; + + // our max buffer size + fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels); + if ( fileBytes > sizeof(raw) ) { + fileBytes = sizeof(raw); + fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + // Read + r = S_CodecReadStream(s_backgroundStream, fileBytes, raw); + if(r < fileBytes) + { + fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + if(r > 0) + { + // add to raw buffer + S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate, + s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1); + } + else + { + // loop + if(s_backgroundLoop[0]) + { + S_OpenBackgroundStream( s_backgroundLoop ); + if(!s_backgroundStream) + return; + } + else + { + S_Base_StopBackgroundTrack(); + return; + } + } + + } +} + + +/* +====================== +S_FreeOldestSound +====================== +*/ + +void S_FreeOldestSound( void ) { + int i, oldest, used; + sfx_t *sfx; + sndBuffer *buffer, *nbuffer; + + oldest = Com_Milliseconds(); + used = 0; + + for (i=1 ; i < s_numSfx ; i++) { + sfx = &s_knownSfx[i]; + if (sfx->inMemory && sfx->lastTimeUsedlastTimeUsed; + } + } + + sfx = &s_knownSfx[used]; + + Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); + + buffer = sfx->soundData; + while(buffer != NULL) { + nbuffer = buffer->next; + SND_free(buffer); + buffer = nbuffer; + } + sfx->inMemory = false; + sfx->soundData = NULL; +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Base_Shutdown( void ) { + if ( !s_soundStarted ) { + return; + } + + SNDDMA_Shutdown(); + SND_shutdown(); + + s_soundStarted = false; + s_numSfx = 0; + + Cmd_RemoveCommand("s_info"); +} + +/* +================ +S_Init +================ +*/ +bool S_Base_Init( soundInterface_t *si ) +{ + + if( !si ) { + return false; + } + + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); + + bool r = SNDDMA_Init(); + if ( r ) + { + s_soundStarted = true; + s_soundMuted = true; +// s_numSfx = 0; + + ::memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + + s_soundtime = 0; + s_paintedtime = 0; + + S_Base_StopAllSounds( ); + } else { + return false; + } + + si->Shutdown = S_Base_Shutdown; + si->StartSound = S_Base_StartSound; + si->StartLocalSound = S_Base_StartLocalSound; + si->StartBackgroundTrack = S_Base_StartBackgroundTrack; + si->StopBackgroundTrack = S_Base_StopBackgroundTrack; + si->RawSamples = S_Base_RawSamples; + si->StopAllSounds = S_Base_StopAllSounds; + si->ClearLoopingSounds = S_Base_ClearLoopingSounds; + si->AddLoopingSound = S_Base_AddLoopingSound; + si->AddRealLoopingSound = S_Base_AddRealLoopingSound; + si->StopLoopingSound = S_Base_StopLoopingSound; + si->Respatialize = S_Base_Respatialize; + si->UpdateEntityPosition = S_Base_UpdateEntityPosition; + si->Update = S_Base_Update; + si->DisableSounds = S_Base_DisableSounds; + si->BeginRegistration = S_Base_BeginRegistration; + si->RegisterSound = S_Base_RegisterSound; + si->SoundDuration = S_Base_SoundDuration; + si->ClearSoundBuffer = S_Base_ClearSoundBuffer; + si->SoundInfo = S_Base_SoundInfo; + si->SoundList = S_Base_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_Base_StartCapture; + si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples; + si->Capture = S_Base_Capture; + si->StopCapture = S_Base_StopCapture; + si->MasterGain = S_Base_MasterGain; +#endif + + return true; +} diff --git a/src/client/snd_local.h b/src/client/snd_local.h new file mode 100644 index 0000000..38f26c4 --- /dev/null +++ b/src/client/snd_local.h @@ -0,0 +1,267 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// snd_local.h -- private sound definations + +#ifndef _SND_LOCAL_H +#define _SND_LOCAL_H + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +#include "snd_public.h" + +typedef struct cvar_s cvar_t; + +#define PAINTBUFFER_SIZE 4096 // this is in samples + +#define SND_CHUNK_SIZE 1024 // samples +#define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats +#define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats + +typedef struct { + int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down + int right; +} portable_samplepair_t; + +typedef struct adpcm_state { + short sample; /* Previous output value */ + char index; /* Index into stepsize table */ +} adpcm_state_t; + +typedef struct sndBuffer_s { + short sndChunk[SND_CHUNK_SIZE]; + struct sndBuffer_s *next; + int size; + adpcm_state_t adpcm; +} sndBuffer; + +typedef struct sfx_s { + sndBuffer *soundData; + bool defaultSound; // couldn't be loaded, so use buzz + bool inMemory; // not in Memory + bool soundCompressed; // not in Memory + int soundCompressionMethod; + int soundLength; + int soundChannels; + char soundName[MAX_QPATH]; + int lastTimeUsed; + int duration; + struct sfx_s *next; +} sfx_t; + +typedef struct { + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplebits; + int speed; + byte *buffer; +} dma_t; + +#define START_SAMPLE_IMMEDIATE 0x7fffffff + +#define MAX_DOPPLER_SCALE 50.0f //arbitrary + +#define THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f) + +typedef struct loopSound_s { + vec3_t origin; + vec3_t velocity; + sfx_t *sfx; + int mergeFrame; + bool active; + bool kill; + bool doppler; + float dopplerScale; + float oldDopplerScale; + int framenum; +} loopSound_t; + +typedef struct +{ + sfx_t *thesfx; // sfx structure + int allocTime; + int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int leftvol; // 0-255 volume after spatialization + int rightvol; // 0-255 volume after spatialization + int master_vol; // 0-255 volume before spatialization + float dopplerScale; + float oldDopplerScale; + vec3_t origin; // only use if fixed_origin is set + bool fixed_origin; // use origin instead of fetching entnum's origin + bool doppler; + bool fullVolume; +} channel_t; + + +#define WAV_FORMAT_PCM 1 + + +typedef struct { + int format; + int rate; + int width; + int channels; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + +// Interface between Q3 sound "api" and the sound backend +typedef struct +{ + void (*Shutdown)(void); + void (*StartSound)( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); + void (*StartLocalSound)( sfxHandle_t sfx, int channelNum ); + void (*StartBackgroundTrack)( const char *intro, const char *loop ); + void (*StopBackgroundTrack)( void ); + void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum); + void (*StopAllSounds)( void ); + void (*ClearLoopingSounds)( bool killall ); + void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*AddRealLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*StopLoopingSound)(int entityNum ); + void (*Respatialize)( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + void (*UpdateEntityPosition)( int entityNum, const vec3_t origin ); + void (*Update)( void ); + void (*DisableSounds)( void ); + void (*BeginRegistration)( void ); + sfxHandle_t (*RegisterSound)( const char *sample, bool compressed ); + int (*SoundDuration)( sfxHandle_t handle ); + void (*ClearSoundBuffer)( void ); + void (*SoundInfo)( void ); + void (*SoundList)( void ); +#ifdef USE_VOIP + void (*StartCapture)( void ); + int (*AvailableCaptureSamples)( void ); + void (*Capture)( int samples, byte *data ); + void (*StopCapture)( void ); + void (*MasterGain)( float gain ); +#endif +} soundInterface_t; + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +bool 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 96 + +extern channel_t s_channels[MAX_CHANNELS]; +extern channel_t loop_channels[MAX_CHANNELS]; +extern int numLoopChannels; + +extern int s_paintedtime; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; + +#define MAX_RAW_SAMPLES 16384 +#define MAX_RAW_STREAMS (MAX_CLIENTS * 2 + 1) +extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; +extern int s_rawend[MAX_RAW_STREAMS]; + +extern cvar_t *s_volume; +extern cvar_t *s_musicVolume; +extern cvar_t *s_muted; +extern cvar_t *s_doppler; + +extern cvar_t *s_testsound; + +bool S_LoadSound( sfx_t *sfx ); + +void SND_free(sndBuffer *v); +sndBuffer* SND_malloc( void ); +void SND_setup( void ); +void SND_shutdown(void); + +void S_PaintChannels(int endtime); + +void S_memoryLoad(sfx_t *sfx); + +// spatializes a channel +void S_Spatialize(channel_t *ch); + +// adpcm functions +int S_AdpcmMemoryNeeded( const wavinfo_t *info ); +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); +void S_AdpcmGetSamples(sndBuffer *chunk, short *to); + +// wavelet function + +#define SENTINEL_MULAW_ZERO_RUN 127 +#define SENTINEL_MULAW_FOUR_BIT_RUN 126 + +void S_FreeOldestSound( void ); + +#define NXStream byte + +void encodeWavelet(sfx_t *sfx, short *packets); +void decodeWavelet( sndBuffer *stream, short *packets); + +void encodeMuLaw( sfx_t *sfx, short *packets); +extern short mulawToShort[256]; + +extern short *sfxScratchBuffer; +extern sfx_t *sfxScratchPointer; +extern int sfxScratchIndex; + +bool S_Base_Init( soundInterface_t *si ); + +// OpenAL stuff +typedef enum +{ + SRCPRI_AMBIENT = 0, // Ambient sound effects + SRCPRI_ENTITY, // Entity sound effects + SRCPRI_ONESHOT, // One-shot sounds + SRCPRI_LOCAL, // Local sounds + SRCPRI_STREAM // Streams (music, cutscenes) +} alSrcPriority_t; + +typedef int srcHandle_t; + +bool S_AL_Init( soundInterface_t *si ); + +#endif diff --git a/src/client/snd_main.cpp b/src/client/snd_main.cpp new file mode 100644 index 0000000..a6873f1 --- /dev/null +++ b/src/client/snd_main.cpp @@ -0,0 +1,566 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" +#include "snd_local.h" +#include "snd_public.h" + +cvar_t *s_volume; +cvar_t *s_muted; +cvar_t *s_musicVolume; +cvar_t *s_doppler; +cvar_t *s_backend; +cvar_t *s_muteWhenMinimized; +cvar_t *s_muteWhenUnfocused; + +static soundInterface_t si; + +/* +================= +S_ValidateInterface +================= +*/ +static bool S_ValidSoundInterface( soundInterface_t *si ) +{ + if( !si->Shutdown ) return false; + if( !si->StartSound ) return false; + if( !si->StartLocalSound ) return false; + if( !si->StartBackgroundTrack ) return false; + if( !si->StopBackgroundTrack ) return false; + if( !si->RawSamples ) return false; + if( !si->StopAllSounds ) return false; + if( !si->ClearLoopingSounds ) return false; + if( !si->AddLoopingSound ) return false; + if( !si->AddRealLoopingSound ) return false; + if( !si->StopLoopingSound ) return false; + if( !si->Respatialize ) return false; + if( !si->UpdateEntityPosition ) return false; + if( !si->Update ) return false; + if( !si->DisableSounds ) return false; + if( !si->BeginRegistration ) return false; + if( !si->RegisterSound ) return false; + if( !si->SoundDuration ) return false; + if( !si->ClearSoundBuffer ) return false; + if( !si->SoundInfo ) return false; + if( !si->SoundList ) return false; + +#ifdef USE_VOIP + if( !si->StartCapture ) return false; + if( !si->AvailableCaptureSamples ) return false; + if( !si->Capture ) return false; + if( !si->StopCapture ) return false; + if( !si->MasterGain ) return false; +#endif + + return true; +} + +/* +================= +S_StartSound +================= +*/ +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + if( si.StartSound ) { + si.StartSound( origin, entnum, entchannel, sfx ); + } +} + +/* +================= +S_StartLocalSound +================= +*/ +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ + if( si.StartLocalSound ) { + si.StartLocalSound( sfx, channelNum ); + } +} + +/* +================= +S_StartBackgroundTrack +================= +*/ +void S_StartBackgroundTrack( const char *intro, const char *loop ) +{ + if( si.StartBackgroundTrack ) { + si.StartBackgroundTrack( intro, loop ); + } +} + +/* +================= +S_StopBackgroundTrack +================= +*/ +void S_StopBackgroundTrack( void ) +{ + if( si.StopBackgroundTrack ) { + si.StopBackgroundTrack( ); + } +} + +/* +================= +S_RawSamples +================= +*/ +void S_RawSamples (int stream, int samples, int rate, int width, int channels, + const byte *data, float volume, int entityNum) +{ + if(si.RawSamples) + si.RawSamples(stream, samples, rate, width, channels, data, volume, entityNum); +} + +/* +================= +S_StopAllSounds +================= +*/ +void S_StopAllSounds( void ) +{ + if( si.StopAllSounds ) { + si.StopAllSounds( ); + } +} + +/* +================= +S_ClearLoopingSounds +================= +*/ +void S_ClearLoopingSounds( bool killall ) +{ + if( si.ClearLoopingSounds ) { + si.ClearLoopingSounds( killall ); + } +} + +/* +================= +S_AddLoopingSound +================= +*/ +void S_AddLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) +{ + if( si.AddLoopingSound ) { + si.AddLoopingSound( entityNum, origin, velocity, sfx ); + } +} + +/* +================= +S_AddRealLoopingSound +================= +*/ +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) +{ + if( si.AddRealLoopingSound ) { + si.AddRealLoopingSound( entityNum, origin, velocity, sfx ); + } +} + +/* +================= +S_StopLoopingSound +================= +*/ +void S_StopLoopingSound( int entityNum ) +{ + if( si.StopLoopingSound ) { + si.StopLoopingSound( entityNum ); + } +} + +/* +================= +S_Respatialize +================= +*/ +void S_Respatialize( int entityNum, const vec3_t origin, + vec3_t axis[3], int inwater ) +{ + if( si.Respatialize ) { + si.Respatialize( entityNum, origin, axis, inwater ); + } +} + +/* +================= +S_UpdateEntityPosition +================= +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + if( si.UpdateEntityPosition ) { + si.UpdateEntityPosition( entityNum, origin ); + } +} + +/* +================= +S_Update +================= +*/ +void S_Update( void ) +{ + if(s_muted->integer) + { + if(!(s_muteWhenMinimized->integer && com_minimized->integer) && + !(s_muteWhenUnfocused->integer && com_unfocused->integer)) + { + s_muted->integer = false; + s_muted->modified = true; + } + } + else + { + if((s_muteWhenMinimized->integer && com_minimized->integer) || + (s_muteWhenUnfocused->integer && com_unfocused->integer)) + { + s_muted->integer = true; + s_muted->modified = true; + } + } + + if( si.Update ) { + si.Update( ); + } +} + +/* +================= +S_DisableSounds +================= +*/ +void S_DisableSounds( void ) +{ + if( si.DisableSounds ) { + si.DisableSounds( ); + } +} + +/* +================= +S_BeginRegistration +================= +*/ +void S_BeginRegistration( void ) +{ + if( si.BeginRegistration ) { + si.BeginRegistration( ); + } +} + +/* +================= +S_RegisterSound +================= +*/ +sfxHandle_t S_RegisterSound( const char *sample, bool compressed ) +{ + if( si.RegisterSound ) { + return si.RegisterSound( sample, compressed ); + } else { + return 0; + } +} + +/* +================= +S_SoundDuration +================= +*/ +int S_SoundDuration( sfxHandle_t handle ) +{ + if( si.SoundDuration ) + return si.SoundDuration( handle ); + else + return 0; +} + +/* +================= +S_ClearSoundBuffer +================= +*/ +void S_ClearSoundBuffer( void ) +{ + if( si.ClearSoundBuffer ) { + si.ClearSoundBuffer( ); + } +} + +/* +================= +S_SoundInfo +================= +*/ +void S_SoundInfo( void ) +{ + if( si.SoundInfo ) { + si.SoundInfo( ); + } +} + +/* +================= +S_SoundList +================= +*/ +void S_SoundList( void ) +{ + if( si.SoundList ) { + si.SoundList( ); + } +} + + +#ifdef USE_VOIP +/* +================= +S_StartCapture +================= +*/ +void S_StartCapture( void ) +{ + if( si.StartCapture ) { + si.StartCapture( ); + } +} + +/* +================= +S_AvailableCaptureSamples +================= +*/ +int S_AvailableCaptureSamples( void ) +{ + if( si.AvailableCaptureSamples ) { + return si.AvailableCaptureSamples( ); + } + return 0; +} + +/* +================= +S_Capture +================= +*/ +void S_Capture( int samples, byte *data ) +{ + if( si.Capture ) { + si.Capture( samples, data ); + } +} + +/* +================= +S_StopCapture +================= +*/ +void S_StopCapture( void ) +{ + if( si.StopCapture ) { + si.StopCapture( ); + } +} + +/* +================= +S_MasterGain +================= +*/ +void S_MasterGain( float gain ) +{ + if( si.MasterGain ) { + si.MasterGain( gain ); + } +} +#endif + +//============================================================================= + +/* +================= +S_Play_f +================= +*/ +void S_Play_f( void ) { + int i; + int c; + sfxHandle_t h; + + if( !si.RegisterSound || !si.StartLocalSound ) { + return; + } + + c = Cmd_Argc(); + + if( c < 2 ) { + Com_Printf ("Usage: play [sound filename] [sound filename] ...\n"); + return; + } + + for( i = 1; i < c; i++ ) { + h = si.RegisterSound( Cmd_Argv(i), false ); + + if( h ) { + si.StartLocalSound( h, CHAN_LOCAL_SOUND ); + } + } +} + +/* +================= +S_Music_f +================= +*/ +void S_Music_f( void ) { + int c; + + if( !si.StartBackgroundTrack ) { + return; + } + + c = Cmd_Argc(); + + if ( c == 2 ) { + si.StartBackgroundTrack( Cmd_Argv(1), NULL ); + } else if ( c == 3 ) { + si.StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(2) ); + } else { + Com_Printf ("Usage: music [loopfile]\n"); + return; + } + +} + +/* +================= +S_Music_f +================= +*/ +void S_StopMusic_f( void ) +{ + if(!si.StopBackgroundTrack) + return; + + si.StopBackgroundTrack(); +} + + +//============================================================================= + +/* +================= +S_Init +================= +*/ +void S_Init( void ) +{ + cvar_t *cv; + bool started = false; + + Com_Printf( "------ Initializing Sound ------\n" ); + + s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE ); + s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE ); + s_muted = Cvar_Get("s_muted", "0", CVAR_ROM); + s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE ); + s_backend = Cvar_Get( "s_backend", "", CVAR_ROM ); + s_muteWhenMinimized = Cvar_Get( "s_muteWhenMinimized", "0", CVAR_ARCHIVE ); + s_muteWhenUnfocused = Cvar_Get( "s_muteWhenUnfocused", "0", CVAR_ARCHIVE ); + + cv = Cvar_Get( "s_initsound", "1", 0 ); + if( !cv->integer ) { + Com_Printf( "Sound disabled.\n" ); + } else { + + S_CodecInit( ); + + Cmd_AddCommand( "play", S_Play_f ); + Cmd_AddCommand( "music", S_Music_f ); + Cmd_AddCommand( "stopmusic", S_StopMusic_f ); + Cmd_AddCommand( "s_list", S_SoundList ); + Cmd_AddCommand( "s_stop", S_StopAllSounds ); + Cmd_AddCommand( "s_info", S_SoundInfo ); + + cv = Cvar_Get( "s_useOpenAL", "0", CVAR_ARCHIVE ); + if( cv->integer ) { + //OpenAL + started = S_AL_Init( &si ); + Cvar_Set( "s_backend", "OpenAL" ); + } + + if( !started ) { + started = S_Base_Init( &si ); + Cvar_Set( "s_backend", "base" ); + } + + if( started ) { + if( !S_ValidSoundInterface( &si ) ) { + Com_Error( ERR_FATAL, "Sound interface invalid" ); + } + + S_SoundInfo( ); + Com_Printf( "Sound initialization successful.\n" ); + } else { + Com_Printf( "Sound initialization failed.\n" ); + } + } + + Com_Printf( "--------------------------------\n"); +} + +/* +================= +S_Shutdown +================= +*/ +void S_Shutdown( void ) +{ + if( si.Shutdown ) + { + si.Shutdown( ); + } + + ::memset( &si, 0, sizeof( soundInterface_t ) ); + + Cmd_RemoveCommand( "play" ); + Cmd_RemoveCommand( "music"); + Cmd_RemoveCommand( "stopmusic"); + Cmd_RemoveCommand( "s_list" ); + Cmd_RemoveCommand( "s_stop" ); + Cmd_RemoveCommand( "s_info" ); + + S_CodecShutdown( ); +} diff --git a/src/client/snd_mem.cpp b/src/client/snd_mem.cpp new file mode 100644 index 0000000..128a471 --- /dev/null +++ b/src/client/snd_mem.cpp @@ -0,0 +1,297 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_mem.c + * + * desc: sound caching + *****************************************************************************/ + +#include "snd_codec.h" +#include "snd_local.h" + +#include "qcommon/cvar.h" + +#define DEF_COMSOUNDMEGS "8" + +/* +=============================================================================== + +memory management + +=============================================================================== +*/ + +static sndBuffer *buffer = NULL; +static sndBuffer *freelist = NULL; +static int inUse = 0; +static int totalInUse = 0; + +short *sfxScratchBuffer = NULL; +sfx_t *sfxScratchPointer = NULL; +int sfxScratchIndex = 0; + +void SND_free(sndBuffer *v) +{ + *(sndBuffer **)v = freelist; + freelist = (sndBuffer *)v; + inUse += sizeof(sndBuffer); +} + +sndBuffer *SND_malloc(void) +{ + sndBuffer *v; +redo: + if (freelist == NULL) + { + S_FreeOldestSound(); + goto redo; + } + + inUse -= sizeof(sndBuffer); + totalInUse += sizeof(sndBuffer); + + v = freelist; + freelist = *(sndBuffer **)freelist; + v->next = NULL; + + return v; +} + +void SND_setup(void) +{ + cvar_t* cv = Cvar_Get("com_soundMegs", + DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE); + int scs = cv->integer * 1536; + + buffer = (sndBuffer*)malloc(scs * sizeof(sndBuffer)); + + // allocate the stack based hunk allocator + sfxScratchBuffer = (short*)malloc(SND_CHUNK_SIZE * sizeof(short) * 4); + sfxScratchPointer = NULL; + + inUse = scs * sizeof(sndBuffer); + sndBuffer* p = buffer; + sndBuffer* q = p + scs; + while (--q > p) + { + *(sndBuffer **)q = q - 1; + } + + *(sndBuffer **)q = NULL; + freelist = p + scs - 1; + + Com_Printf("Sound memory manager started\n"); +} + +void SND_shutdown(void) +{ + free(sfxScratchBuffer); + free(buffer); +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static int ResampleSfx(sfx_t *sfx, int channels, int inrate, int inwidth, int samples, byte *data, bool compressed) +{ + float stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + int outcount = samples / stepscale; + int fracstep = stepscale * 256 * channels; + sndBuffer* chunk = sfx->soundData; + + int samplefrac = 0; + int srcsample = 0; + + for (int i = 0; i < outcount; i++) + { + srcsample += samplefrac >> 8; + samplefrac &= 255; + samplefrac += fracstep; + + for (int j = 0; j < channels; j++) + { + int sample; + + if (inwidth == 2) + { + sample = ((short *)data)[srcsample + j]; + } + else + { + sample = (int)((unsigned char)(data[srcsample + j]) - 128) << 8; + } + + int part = (i * channels + j) & (SND_CHUNK_SIZE - 1); + if (part == 0) + { + sndBuffer *newchunk = SND_malloc(); + + if (chunk == NULL) + { + sfx->soundData = newchunk; + } + else + { + chunk->next = newchunk; + } + chunk = newchunk; + } + + chunk->sndChunk[part] = sample; + } + } + + return outcount; +} + +/* +================ +ResampleSfxRaw + +resample / decimate to the current source rate +================ +*/ +static int ResampleSfxRaw(short *sfx, int channels, int inrate, int inwidth, int samples, byte *data) +{ + float stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + int outcount = samples / stepscale; + int fracstep = stepscale * 256 * channels; + + int samplefrac = 0; + int srcsample = 0; + + for (int i = 0; i < outcount; i++) + { + srcsample += samplefrac >> 8; + srcsample &= 255; + samplefrac += fracstep; + + for (int j = 0; j < channels; j++) + { + int sample; + if (inwidth == 2) + { + sample = LittleShort(((short *)data)[srcsample + j]); + } + else + { + sample = (int)((unsigned char)(data[srcsample + j]) - 128) << 8; + } + sfx[i * channels + j] = sample; + } + } + return outcount; +} + +//============================================================================= + +/* +============== +S_LoadSound + +The filename may be different than sfx->name in the case +of a forced fallback of a player specific sound +============== +*/ +bool S_LoadSound(sfx_t *sfx) +{ + snd_info_t info; + byte *data = (byte*)S_CodecLoad(sfx->soundName, &info); + if (!data) + { + return false; + } + + int size_per_sec = info.rate * info.channels * info.width; + if (size_per_sec > 0) + { + sfx->duration = (int)(1000.0f * ((double)info.size / size_per_sec)); + } + + if (info.width == 1) + { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit audio file\n", + sfx->soundName); + } + + if (info.rate != 22050) + { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz audio file\n", + sfx->soundName); + } + + short* samples = (short*)Hunk_AllocateTempMemory(info.channels * info.samples * sizeof(short) * 2); + + sfx->lastTimeUsed = Com_Milliseconds() + 1; + + // each of these compression schemes works just fine + // but the 16bit quality is much nicer and with a local + // install assured we can rely upon the sound memory + // manager to do the right thing for us and page + // sound in as needed + + if (info.channels == 1 && sfx->soundCompressed == true) + { + sfx->soundCompressionMethod = 1; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( + samples, + info.channels, + info.rate, + info.width, + info.samples, + data + info.dataofs); + S_AdpcmEncodeSound(sfx, samples); + } + else + { + sfx->soundCompressionMethod = 0; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfx( + sfx, + info.channels, + info.rate, + info.width, + info.samples, + data + info.dataofs, + false); + } + + sfx->soundChannels = info.channels; + + Hunk_FreeTempMemory(samples); + Hunk_FreeTempMemory(data); + + return true; +} + +void S_DisplayFreeMemory(void) +{ + Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); +} diff --git a/src/client/snd_mix.cpp b/src/client/snd_mix.cpp new file mode 100644 index 0000000..4030bc6 --- /dev/null +++ b/src/client/snd_mix.cpp @@ -0,0 +1,792 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "snd_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif +#include "client.h" + +static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +static int snd_vol; + +int* snd_p; +int snd_linear_count; +short* snd_out; + +#undef id386 + +#if !id386 // if configured not to use asm + +void S_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i=0 ; i>8; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < -32768) + snd_out[i] = -32768; + else + snd_out[i] = val; + + val = snd_p[i+1]>>8; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < -32768) + snd_out[i+1] = -32768; + else + snd_out[i+1] = val; + } +} +#elif defined(__GNUC__) +// uses snd_mixa.s +void S_WriteLinearBlastStereo16 (void); +#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 + +void S_TransferStereo16 (unsigned long *pbuf, int endtime) +{ + int lpos; + int ls_paintedtime; + + snd_p = (int *) paintbuffer; + ls_paintedtime = s_paintedtime; + + while (ls_paintedtime < endtime) + { + // handle recirculating buffer issues + lpos = ls_paintedtime & ((dma.samples>>1)-1); + + snd_out = (short *) pbuf + (lpos<<1); + + snd_linear_count = (dma.samples>>1) - lpos; + if (ls_paintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - ls_paintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + ls_paintedtime += (snd_linear_count>>1); + + if( CL_VideoRecording( ) ) + CL_WriteAVIAudioFrame( (byte *)snd_out, 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->integer ) { + int i; + + // write a fixed sine wave + count = (endtime - s_paintedtime); + for (i=0 ; i> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + 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 < -32768) + val = -32768; + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +#if idppc_altivec +static void S_PaintChannelFrom16_altivec( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata[2], fdiv, fleftvol, frightvol; + + if (sc->soundChannels <= 0) { + return; + } + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + if ( sc->soundChannels == 2 ) { + sampleOffset *= sc->soundChannels; + + if ( sampleOffset & 1 ) { + sampleOffset &= ~1; + } + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + vector signed short volume_vec; + vector unsigned int volume_shift; + int vectorCount, samplesLeft, chunkSamplesLeft; + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + ((short *)&volume_vec)[0] = leftvol; + ((short *)&volume_vec)[1] = leftvol; + ((short *)&volume_vec)[4] = leftvol; + ((short *)&volume_vec)[5] = leftvol; + ((short *)&volume_vec)[2] = rightvol; + ((short *)&volume_vec)[3] = rightvol; + ((short *)&volume_vec)[6] = rightvol; + ((short *)&volume_vec)[7] = rightvol; + volume_shift = vec_splat_u32(8); + i = 0; + + while(i < count) { + /* Try to align destination to 16-byte boundary */ + while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { + data = samples[sampleOffset++]; + samp[i].left += (data * leftvol)>>8; + + if ( sc->soundChannels == 2 ) { + data = samples[sampleOffset++]; + } + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + i++; + } + /* Destination is now aligned. Process as many 8-sample + chunks as we can before we run out of room from the current + sound chunk. We do 8 per loop to avoid extra source data reads. */ + samplesLeft = count - i; + chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; + if(samplesLeft > chunkSamplesLeft) + samplesLeft = chunkSamplesLeft; + + vectorCount = samplesLeft / 8; + + if(vectorCount) + { + vector unsigned char tmp; + vector short s0, s1, sampleData0, sampleData1; + vector signed int merge0, merge1; + vector signed int d0, d1, d2, d3; + vector unsigned char samplePermute0 = + VECCONST_UINT8(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); + vector unsigned char samplePermute1 = + VECCONST_UINT8(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); + vector unsigned char loadPermute0, loadPermute1; + + // Rather than permute the vectors after we load them to do the sample + // replication and rearrangement, we permute the alignment vector so + // we do everything in one step below and avoid data shuffling. + tmp = vec_lvsl(0,&samples[sampleOffset]); + loadPermute0 = vec_perm(tmp,tmp,samplePermute0); + loadPermute1 = vec_perm(tmp,tmp,samplePermute1); + + s0 = *(vector short *)&samples[sampleOffset]; + while(vectorCount) + { + /* Load up source (16-bit) sample data */ + s1 = *(vector short *)&samples[sampleOffset+7]; + + /* Load up destination sample data */ + d0 = *(vector signed int *)&samp[i]; + d1 = *(vector signed int *)&samp[i+2]; + d2 = *(vector signed int *)&samp[i+4]; + d3 = *(vector signed int *)&samp[i+6]; + + sampleData0 = vec_perm(s0,s1,loadPermute0); + sampleData1 = vec_perm(s0,s1,loadPermute1); + + merge0 = vec_mule(sampleData0,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData0,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d0 = vec_add(merge0,d0); + d1 = vec_add(merge1,d1); + + merge0 = vec_mule(sampleData1,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData1,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d2 = vec_add(merge0,d2); + d3 = vec_add(merge1,d3); + + /* Store destination sample data */ + *(vector signed int *)&samp[i] = d0; + *(vector signed int *)&samp[i+2] = d1; + *(vector signed int *)&samp[i+4] = d2; + *(vector signed int *)&samp[i+6] = d3; + + i += 8; + vectorCount--; + s0 = s1; + sampleOffset += 8; + } + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + for ( i=0 ; idopplerScale * sc->soundChannels; + boff = ooff; + fdata[0] = fdata[1] = 0; + for (j=aoff; jsoundChannels) { + if (j == SND_CHUNK_SIZE) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + if ( sc->soundChannels == 2 ) { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[(j+1)&(SND_CHUNK_SIZE-1)]; + } else { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[j&(SND_CHUNK_SIZE-1)]; + } + } + fdiv = 256 * (boff-aoff) / sc->soundChannels; + samp[i].left += (fdata[0] * fleftvol)/fdiv; + samp[i].right += (fdata[1] * frightvol)/fdiv; + } + } +} +#endif + +static void S_PaintChannelFrom16_scalar( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata[2], fdiv, fleftvol, frightvol; + + if (sc->soundChannels <= 0) { + return; + } + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + if ( sc->soundChannels == 2 ) { + sampleOffset *= sc->soundChannels; + + if ( sampleOffset & 1 ) { + sampleOffset &= ~1; + } + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + for ( i=0 ; i>8; + + if ( sc->soundChannels == 2 ) { + data = samples[sampleOffset++]; + } + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + + + + for ( i=0 ; idopplerScale * sc->soundChannels; + boff = ooff; + fdata[0] = fdata[1] = 0; + for (j=aoff; jsoundChannels) { + if (j == SND_CHUNK_SIZE) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + if ( sc->soundChannels == 2 ) { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[(j+1)&(SND_CHUNK_SIZE-1)]; + } else { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[j&(SND_CHUNK_SIZE-1)]; + } + } + fdiv = 256 * (boff-aoff) / sc->soundChannels; + samp[i].left += (fdata[0] * fleftvol)/fdiv; + samp[i].right += (fdata[1] * frightvol)/fdiv; + } + } +} + +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + S_PaintChannelFrom16_altivec( ch, sc, count, sampleOffset, bufferOffset ); + return; + } +#endif + S_PaintChannelFrom16_scalar( ch, sc, count, sampleOffset, bufferOffset ); +} + +void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*2) { + chunk = chunk->next; + decodeWavelet(chunk, sfxScratchBuffer); + sfxScratchIndex++; + sampleOffset = 0; + } + } +} + +void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + while (sampleOffset>=(SND_CHUNK_SIZE*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*4) { + chunk = chunk->next; + S_AdpcmGetSamples( chunk, sfxScratchBuffer); + sampleOffset = 0; + sfxScratchIndex++; + } + } +} + +void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + byte *samples; + float ooff; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*2); + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler) { + samples = (byte *)chunk->sndChunk + sampleOffset; + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + samples++; + if (chunk != NULL && samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + samples = (byte *)chunk->sndChunk; + } + } + } else { + ooff = sampleOffset; + samples = (byte *)chunk->sndChunk; + for ( i=0 ; idopplerScale; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + if (ooff >= SND_CHUNK_SIZE*2) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = (byte *)chunk->sndChunk; + ooff = 0.0; + } + } + } +} + +/* +=================== +S_PaintChannels +=================== +*/ +void S_PaintChannels( int endtime ) { + int i; + int end; + int stream; + channel_t *ch; + sfx_t *sc; + int ltime, count; + int sampleOffset; + + if(s_muted->integer) + snd_vol = 0; + else + snd_vol = s_volume->value*255; + +//Com_Printf ("%i to %i\n", s_paintedtime, endtime); + while ( s_paintedtime < endtime ) { + // if paintbuffer is smaller than DMA buffer + // we may need to fill it multiple times + end = endtime; + if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { + end = s_paintedtime + PAINTBUFFER_SIZE; + } + + // clear the paint buffer and mix any raw samples... + ::memset(paintbuffer, 0, sizeof (paintbuffer)); + for (stream = 0; stream < MAX_RAW_STREAMS; stream++) { + if ( s_rawend[stream] >= s_paintedtime ) { + // copy from the streaming sound source + const portable_samplepair_t *rawsamples = s_rawsamples[stream]; + const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream]; + for ( i = s_paintedtime ; i < stop ; i++ ) { + const int s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-s_paintedtime].left += rawsamples[s].left; + paintbuffer[i-s_paintedtime].right += rawsamples[s].right; + } + } + } + + // paint in the channels. + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + + sampleOffset = ltime - ch->startSample; + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + } + } + + // paint in the looped channels. + ch = loop_channels; + for ( i = 0; i < numLoopChannels ; i++, ch++ ) { + if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + // we might have to make two passes if it + // is a looping sound effect and the end of + // the sample is hit + do { + sampleOffset = (ltime % sc->soundLength); + + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + ltime += count; + } + } while ( ltime < end); + } + + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + s_paintedtime = end; + } +} diff --git a/src/client/snd_openal.cpp b/src/client/snd_openal.cpp new file mode 100644 index 0000000..7ee53e2 --- /dev/null +++ b/src/client/snd_openal.cpp @@ -0,0 +1,2737 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +#ifdef USE_OPENAL + +#include "qal.h" + +// Console variables specific to OpenAL +cvar_t *s_alPrecache; +cvar_t *s_alGain; +cvar_t *s_alSources; +cvar_t *s_alDopplerFactor; +cvar_t *s_alDopplerSpeed; +cvar_t *s_alMinDistance; +cvar_t *s_alMaxDistance; +cvar_t *s_alRolloff; +cvar_t *s_alGraceDistance; +cvar_t *s_alDriver; +cvar_t *s_alDevice; +cvar_t *s_alInputDevice; +cvar_t *s_alAvailableDevices; +cvar_t *s_alAvailableInputDevices; + +static bool enumeration_ext = false; +static bool enumeration_all_ext = false; +#ifdef USE_VOIP +static bool capture_ext = false; +#endif + +/* +================= +S_AL_Format +================= +*/ +static +ALuint S_AL_Format(int width, int channels) +{ + ALuint format = AL_FORMAT_MONO16; + + // Work out format + if(width == 1) + { + if(channels == 1) + format = AL_FORMAT_MONO8; + else if(channels == 2) + format = AL_FORMAT_STEREO8; + } + else if(width == 2) + { + if(channels == 1) + format = AL_FORMAT_MONO16; + else if(channels == 2) + format = AL_FORMAT_STEREO16; + } + + return format; +} + +/* +================= +S_AL_ErrorMsg +================= +*/ +static const char *S_AL_ErrorMsg(ALenum error) +{ + switch(error) + { + case AL_NO_ERROR: + return "No error"; + case AL_INVALID_NAME: + return "Invalid name"; + case AL_INVALID_ENUM: + return "Invalid enumerator"; + case AL_INVALID_VALUE: + return "Invalid value"; + case AL_INVALID_OPERATION: + return "Invalid operation"; + case AL_OUT_OF_MEMORY: + return "Out of memory"; + default: + return "Unknown error"; + } +} + +/* +================= +S_AL_ClearError +================= +*/ +static void S_AL_ClearError( bool quiet ) +{ + int error = qalGetError(); + + if( quiet ) + return; + if(error != AL_NO_ERROR) + { + Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n", + S_AL_ErrorMsg(error)); + } +} + + +//=========================================================================== + + +typedef struct alSfx_s +{ + char filename[MAX_QPATH]; + ALuint buffer; // OpenAL buffer + snd_info_t info; // information for this sound like rate, sample count.. + + bool isDefault; // Couldn't be loaded - use default FX + bool isDefaultChecked; // Sound has been check if it isDefault + bool inMemory; // Sound is stored in memory + bool isLocked; // Sound is locked (can not be unloaded) + int lastUsedTime; // Time last used + + int duration; // Milliseconds + + int loopCnt; // number of loops using this sfx + int loopActiveCnt; // number of playing loops using this sfx + int masterLoopSrc; // All other sources looping this buffer are synced to this master src +} alSfx_t; + +static bool alBuffersInitialised = false; + +// Sound effect storage, data structures +#define MAX_SFX 4096 +static alSfx_t knownSfx[MAX_SFX]; +static sfxHandle_t numSfx = 0; + +static sfxHandle_t default_sfx; + +/* +================= +S_AL_BufferFindFree + +Find a free handle +================= +*/ +static sfxHandle_t S_AL_BufferFindFree( void ) +{ + int i; + + for(i = 0; i < MAX_SFX; i++) + { + // Got one + if(knownSfx[i].filename[0] == '\0') + { + if(i >= numSfx) + numSfx = i + 1; + return i; + } + } + + // Shit... + Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles"); + return -1; +} + +/* +================= +S_AL_BufferFind + +Find a sound effect if loaded, set up a handle otherwise +================= +*/ +static sfxHandle_t S_AL_BufferFind(const char *filename) +{ + // Look it up in the table + sfxHandle_t sfx = -1; + int i; + + if ( !filename ) { + Com_Error( ERR_FATAL, "Sound name is NULL" ); + } + + if ( !filename[0] ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" ); + return 0; + } + + if ( strlen( filename ) >= MAX_QPATH ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", filename ); + return 0; + } + + if ( filename[0] == '*' ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", filename ); + return 0; + } + + for(i = 0; i < numSfx; i++) + { + if(!Q_stricmp(knownSfx[i].filename, filename)) + { + sfx = i; + break; + } + } + + // Not found in table? + if(sfx == -1) + { + alSfx_t *ptr; + + sfx = S_AL_BufferFindFree(); + + // Clear and copy the filename over + ptr = &knownSfx[sfx]; + memset(ptr, 0, sizeof(*ptr)); + ptr->masterLoopSrc = -1; + strcpy(ptr->filename, filename); + } + + // Return the handle + return sfx; +} + +/* +================= +S_AL_BufferUseDefault +================= +*/ +static void S_AL_BufferUseDefault(sfxHandle_t sfx) +{ + if(sfx == default_sfx) + Com_Error(ERR_FATAL, "Can't load default sound effect %s", knownSfx[sfx].filename); + + Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename); + knownSfx[sfx].isDefault = true; + knownSfx[sfx].buffer = knownSfx[default_sfx].buffer; +} + +/* +================= +S_AL_BufferUnload +================= +*/ +static void S_AL_BufferUnload(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if(!knownSfx[sfx].inMemory) + return; + + // Delete it + S_AL_ClearError( false ); + qalDeleteBuffers(1, &knownSfx[sfx].buffer); + if(qalGetError() != AL_NO_ERROR) + Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n", + knownSfx[sfx].filename); + + knownSfx[sfx].inMemory = false; +} + +/* +================= +S_AL_BufferEvict +================= +*/ +static bool S_AL_BufferEvict( void ) +{ + int i, oldestBuffer = -1; + int oldestTime = Sys_Milliseconds( ); + + for( i = 0; i < numSfx; i++ ) + { + if( !knownSfx[ i ].filename[ 0 ] ) + continue; + + if( !knownSfx[ i ].inMemory ) + continue; + + if( knownSfx[ i ].lastUsedTime < oldestTime ) + { + oldestTime = knownSfx[ i ].lastUsedTime; + oldestBuffer = i; + } + } + + if( oldestBuffer >= 0 ) + { + S_AL_BufferUnload( oldestBuffer ); + return true; + } + else + return false; +} + +/* +================= +S_AL_GenBuffers +================= +*/ +static bool S_AL_GenBuffers(ALsizei numBuffers, ALuint *buffers, const char *name) +{ + S_AL_ClearError( false ); + qalGenBuffers( numBuffers, buffers ); + ALenum error = qalGetError(); + + // If we ran out of buffers, start evicting the least recently used sounds + while( error == AL_INVALID_VALUE ) + { + if( !S_AL_BufferEvict( ) ) + { + Com_Printf( S_COLOR_RED "ERROR: Out of audio buffers\n"); + return false; + } + + // Try again + S_AL_ClearError( false ); + qalGenBuffers( numBuffers, buffers ); + error = qalGetError(); + } + + if( error != AL_NO_ERROR ) + { + Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n", + name, S_AL_ErrorMsg(error)); + return false; + } + + return true; +} + +/* +================= +S_AL_BufferLoad +================= +*/ +static void S_AL_BufferLoad(sfxHandle_t sfx, bool cache) +{ + ALenum error; + ALuint format; + + void *data; + snd_info_t info; + alSfx_t *curSfx = &knownSfx[sfx]; + + // Nothing? + if(curSfx->filename[0] == '\0') + return; + + // Already done? + if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked)) + return; + + // Try to load + data = S_CodecLoad(curSfx->filename, &info); + if(!data) + { + S_AL_BufferUseDefault(sfx); + return; + } + + curSfx->isDefaultChecked = true; + + if (!cache) + { + // Don't create AL cache + Hunk_FreeTempMemory(data); + return; + } + + format = S_AL_Format(info.width, info.channels); + + // Create a buffer + if (!S_AL_GenBuffers(1, &curSfx->buffer, curSfx->filename)) + { + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + return; + } + + // Fill the buffer + if( info.size == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050); + } + else + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + + error = qalGetError(); + + // If we ran out of memory, start evicting the least recently used sounds + while(error == AL_OUT_OF_MEMORY) + { + if( !S_AL_BufferEvict( ) ) + { + qalDeleteBuffers(1, &curSfx->buffer); + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename); + return; + } + + // Try load it again + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + error = qalGetError(); + } + + // Some other error condition + if(error != AL_NO_ERROR) + { + qalDeleteBuffers(1, &curSfx->buffer); + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n", + curSfx->filename, S_AL_ErrorMsg(error)); + return; + } + + curSfx->info = info; + + // Free the memory + Hunk_FreeTempMemory(data); + + // Woo! + curSfx->inMemory = true; +} + +/* +================= +S_AL_BufferUse +================= +*/ +static void S_AL_BufferUse(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, true); + knownSfx[sfx].lastUsedTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_BufferInit +================= +*/ +static bool S_AL_BufferInit( void ) +{ + if(alBuffersInitialised) + return true; + + // Clear the hash table, and SFX table + memset(knownSfx, 0, sizeof(knownSfx)); + numSfx = 0; + + // Load the default sound, and lock it + default_sfx = S_AL_BufferFind("sound/feedback/hit.wav"); + S_AL_BufferUse(default_sfx); + knownSfx[default_sfx].isLocked = true; + + // All done + alBuffersInitialised = true; + return true; +} + +/* +================= +S_AL_BufferShutdown +================= +*/ +static void S_AL_BufferShutdown( void ) +{ + if(!alBuffersInitialised) + return; + + // Unlock the default sound effect + knownSfx[default_sfx].isLocked = false; + + // Free all used effects + for(int i = 0; i < numSfx; i++) + S_AL_BufferUnload(i); + + // Clear the tables + numSfx = 0; + + // All undone + alBuffersInitialised = false; +} + +/* +================= +S_AL_RegisterSound +================= +*/ +static sfxHandle_t S_AL_RegisterSound( const char *sample, bool compressed ) +{ + sfxHandle_t sfx = S_AL_BufferFind(sample); + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, s_alPrecache->integer ? true : false); + knownSfx[sfx].lastUsedTime = Com_Milliseconds(); + + if (knownSfx[sfx].isDefault) { + return 0; + } + + return sfx; +} + +/* +================= +S_AL_SoundDuration +================= +*/ +static int S_AL_SoundDuration( sfxHandle_t sfx ) +{ + if (sfx < 0 || sfx >= numSfx) + { + Com_Printf(S_COLOR_RED "ERROR: S_AL_SoundDuration: handle %i out of range\n", sfx); + return 0; + } + return knownSfx[sfx].duration; +} + +/* +================= +S_AL_BufferGet + +Return's a sfx's buffer +================= +*/ +static +ALuint S_AL_BufferGet(sfxHandle_t sfx) +{ + return knownSfx[sfx].buffer; +} + + +//=========================================================================== + + +typedef struct src_s +{ + ALuint alSource; // OpenAL source object + sfxHandle_t sfx; // Sound effect in use + + int lastUsedTime; // Last time used + alSrcPriority_t priority; // Priority + int entity; // Owning entity (-1 if none) + int channel; // Associated channel (-1 if none) + + bool isActive; // Is this source currently in use? + bool isPlaying; // Is this source currently playing, or stopped? + bool isLocked; // This is locked (un-allocatable) + bool isLooping; // Is this a looping effect (attached to an entity) + bool isTracking; // Is this object tracking its owner + bool isStream; // Is this source a stream + + float curGain; // gain employed if source is within maxdistance. + float scaleGain; // Last gain value for this source. 0 if muted. + + float lastTimePos; // On stopped loops, the last position in the buffer + int lastSampleTime; // Time when this was stopped + vec3_t loopSpeakerPos; // Origin of the loop speaker + + bool local; // Is this local (relative to the cam) +} src_t; + +#ifdef __APPLE__ + #define MAX_SRC 64 +#else + #define MAX_SRC 128 +#endif +static src_t srcList[MAX_SRC]; +static int srcCount = 0; +static int srcActiveCnt = 0; +static bool alSourcesInitialised = false; +static int lastListenerNumber = -1; +static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f }; + +typedef struct sentity_s +{ + vec3_t origin; + + bool srcAllocated; // If a src_t has been allocated to this entity + int srcIndex; + + bool loopAddedThisFrame; + alSrcPriority_t loopPriority; + sfxHandle_t loopSfx; + bool startLoopingSound; +} sentity_t; + +static sentity_t entityList[MAX_GENTITIES]; + +/* +================= +S_AL_SanitiseVector +================= +*/ +#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__) +static void _S_AL_SanitiseVector( vec3_t v, int line ) +{ + if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components " + "being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line ); + VectorClear( v ); + } +} + +/* +================= +S_AL_Gain +Set gain to 0 if muted, otherwise set it to given value. +================= +*/ + +static void S_AL_Gain(ALuint source, float gainval) +{ + if(s_muted->integer) + qalSourcef(source, AL_GAIN, 0.0f); + else + qalSourcef(source, AL_GAIN, gainval); +} + +/* +================= +S_AL_ScaleGain +Adapt the gain if necessary to get a quicker fadeout when the source is too far away. +================= +*/ + +static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin) +{ + float distance; + + if(!chksrc->local) + distance = Distance(origin, lastListenerOrigin); + + // If we exceed a certain distance, scale the gain linearly until the sound + // vanishes into nothingness. + if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0) + { + float scaleFactor; + + if(distance >= s_alGraceDistance->value) + scaleFactor = 0.0f; + else + scaleFactor = 1.0f - distance / s_alGraceDistance->value; + + scaleFactor *= chksrc->curGain; + + if(chksrc->scaleGain != scaleFactor) + { + chksrc->scaleGain = scaleFactor; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } + } + else if(chksrc->scaleGain != chksrc->curGain) + { + chksrc->scaleGain = chksrc->curGain; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } +} + +/* +================= +S_AL_HearingThroughEntity + +Also see S_Base_HearingThroughEntity +================= +*/ +static bool S_AL_HearingThroughEntity( int entityNum ) +{ + + if( lastListenerNumber == entityNum ) + { + // This is an outrageous hack to detect + // whether or not the player is rendering in third person or not. We can't + // ask the renderer because the renderer has no notion of entities and we + // can't ask cgame since that would involve changing the API and hence mod + // compatibility. I don't think there is any way around this, but I'll leave + // the FIXME just in case anyone has a bright idea. + float distanceSq = DistanceSquared( entityList[ entityNum ].origin, lastListenerOrigin ); + if( distanceSq > THIRD_PERSON_THRESHOLD_SQ ) + return false; //we're the player, but third person + + //we're the player + return true; + } + + //not the player + return false; +} + +/* +================= +S_AL_SrcInit +================= +*/ +static bool S_AL_SrcInit( void ) +{ + int i; + int limit; + + // Clear the sources data structure + memset(srcList, 0, sizeof(srcList)); + srcCount = 0; + srcActiveCnt = 0; + + // Cap s_alSources to MAX_SRC + limit = s_alSources->integer; + if(limit > MAX_SRC) + limit = MAX_SRC; + else if(limit < 16) + limit = 16; + + S_AL_ClearError( false ); + // Allocate as many sources as possible + for(i = 0; i < limit; i++) + { + qalGenSources(1, &srcList[i].alSource); + if(qalGetError() != AL_NO_ERROR) + break; + srcCount++; + } + + // All done. Print this for informational purposes + Com_Printf( "Allocated %d sources.\n", srcCount); + alSourcesInitialised = true; + return true; +} + +/* +================= +S_AL_SrcShutdown +================= +*/ +static +void S_AL_SrcShutdown( void ) +{ + int i; + src_t *curSource; + + if(!alSourcesInitialised) + return; + + // Destroy all the sources + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + if(curSource->isLocked) + Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i); + + if(curSource->entity > 0) + entityList[curSource->entity].srcAllocated = false; + + qalSourceStop(srcList[i].alSource); + qalDeleteSources(1, &srcList[i].alSource); + } + + memset(srcList, 0, sizeof(srcList)); + + alSourcesInitialised = false; +} + +/* +================= +S_AL_SrcSetup +================= +*/ +static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, int entity, int channel, bool local) +{ + src_t *curSource; + + // Set up src struct + curSource = &srcList[src]; + + curSource->lastUsedTime = Sys_Milliseconds(); + curSource->sfx = sfx; + curSource->priority = priority; + curSource->entity = entity; + curSource->channel = channel; + curSource->isPlaying = false; + curSource->isLocked = false; + curSource->isLooping = false; + curSource->isTracking = false; + curSource->isStream = false; + curSource->curGain = s_alGain->value * s_volume->value; + curSource->scaleGain = curSource->curGain; + curSource->local = local; + + // Set up OpenAL source + if(sfx >= 0) + { + // Mark the SFX as used, and grab the raw AL buffer + S_AL_BufferUse(sfx); + qalSourcei(curSource->alSource, AL_BUFFER, S_AL_BufferGet(sfx)); + } + + qalSourcef(curSource->alSource, AL_PITCH, 1.0f); + S_AL_Gain(curSource->alSource, curSource->curGain); + qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE); + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } +} + +/* +================= +S_AL_SaveLoopPos +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource) +{ + int error; + + S_AL_ClearError(false); + + qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos); + if((error = qalGetError()) != AL_NO_ERROR) + { + // Old OpenAL implementations don't support AL_SEC_OFFSET + + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n", + alSource, S_AL_ErrorMsg(error)); + } + + dest->lastTimePos = -1; + } + else + dest->lastSampleTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_NewLoopMaster +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_NewLoopMaster(src_t *rmSource, bool iskilled) +{ + int index; + src_t *curSource = NULL; + alSfx_t *curSfx; + + curSfx = &knownSfx[rmSource->sfx]; + + if(rmSource->isPlaying) + curSfx->loopActiveCnt--; + if(iskilled) + curSfx->loopCnt--; + + if(curSfx->loopCnt) + { + if(rmSource->priority == SRCPRI_ENTITY) + { + if(!iskilled && rmSource->isPlaying) + { + // only sync ambient loops... + // It makes more sense to have sounds for weapons/projectiles unsynced + S_AL_SaveLoopPos(rmSource, rmSource->alSource); + } + } + else if(rmSource == &srcList[curSfx->masterLoopSrc]) + { + int firstInactive = -1; + + // Only if rmSource was the master and if there are still playing loops for + // this sound will we need to find a new master. + + if(iskilled || curSfx->loopActiveCnt) + { + for(index = 0; index < srcCount; index++) + { + curSource = &srcList[index]; + + if(curSource->sfx == rmSource->sfx && curSource != rmSource && + curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + curSfx->masterLoopSrc = index; + break; + } + else if(firstInactive < 0) + firstInactive = index; + } + } + } + + if(!curSfx->loopActiveCnt) + { + if(firstInactive < 0) + { + if(iskilled) + { + curSfx->masterLoopSrc = -1; + return; + } + else + curSource = rmSource; + } + else + curSource = &srcList[firstInactive]; + + if(rmSource->isPlaying) + { + // this was the last not stopped source, save last sample position + time + S_AL_SaveLoopPos(curSource, rmSource->alSource); + } + else + { + // second case: all loops using this sound have stopped due to listener being of of range, + // and now the inactive master gets deleted. Just move over the soundpos settings to the + // new master. + curSource->lastTimePos = rmSource->lastTimePos; + curSource->lastSampleTime = rmSource->lastSampleTime; + } + } + } + } + else + curSfx->masterLoopSrc = -1; +} + +/* +================= +S_AL_SrcKill +================= +*/ +static void S_AL_SrcKill(srcHandle_t src) +{ + src_t *curSource = &srcList[src]; + + // I'm not touching it. Unlock it first. + if(curSource->isLocked) + return; + + // Remove the entity association and loop master status + if(curSource->isLooping) + { + curSource->isLooping = false; + + if(curSource->entity != -1) + { + sentity_t *curEnt = &entityList[curSource->entity]; + + curEnt->srcAllocated = false; + curEnt->srcIndex = -1; + curEnt->loopAddedThisFrame = false; + curEnt->startLoopingSound = false; + } + + S_AL_NewLoopMaster(curSource, true); + } + + // Stop it if it's playing + if(curSource->isPlaying) + { + qalSourceStop(curSource->alSource); + curSource->isPlaying = false; + } + + // Detach any buffers + qalSourcei(curSource->alSource, AL_BUFFER, 0); + + curSource->sfx = 0; + curSource->lastUsedTime = 0; + curSource->priority = SRCPRI_AMBIENT; + curSource->entity = -1; + curSource->channel = -1; + if(curSource->isActive) + { + curSource->isActive = false; + srcActiveCnt--; + } + curSource->isLocked = false; + curSource->isTracking = false; + curSource->local = false; +} + +/* +================= +S_AL_SrcAlloc +================= +*/ +static +srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel ) +{ + int i; + int empty = -1; + int weakest = -1; + int weakest_time = Sys_Milliseconds(); + int weakest_pri = 999; + float weakest_gain = 1000.0; + bool weakest_isplaying = true; + int weakest_numloops = 0; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + // If it's locked, we aren't even going to look at it + if(curSource->isLocked) + continue; + + // Is it empty or not? + if(!curSource->isActive) + { + empty = i; + break; + } + + if(curSource->isPlaying) + { + if(weakest_isplaying && curSource->priority < priority && + (curSource->priority < weakest_pri || + (!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time)))) + { + // If it has lower priority, is fainter or older, flag it as weak + // the last two values are only compared if it's not a looping sound, because we want to prevent two + // loops (loops are added EVERY frame) fighting for a slot + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_gain = curSource->scaleGain; + weakest = i; + } + } + else + { + weakest_isplaying = false; + + if(weakest < 0 || + knownSfx[curSource->sfx].loopCnt > weakest_numloops || + curSource->priority < weakest_pri || + curSource->lastUsedTime < weakest_time) + { + // Sources currently not playing of course have lowest priority + // also try to always keep at least one loop master for every loop sound + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_numloops = knownSfx[curSource->sfx].loopCnt; + weakest = i; + } + } + + // The channel system is not actually adhered to by baseq3, and not + // implemented in snd_dma.c, so while the following is strictly correct, it + // causes incorrect behaviour versus defacto baseq3 +#if 0 + // Is it an exact match, and not on channel 0? + if((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0)) + { + S_AL_SrcKill(i); + return i; + } +#endif + } + + if(empty == -1) + empty = weakest; + + if(empty >= 0) + { + S_AL_SrcKill(empty); + srcList[empty].isActive = true; + srcActiveCnt++; + } + + return empty; +} + +/* +================= +S_AL_SrcFind + +Finds an active source with matching entity and channel numbers +Returns -1 if there isn't one +================= +*/ +#if 0 +static +srcHandle_t S_AL_SrcFind(int entnum, int channel) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if(!srcList[i].isActive) + continue; + if((srcList[i].entity == entnum) && (srcList[i].channel == channel)) + return i; + } + return -1; +} +#endif + +/* +================= +S_AL_SrcLock + +Locked sources will not be automatically reallocated or managed +================= +*/ +static +void S_AL_SrcLock(srcHandle_t src) +{ + srcList[src].isLocked = true; +} + +/* +================= +S_AL_SrcUnlock + +Once unlocked, the source may be reallocated again +================= +*/ +static +void S_AL_SrcUnlock(srcHandle_t src) +{ + srcList[src].isLocked = false; +} + +/* +================= +S_AL_UpdateEntityPosition +================= +*/ +static +void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + vec3_t sanOrigin; + + VectorCopy( origin, sanOrigin ); + S_AL_SanitiseVector( sanOrigin ); + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + VectorCopy( sanOrigin, entityList[entityNum].origin ); +} + +/* +================= +S_AL_CheckInput +Check whether input values from mods are out of range. +Necessary for i.g. Western Quake3 mod which is buggy. +================= +*/ +static bool S_AL_CheckInput(int entityNum, sfxHandle_t sfx) +{ + if (entityNum < 0 || entityNum >= MAX_GENTITIES) + Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum); + + if (sfx < 0 || sfx >= numSfx) + { + Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx); + return true; + } + + return false; +} + +/* +================= +S_AL_StartLocalSound + +Play a local (non-spatialized) sound effect +================= +*/ +static +void S_AL_StartLocalSound(sfxHandle_t sfx, int channel) +{ + srcHandle_t src; + + if(S_AL_CheckInput(0, sfx)) + return; + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel); + + if(src == -1) + return; + + // Set up the effect + S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, true); + + // Start it playing + srcList[src].isPlaying = true; + qalSourcePlay(srcList[src].alSource); +} + +/* +================= +S_AL_StartSound + +Play a one-shot sound effect +================= +*/ +static void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + vec3_t sorigin; + srcHandle_t src; + src_t *curSource; + + if(origin) + { + if(S_AL_CheckInput(0, sfx)) + return; + + VectorCopy(origin, sorigin); + } + else + { + if(S_AL_CheckInput(entnum, sfx)) + return; + + if(S_AL_HearingThroughEntity(entnum)) + { + S_AL_StartLocalSound(sfx, entchannel); + return; + } + + VectorCopy(entityList[entnum].origin, sorigin); + } + + S_AL_SanitiseVector(sorigin); + + if((srcActiveCnt > 5 * srcCount / 3) && + (DistanceSquared(sorigin, lastListenerOrigin) >= + (s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value))) + { + // We're getting tight on sources and source is not within hearing distance so don't add it + return; + } + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel); + if(src == -1) + return; + + S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, false); + + curSource = &srcList[src]; + + if(!origin) + curSource->isTracking = true; + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin ); + S_AL_ScaleGain(curSource, sorigin); + + // Start it playing + curSource->isPlaying = true; + qalSourcePlay(curSource->alSource); +} + +/* +================= +S_AL_ClearLoopingSounds +================= +*/ +static void S_AL_ClearLoopingSounds( bool killall ) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if((srcList[i].isLooping) && (srcList[i].entity != -1)) + entityList[srcList[i].entity].loopAddedThisFrame = false; + } +} + +/* +================= +S_AL_SrcLoop +================= +*/ +static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, + const vec3_t origin, const vec3_t velocity, int entityNum ) +{ + int src; + sentity_t *sent = &entityList[ entityNum ]; + src_t *curSource; + vec3_t sorigin, svelocity; + + if(S_AL_CheckInput(entityNum, sfx)) + return; + + // Do we need to allocate a new source for this entity + if( !sent->srcAllocated ) + { + // Try to get a channel + src = S_AL_SrcAlloc( priority, entityNum, -1 ); + if( src == -1 ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source " + "for loop sfx %d on entity %d\n", sfx, entityNum ); + return; + } + + curSource = &srcList[src]; + + sent->startLoopingSound = true; + + curSource->lastTimePos = -1.0; + curSource->lastSampleTime = Sys_Milliseconds(); + } + else + { + src = sent->srcIndex; + curSource = &srcList[src]; + } + + sent->srcAllocated = true; + sent->srcIndex = src; + + sent->loopPriority = priority; + sent->loopSfx = sfx; + + // If this is not set then the looping sound is stopped. + sent->loopAddedThisFrame = true; + + // UGH + // These lines should be called via S_AL_SrcSetup, but we + // can't call that yet as it buffers sfxes that may change + // with subsequent calls to S_AL_SrcLoop + curSource->entity = entityNum; + curSource->isLooping = true; + + if( S_AL_HearingThroughEntity( entityNum ) ) + { + curSource->local = true; + + VectorClear(sorigin); + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + } + else + { + curSource->local = false; + + if(origin) + VectorCopy(origin, sorigin); + else + VectorCopy(sent->origin, sorigin); + + S_AL_SanitiseVector(sorigin); + + VectorCopy(sorigin, curSource->loopSpeakerPos); + + if(velocity) + { + VectorCopy(velocity, svelocity); + S_AL_SanitiseVector(svelocity); + } + else + VectorClear(svelocity); + + qalSourcefv(curSource->alSource, AL_POSITION, (ALfloat *) sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, (ALfloat *) svelocity); + } +} + +/* +================= +S_AL_AddLoopingSound +================= +*/ +static void S_AL_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_AddRealLoopingSound +================= +*/ +static void S_AL_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_StopLoopingSound +================= +*/ +static +void S_AL_StopLoopingSound(int entityNum ) +{ + if(entityList[entityNum].srcAllocated) + S_AL_SrcKill(entityList[entityNum].srcIndex); +} + +/* +================= +S_AL_SrcUpdate + +Update state (move things around, manage sources, and so on) +================= +*/ +static +void S_AL_SrcUpdate( void ) +{ + int i; + int entityNum; + ALint state; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + entityNum = srcList[i].entity; + curSource = &srcList[i]; + + if(curSource->isLocked) + continue; + + if(!curSource->isActive) + continue; + + // Update source parameters + if((s_alGain->modified) || (s_volume->modified)) + curSource->curGain = s_alGain->value * s_volume->value; + if((s_alRolloff->modified) && (!curSource->local)) + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + if(s_alMinDistance->modified) + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(curSource->isLooping) + { + sentity_t *sent = &entityList[ entityNum ]; + + // If a looping effect hasn't been touched this frame, pause or kill it + if(sent->loopAddedThisFrame) + { + alSfx_t *curSfx; + + // The sound has changed without an intervening removal + if(curSource->isActive && !sent->startLoopingSound && + curSource->sfx != sent->loopSfx) + { + S_AL_NewLoopMaster(curSource, true); + + curSource->isPlaying = false; + qalSourceStop(curSource->alSource); + qalSourcei(curSource->alSource, AL_BUFFER, 0); + sent->startLoopingSound = true; + } + + // The sound hasn't been started yet + if(sent->startLoopingSound) + { + S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority, + entityNum, -1, curSource->local); + curSource->isLooping = true; + + knownSfx[curSource->sfx].loopCnt++; + sent->startLoopingSound = false; + } + + curSfx = &knownSfx[curSource->sfx]; + + S_AL_ScaleGain(curSource, curSource->loopSpeakerPos); + if(!curSource->scaleGain) + { + if(curSource->isPlaying) + { + // Sound is mute, stop playback until we are in range again + S_AL_NewLoopMaster(curSource, false); + qalSourceStop(curSource->alSource); + curSource->isPlaying = false; + } + else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0) + curSfx->masterLoopSrc = i; + + continue; + } + + if(!curSource->isPlaying) + { + qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE); + curSource->isPlaying = true; + qalSourcePlay(curSource->alSource); + + if(curSource->priority == SRCPRI_AMBIENT) + { + // If there are other ambient looping sources with the same sound, + // make sure the sound of these sources are in sync. + + if(curSfx->loopActiveCnt) + { + int offset, error; + + // we already have a master loop playing, get buffer position. + S_AL_ClearError(false); + qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset); + if((error = qalGetError()) != AL_NO_ERROR) + { + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: " + "%s\n", i, S_AL_ErrorMsg(error)); + } + } + else + qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset); + } + else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0) + { + float secofs; + + src_t *master = &srcList[curSfx->masterLoopSrc]; + // This loop sound used to be played, but all sources are stopped. Use last sample position/time + // to calculate offset so the player thinks the sources continued playing while they were inaudible. + + if(master->lastTimePos >= 0) + { + secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + // I be the master now + curSfx->masterLoopSrc = i; + } + else + curSfx->masterLoopSrc = i; + } + else if(curSource->lastTimePos >= 0) + { + float secofs; + + // For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped + + secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + curSfx->loopActiveCnt++; + } + + // Update locality + if(curSource->local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } + + } + else if(curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + S_AL_NewLoopMaster(curSource, false); + qalSourceStop(curSource->alSource); + curSource->isPlaying = false; + } + } + else + S_AL_SrcKill(i); + + continue; + } + + if(!curSource->isStream) + { + // Check if it's done, and flag it + qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + curSource->isPlaying = false; + S_AL_SrcKill(i); + continue; + } + } + + // Query relativity of source, don't move if it's true + qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state); + + // See if it needs to be moved + if(curSource->isTracking && !state) + { + qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin); + S_AL_ScaleGain(curSource, entityList[entityNum].origin); + } + } +} + +/* +================= +S_AL_SrcShutup +================= +*/ +static +void S_AL_SrcShutup( void ) +{ + int i; + for(i = 0; i < srcCount; i++) + S_AL_SrcKill(i); +} + +/* +================= +S_AL_SrcGet +================= +*/ +static +ALuint S_AL_SrcGet(srcHandle_t src) +{ + return srcList[src].alSource; +} + + +//=========================================================================== + +// Q3A cinematics use up to 12 buffers at once +#define MAX_STREAM_BUFFERS 20 + +static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS]; +static bool streamPlaying[MAX_RAW_STREAMS]; +static ALuint streamSources[MAX_RAW_STREAMS]; +static ALuint streamBuffers[MAX_RAW_STREAMS][MAX_STREAM_BUFFERS]; +static int streamNumBuffers[MAX_RAW_STREAMS]; +static int streamBufIndex[MAX_RAW_STREAMS]; + +/* +================= +S_AL_AllocateStreamChannel +================= +*/ +static void S_AL_AllocateStreamChannel(int stream, int entityNum) +{ + srcHandle_t cursrc; + ALuint alsrc; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(entityNum >= 0) + { + // This is a stream that tracks an entity + // Allocate a streamSource at normal priority + cursrc = S_AL_SrcAlloc(SRCPRI_ENTITY, entityNum, 0); + if(cursrc < 0) + return; + + S_AL_SrcSetup(cursrc, -1, SRCPRI_ENTITY, entityNum, 0, false); + alsrc = S_AL_SrcGet(cursrc); + srcList[cursrc].isTracking = true; + srcList[cursrc].isStream = true; + } + else + { + // Unspatialized stream source + + // Allocate a streamSource at high priority + cursrc = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(cursrc < 0) + return; + + alsrc = S_AL_SrcGet(cursrc); + + // Lock the streamSource so nobody else can use it, and get the raw streamSource + S_AL_SrcLock(cursrc); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[cursrc].scaleGain = 0.0f; + + // Set some streamSource parameters + qalSourcei (alsrc, AL_BUFFER, 0 ); + qalSourcei (alsrc, AL_LOOPING, AL_FALSE ); + qalSource3f(alsrc, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (alsrc, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (alsrc, AL_SOURCE_RELATIVE, AL_TRUE ); + } + + streamSourceHandles[stream] = cursrc; + streamSources[stream] = alsrc; + + streamNumBuffers[stream] = 0; + streamBufIndex[stream] = 0; +} + +/* +================= +S_AL_FreeStreamChannel +================= +*/ +static void S_AL_FreeStreamChannel( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + // Detach any buffers + qalSourcei(streamSources[stream], AL_BUFFER, 0); + + // Delete the buffers + if (streamNumBuffers[stream] > 0) { + qalDeleteBuffers(streamNumBuffers[stream], streamBuffers[stream]); + streamNumBuffers[stream] = 0; + } + + // Release the output streamSource + S_AL_SrcUnlock(streamSourceHandles[stream]); + S_AL_SrcKill(streamSourceHandles[stream]); + streamSources[stream] = 0; + streamSourceHandles[stream] = -1; +} + +/* +================= +S_AL_RawSamples +================= +*/ +static +void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum) +{ + int numBuffers; + ALuint buffer; + ALuint format; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + format = S_AL_Format( width, channels ); + + // Create the streamSource if necessary + if(streamSourceHandles[stream] == -1) + { + S_AL_AllocateStreamChannel(stream, entityNum); + + // Failed? + if(streamSourceHandles[stream] == -1) + { + Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n"); + return; + } + } + + qalGetSourcei(streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers); + + if (numBuffers == MAX_STREAM_BUFFERS) + { + Com_DPrintf(S_COLOR_RED"WARNING: Steam dropping raw samples, reached MAX_STREAM_BUFFERS\n"); + return; + } + + // Allocate a new AL buffer if needed + if (numBuffers == streamNumBuffers[stream]) + { + ALuint oldBuffers[MAX_STREAM_BUFFERS]; + int i; + + if (!S_AL_GenBuffers(1, &buffer, "stream")) + return; + + ::memcpy(oldBuffers, &streamBuffers[stream], sizeof (oldBuffers)); + + // Reorder buffer array in order of oldest to newest + for ( i = 0; i < streamNumBuffers[stream]; ++i ) + streamBuffers[stream][i] = oldBuffers[(streamBufIndex[stream] + i) % streamNumBuffers[stream]]; + + // Add the new buffer to end + streamBuffers[stream][streamNumBuffers[stream]] = buffer; + streamBufIndex[stream] = streamNumBuffers[stream]; + streamNumBuffers[stream]++; + } + + // Select next buffer in loop + buffer = streamBuffers[stream][ streamBufIndex[stream] ]; + streamBufIndex[stream] = (streamBufIndex[stream] + 1) % streamNumBuffers[stream]; + + // Fill buffer + qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate); + + // Shove the data onto the streamSource + qalSourceQueueBuffers(streamSources[stream], 1, &buffer); + + if(entityNum < 0) + { + // Volume + S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value); + } + + // Start stream + if(!streamPlaying[stream]) + { + qalSourcePlay( streamSources[stream] ); + streamPlaying[stream] = true; + } +} + +/* +================= +S_AL_StreamUpdate +================= +*/ +static +void S_AL_StreamUpdate( int stream ) +{ + int numBuffers; + ALint state; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + // Un-queue any buffers + qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer); + } + + // Start the streamSource playing if necessary + qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers ); + + qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + streamPlaying[stream] = false; + + // If there are no buffers queued up, release the streamSource + if( !numBuffers ) + S_AL_FreeStreamChannel( stream ); + } + + if( !streamPlaying[stream] && numBuffers ) + { + qalSourcePlay( streamSources[stream] ); + streamPlaying[stream] = true; + } +} + +/* +================= +S_AL_StreamDie +================= +*/ +static +void S_AL_StreamDie( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + streamPlaying[stream] = false; + qalSourceStop(streamSources[stream]); + + S_AL_FreeStreamChannel(stream); +} + + +//=========================================================================== + + +#define NUM_MUSIC_BUFFERS 4 +#define MUSIC_BUFFER_SIZE 4096 + +static bool musicPlaying = false; +static srcHandle_t musicSourceHandle = -1; +static ALuint musicSource; +static ALuint musicBuffers[NUM_MUSIC_BUFFERS]; + +static snd_stream_t *mus_stream; +static snd_stream_t *intro_stream; +static char s_backgroundLoop[MAX_QPATH]; + +static byte decode_buffer[MUSIC_BUFFER_SIZE]; + +/* +================= +S_AL_MusicSourceGet +================= +*/ +static void S_AL_MusicSourceGet( void ) +{ + // Allocate a musicSource at high priority + musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(musicSourceHandle == -1) + return; + + // Lock the musicSource so nobody else can use it, and get the raw musicSource + S_AL_SrcLock(musicSourceHandle); + musicSource = S_AL_SrcGet(musicSourceHandle); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[musicSourceHandle].scaleGain = 0.0f; + + // Set some musicSource parameters + qalSource3f(musicSource, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (musicSource, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_MusicSourceFree +================= +*/ +static void S_AL_MusicSourceFree( void ) +{ + // Release the output musicSource + S_AL_SrcUnlock(musicSourceHandle); + S_AL_SrcKill(musicSourceHandle); + musicSource = 0; + musicSourceHandle = -1; +} + +/* +================= +S_AL_CloseMusicFiles +================= +*/ +static void S_AL_CloseMusicFiles(void) +{ + if(intro_stream) + { + S_CodecCloseStream(intro_stream); + intro_stream = NULL; + } + + if(mus_stream) + { + S_CodecCloseStream(mus_stream); + mus_stream = NULL; + } +} + +/* +================= +S_AL_StopBackgroundTrack +================= +*/ +static +void S_AL_StopBackgroundTrack( void ) +{ + if(!musicPlaying) + return; + + // Stop playing + qalSourceStop(musicSource); + + // Detach any buffers + qalSourcei(musicSource, AL_BUFFER, 0); + + // Delete the buffers + qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Free the musicSource + S_AL_MusicSourceFree(); + + // Unload the stream + S_AL_CloseMusicFiles(); + + musicPlaying = false; +} + +/* +================= +S_AL_MusicProcess +================= +*/ +static +void S_AL_MusicProcess(ALuint b) +{ + ALenum error; + int l; + ALuint format; + snd_stream_t *curstream; + + S_AL_ClearError( false ); + + if(intro_stream) + curstream = intro_stream; + else + curstream = mus_stream; + + if(!curstream) + return; + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + + // Run out data to read, start at the beginning again + if(l == 0) + { + S_CodecCloseStream(curstream); + + // the intro stream just finished playing so we don't need to reopen + // the music stream. + if(intro_stream) + intro_stream = NULL; + else + mus_stream = S_CodecOpenStream(s_backgroundLoop); + + curstream = mus_stream; + + if(!curstream) + { + S_AL_StopBackgroundTrack(); + return; + } + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + } + + format = S_AL_Format(curstream->info.width, curstream->info.channels); + + if( l == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 ); + } + else + qalBufferData(b, format, decode_buffer, l, curstream->info.rate); + + if( ( error = qalGetError( ) ) != AL_NO_ERROR ) + { + S_AL_StopBackgroundTrack( ); + Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n", + S_AL_ErrorMsg( error ) ); + return; + } +} + +/* +================= +S_AL_StartBackgroundTrack +================= +*/ +static +void S_AL_StartBackgroundTrack( const char *intro, const char *loop ) +{ + int i; + bool issame; + + // Stop any existing music that might be playing + S_AL_StopBackgroundTrack(); + + if((!intro || !*intro) && (!loop || !*loop)) + return; + + // Allocate a musicSource + S_AL_MusicSourceGet(); + if(musicSourceHandle == -1) + return; + + if (!loop || !*loop) + { + loop = intro; + issame = true; + } + else if(intro && *intro && !strcmp(intro, loop)) + issame = true; + else + issame = false; + + // Copy the loop over + Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + if(!issame) + { + // Open the intro and don't mind whether it succeeds. + // The important part is the loop. + intro_stream = S_CodecOpenStream(intro); + } + else + intro_stream = NULL; + + mus_stream = S_CodecOpenStream(s_backgroundLoop); + if(!mus_stream) + { + S_AL_CloseMusicFiles(); + S_AL_MusicSourceFree(); + return; + } + + // Generate the musicBuffers + if (!S_AL_GenBuffers(NUM_MUSIC_BUFFERS, musicBuffers, "music")) + return; + + // Queue the musicBuffers up + for(i = 0; i < NUM_MUSIC_BUFFERS; i++) + { + S_AL_MusicProcess(musicBuffers[i]); + } + + qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Set the initial gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); + + // Start playing + qalSourcePlay(musicSource); + + musicPlaying = true; +} + +/* +================= +S_AL_MusicUpdate +================= +*/ +static +void S_AL_MusicUpdate( void ) +{ + int numBuffers; + ALint state; + + if(!musicPlaying) + return; + + qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint b; + qalSourceUnqueueBuffers(musicSource, 1, &b); + S_AL_MusicProcess(b); + qalSourceQueueBuffers(musicSource, 1, &b); + } + + // Hitches can cause OpenAL to be starved of buffers when streaming. + // If this happens, it will stop playback. This restarts the source if + // it is no longer playing, and if there are buffers available + qalGetSourcei( musicSource, AL_SOURCE_STATE, &state ); + qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers ); + if( state == AL_STOPPED && numBuffers ) + { + Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" ); + qalSourcePlay(musicSource); + } + + // Set the gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); +} + + +//=========================================================================== + + +// Local state variables +static ALCdevice *alDevice; +static ALCcontext *alContext; + +#ifdef USE_VOIP +static ALCdevice *alCaptureDevice; +static cvar_t *s_alCapture; +#endif + +#ifdef _WIN32 +#define ALDRIVER_DEFAULT "OpenAL32.dll" +#elif defined(__APPLE__) +#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL" +#elif defined(__OpenBSD__) +#define ALDRIVER_DEFAULT "libopenal.so" +#else +#define ALDRIVER_DEFAULT "libopenal.so.1" +#endif + +/* +================= +S_AL_StopAllSounds +================= +*/ +static +void S_AL_StopAllSounds( void ) +{ + int i; + S_AL_SrcShutup(); + S_AL_StopBackgroundTrack(); + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); +} + +/* +================= +S_AL_Respatialize +================= +*/ +static +void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ + float orientation[6]; + vec3_t sorigin; + + VectorCopy( origin, sorigin ); + S_AL_SanitiseVector( sorigin ); + + S_AL_SanitiseVector( axis[ 0 ] ); + S_AL_SanitiseVector( axis[ 1 ] ); + S_AL_SanitiseVector( axis[ 2 ] ); + + orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2]; + orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2]; + + lastListenerNumber = entityNum; + VectorCopy( sorigin, lastListenerOrigin ); + + // Set OpenAL listener paramaters + qalListenerfv(AL_POSITION, (ALfloat *)sorigin); + qalListenerfv(AL_VELOCITY, vec3_origin); + qalListenerfv(AL_ORIENTATION, orientation); +} + +/* +================= +S_AL_Update +================= +*/ +static +void S_AL_Update( void ) +{ + int i; + + if(s_muted->modified) + { + // muted state changed. Let S_AL_Gain turn up all sources again. + for(i = 0; i < srcCount; i++) + { + if(srcList[i].isActive) + S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain); + } + + s_muted->modified = false; + } + + // Update SFX channels + S_AL_SrcUpdate(); + + // Update streams + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamUpdate(i); + S_AL_MusicUpdate(); + + // Doppler + if(s_doppler->modified) + { + s_alDopplerFactor->modified = true; + s_doppler->modified = false; + } + + // Doppler parameters + if(s_alDopplerFactor->modified) + { + if(s_doppler->integer) + qalDopplerFactor(s_alDopplerFactor->value); + else + qalDopplerFactor(0.0f); + s_alDopplerFactor->modified = false; + } + if(s_alDopplerSpeed->modified) + { + qalSpeedOfSound(s_alDopplerSpeed->value); + s_alDopplerSpeed->modified = false; + } + + // Clear the modified flags on the other cvars + s_alGain->modified = false; + s_volume->modified = false; + s_musicVolume->modified = false; + s_alMinDistance->modified = false; + s_alRolloff->modified = false; +} + +/* +================= +S_AL_DisableSounds +================= +*/ +static +void S_AL_DisableSounds( void ) +{ + S_AL_StopAllSounds(); +} + +/* +================= +S_AL_BeginRegistration +================= +*/ +static +void S_AL_BeginRegistration( void ) +{ + if(!numSfx) + S_AL_BufferInit(); +} + +/* +================= +S_AL_ClearSoundBuffer +================= +*/ +static +void S_AL_ClearSoundBuffer( void ) +{ +} + +/* +================= +S_AL_SoundList +================= +*/ +static +void S_AL_SoundList( void ) +{ +} + +#ifdef USE_VOIP +static +void S_AL_StartCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStart(alCaptureDevice); +} + +static +int S_AL_AvailableCaptureSamples( void ) +{ + int retval = 0; + if (alCaptureDevice != NULL) + { + ALint samples = 0; + qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples); + retval = (int) samples; + } + return retval; +} + +static +void S_AL_Capture( int samples, byte *data ) +{ + if (alCaptureDevice != NULL) + qalcCaptureSamples(alCaptureDevice, data, samples); +} + +void S_AL_StopCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStop(alCaptureDevice); +} + +void S_AL_MasterGain( float gain ) +{ + qalListenerf(AL_GAIN, gain); +} +#endif + + +/* +================= +S_AL_SoundInfo +================= +*/ +static void S_AL_SoundInfo(void) +{ + Com_Printf( "OpenAL info:\n" ); + Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) ); + Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) ); + Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) ); + Com_Printf( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) ); + Com_Printf( " ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) ); + + if(enumeration_all_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER)); + else if(enumeration_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER)); + + if(enumeration_all_ext || enumeration_ext) + Com_Printf(" Available Devices:\n%s", s_alAvailableDevices->string); + +#ifdef USE_VOIP + if(capture_ext) + { + Com_Printf(" Input Device: %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER)); + Com_Printf(" Available Input Devices:\n%s", s_alAvailableInputDevices->string); + } +#endif +} + + + +/* +================= +S_AL_Shutdown +================= +*/ +static +void S_AL_Shutdown( void ) +{ + // Shut down everything + int i; + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); + S_AL_StopBackgroundTrack( ); + S_AL_SrcShutdown( ); + S_AL_BufferShutdown( ); + + qalcDestroyContext(alContext); + qalcCloseDevice(alDevice); + +#ifdef USE_VOIP + if (alCaptureDevice != NULL) { + qalcCaptureStop(alCaptureDevice); + qalcCaptureCloseDevice(alCaptureDevice); + alCaptureDevice = NULL; + Com_Printf( "OpenAL capture device closed.\n" ); + } +#endif + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = false; + streamSources[i] = 0; + } + + QAL_Shutdown(); +} + +#endif + +/* +================= +S_AL_Init +================= +*/ +bool S_AL_Init( soundInterface_t *si ) +{ +#ifdef USE_OPENAL + const char* device = NULL; + const char* inputdevice = NULL; + int i; + + if( !si ) { + return false; + } + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = false; + streamSources[i] = 0; + streamNumBuffers[i] = 0; + streamBufIndex[i] = 0; + } + + // New console variables + s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE ); + s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE ); + s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE ); + s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE ); + s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "9000", CVAR_ARCHIVE ); + s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT ); + s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT); + s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT); + s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT); + + + s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH ); + s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH); + s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH | CVAR_PROTECTED); + + // Load QAL + if( !QAL_Init( s_alDriver->string ) ) + { + Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string ); + if( !Q_stricmp( s_alDriver->string, ALDRIVER_DEFAULT ) || !QAL_Init( ALDRIVER_DEFAULT ) ) { + return false; + } + } + + device = s_alDevice->string; + if(device && !*device) + device = NULL; + + inputdevice = s_alInputDevice->string; + if(inputdevice && !*inputdevice) + inputdevice = NULL; + + + // Device enumeration support + enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"); + enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"); + + if(enumeration_ext || enumeration_all_ext) + { + char devicenames[16384] = ""; + const char *devicelist; +#ifdef _WIN32 + const char *defaultdevice; +#endif + int curlen; + + // get all available devices + the default device name. + if(enumeration_all_ext) + { + devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); +#ifdef _WIN32 + defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); +#endif + } + else + { + // We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration. + devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER); +#ifdef _WIN32 + defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); +#endif + enumeration_ext = true; + } + +#ifdef _WIN32 + // check whether the default device is generic hardware. If it is, change to + // Generic Software as that one works more reliably with various sound systems. + // If it's not, use OpenAL's default selection as we don't want to ignore + // native hardware acceleration. + if(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware")) + device = "Generic Software"; +#endif + + // dump a list of available devices to a cvar for the user to see. + + if(devicelist) + { + while((curlen = strlen(devicelist))) + { + Q_strcat(devicenames, sizeof(devicenames), devicelist); + Q_strcat(devicenames, sizeof(devicenames), "\n"); + + devicelist += curlen + 1; + } + } + + s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART); + } + + alDevice = qalcOpenDevice(device); + if( !alDevice && device ) + { + Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device ); + alDevice = qalcOpenDevice(NULL); + } + + if( !alDevice ) + { + QAL_Shutdown( ); + Com_Printf( "Failed to open OpenAL device.\n" ); + return false; + } + + // Create OpenAL context + alContext = qalcCreateContext( alDevice, NULL ); + if( !alContext ) + { + QAL_Shutdown( ); + qalcCloseDevice( alDevice ); + Com_Printf( "Failed to create OpenAL context.\n" ); + return false; + } + qalcMakeContextCurrent( alContext ); + + // Initialize sources, buffers, music + S_AL_BufferInit( ); + S_AL_SrcInit( ); + + // Set up OpenAL parameters (doppler, etc) + qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + qalDopplerFactor( s_alDopplerFactor->value ); + qalSpeedOfSound( s_alDopplerSpeed->value ); + +#ifdef USE_VOIP + // !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars. + // !!! FIXME: add support for capture device enumeration. + // !!! FIXME: add some better error reporting. + s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + if (!s_alCapture->integer) + { + Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n"); + } +#if USE_MUMBLE + else if (cl_useMumble->integer) + { + Com_Printf("OpenAL capture support disabled for Mumble support\n"); + } +#endif + else + { +#ifdef __APPLE__ + // !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes + // !!! FIXME: capture support, but they don't list it in the + // !!! FIXME: extension string. We need to check the version string, + // !!! FIXME: then the extension string, but that's too much trouble, + // !!! FIXME: so we'll just check the function pointer for now. + if (qalcCaptureOpenDevice == NULL) +#else + if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture")) +#endif + { + Com_Printf("No ALC_EXT_capture support, can't record audio.\n"); + } + else + { + char inputdevicenames[16384] = ""; + const char *inputdevicelist; + const char *defaultinputdevice; + int curlen; + + capture_ext = true; + + // get all available input devices + the default input device name. + inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + + // dump a list of available devices to a cvar for the user to see. + if (inputdevicelist) + { + while((curlen = strlen(inputdevicelist))) + { + Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist); + Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n"); + inputdevicelist += curlen + 1; + } + } + + s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART); + + Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none"); + alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4); + if( !alCaptureDevice && inputdevice ) + { + Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice ); + alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4); + } + Com_Printf( "OpenAL capture device %s.\n", + (alCaptureDevice == NULL) ? "failed to open" : "opened"); + } + } +#endif + + si->Shutdown = S_AL_Shutdown; + si->StartSound = S_AL_StartSound; + si->StartLocalSound = S_AL_StartLocalSound; + si->StartBackgroundTrack = S_AL_StartBackgroundTrack; + si->StopBackgroundTrack = S_AL_StopBackgroundTrack; + si->RawSamples = S_AL_RawSamples; + si->StopAllSounds = S_AL_StopAllSounds; + si->ClearLoopingSounds = S_AL_ClearLoopingSounds; + si->AddLoopingSound = S_AL_AddLoopingSound; + si->AddRealLoopingSound = S_AL_AddRealLoopingSound; + si->StopLoopingSound = S_AL_StopLoopingSound; + si->Respatialize = S_AL_Respatialize; + si->UpdateEntityPosition = S_AL_UpdateEntityPosition; + si->Update = S_AL_Update; + si->DisableSounds = S_AL_DisableSounds; + si->BeginRegistration = S_AL_BeginRegistration; + si->RegisterSound = S_AL_RegisterSound; + si->SoundDuration = S_AL_SoundDuration; + si->ClearSoundBuffer = S_AL_ClearSoundBuffer; + si->SoundInfo = S_AL_SoundInfo; + si->SoundList = S_AL_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_AL_StartCapture; + si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples; + si->Capture = S_AL_Capture; + si->StopCapture = S_AL_StopCapture; + si->MasterGain = S_AL_MasterGain; +#endif + + return true; +#else + return false; +#endif +} diff --git a/src/client/snd_public.h b/src/client/snd_public.h new file mode 100644 index 0000000..b8e97b9 --- /dev/null +++ b/src/client/snd_public.h @@ -0,0 +1,88 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef _SND_PUBLIC_H_ +#define _SND_PUBLIC_H_ + +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, sfxHandle_t sfx ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); + +void S_StartBackgroundTrack( const char *intro, const char *loop ); +void S_StopBackgroundTrack( void ); + +// cinematics and voice-over-network will send raw samples +// 1.0 volume will be direct output of source samples +void S_RawSamples(int stream, int samples, int rate, int width, int channels, + const byte *data, float volume, int entityNum); + +// stop all sounds and the background track +void S_StopAllSounds( void ); + +// all continuous looping sounds must be added before calling S_Update +void S_ClearLoopingSounds( bool killall ); +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_StopLoopingSound(int entityNum ); + +// recompute the relative volumes for all running sounds +// relative to the given entityNum / orientation +void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + +// let the sound system know where an entity currently is +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +void S_Update( void ); + +void S_DisableSounds( void ); + +void S_BeginRegistration( void ); + +// RegisterSound will allways return a valid sample, even if it +// has to create a placeholder. This prevents continuous filesystem +// checks for missing files +sfxHandle_t S_RegisterSound( const char *sample, bool compressed ); + +int S_SoundDuration( sfxHandle_t handle ); + +void S_DisplayFreeMemory(void); + +void S_ClearSoundBuffer( void ); + +void SNDDMA_Activate( void ); + +void S_UpdateBackgroundTrack( void ); + + +#ifdef USE_VOIP +void S_StartCapture( void ); +int S_AvailableCaptureSamples( void ); +void S_Capture( int samples, byte *data ); +void S_StopCapture( void ); +void S_MasterGain( float gain ); +#endif +#endif diff --git a/src/client/snd_wavelet.cpp b/src/client/snd_wavelet.cpp new file mode 100644 index 0000000..e06e3ef --- /dev/null +++ b/src/client/snd_wavelet.cpp @@ -0,0 +1,252 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "snd_local.h" + +#define C0 0.4829629131445341 +#define C1 0.8365163037378079 +#define C2 0.2241438680420134 +#define C3 -0.1294095225512604 + +void daub4(float b[], unsigned long n, int isign) +{ + float wksp[4097] = { 0.0f }; +#define a(x) b[(x)-1] // numerical recipies so a[1] = b[0] + + + unsigned long nh,nh1,i,j; + + if (n < 4) return; + + nh1=(nh=n >> 1)+1; + if (isign >= 0) { + for (i=1,j=1;j<=n-3;j+=2,i++) { + wksp[i] = C0*a(j)+C1*a(j+1)+C2*a(j+2)+C3*a(j+3); + wksp[i+nh] = C3*a(j)-C2*a(j+1)+C1*a(j+2)-C0*a(j+3); + } + wksp[i ] = C0*a(n-1)+C1*a(n)+C2*a(1)+C3*a(2); + wksp[i+nh] = C3*a(n-1)-C2*a(n)+C1*a(1)-C0*a(2); + } else { + wksp[1] = C2*a(nh)+C1*a(n)+C0*a(1)+C3*a(nh1); + wksp[2] = C3*a(nh)-C0*a(n)+C1*a(1)-C2*a(nh1); + for (i=1,j=3;i= 0) { + for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign); + } else { + for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign); + } +} + +/* The number of bits required by each value */ +static unsigned char numBits[] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +byte MuLawEncode(short s) { + unsigned long adjusted; + byte sign, exponent, mantissa; + + sign = (s<0)?0:0x80; + + if (s<0) s=-s; + adjusted = (long)s << (16-sizeof(short)*8); + adjusted += 128L + 4L; + if (adjusted > 32767) adjusted = 32767; + exponent = numBits[(adjusted>>7)&0xff] - 1; + mantissa = (adjusted>>(exponent+3))&0xf; + return ~(sign | (exponent<<4) | mantissa); +} + +short MuLawDecode(byte uLaw) { + signed long adjusted; + byte exponent, mantissa; + + uLaw = ~uLaw; + exponent = (uLaw>>4) & 0x7; + mantissa = (uLaw&0xf) + 16; + adjusted = (mantissa << (exponent +3)) - 128 - 4; + + return (uLaw & 0x80)? adjusted : -adjusted; +} + +short mulawToShort[256]; +static bool madeTable = false; + +static int NXStreamCount; + +void NXPutc(NXStream *stream, char out) { + stream[NXStreamCount++] = out; +} + + +void encodeWavelet( sfx_t *sfx, short *packets) { + float wksp[4097] = {0}, temp; + int i, samples, size; + sndBuffer *newchunk, *chunk; + byte *out; + + if (!madeTable) { + for (i=0;i<256;i++) { + mulawToShort[i] = (float)MuLawDecode((byte)i); + } + madeTable = true; + } + chunk = NULL; + + samples = sfx->soundLength; + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + if (size<4) { + size = 4; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else if (chunk != NULL) { + chunk->next = newchunk; + } + chunk = newchunk; + for(i=0; isndChunk; + + for(i=0;i 32767) temp = 32767; else if (temp<-32768) temp = -32768; + out[i] = MuLawEncode((short)temp); + } + + chunk->size = size; + samples -= size; + } +} + +void decodeWavelet(sndBuffer *chunk, short *to) { + float wksp[4097] = {0}; + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;isoundLength; + grade = 0; + + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else if (chunk != NULL) { + chunk->next = newchunk; + } + chunk = newchunk; + out = (byte *)chunk->sndChunk; + for(i=0; i32767) { + poop = 32767; + } else if (poop<-32768) { + poop = -32768; + } + out[i] = MuLawEncode((short)poop); + grade = poop - mulawToShort[out[i]]; + packets++; + } + chunk->size = size; + samples -= size; + } +} + +void decodeMuLaw(sndBuffer *chunk, short *to) { + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;i + +=========================================================================== +*/ + +#include "qcommon/q_shared.h" +#include "bg_public.h" + +#ifdef GAME +# define POOLSIZE ( 1024 * 1024 ) +#else +# define POOLSIZE ( 256 * 1024 ) +#endif + +#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value +#define ROUNDBITS 31 // Round to 32 bytes + +typedef struct freeMemNode_s +{ + // Size of ROUNDBITS + int cookie, size; // Size includes node (obviously) + struct freeMemNode_s *prev, *next; +} freeMemNode_t; + +static char memoryPool[POOLSIZE]; +static freeMemNode_t *freeHead; +static int freeMem; + +void *BG_Alloc( int size ) +{ + // Find a free block and allocate. + // Does two passes, attempts to fill same-sized free slot first. + + freeMemNode_t *fmn, *prev, *next, *smallest; + int allocsize, smallestsize; + char *endptr; + int *ptr; + + allocsize = ( size + (int)sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary + ptr = NULL; + + smallest = NULL; + smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :) + for( fmn = freeHead; fmn; fmn = fmn->next ) + { + if( fmn->cookie != FREEMEMCOOKIE ) + Com_Error( ERR_DROP, "BG_Alloc: Memory corruption detected!" ); + + if( fmn->size >= allocsize ) + { + // We've got a block + if( fmn->size == allocsize ) + { + // Same size, just remove + + prev = fmn->prev; + next = fmn->next; + if( prev ) + prev->next = next; // Point previous node to next + if( next ) + next->prev = prev; // Point next node to previous + if( fmn == freeHead ) + freeHead = next; // Set head pointer to next + ptr = (int *) fmn; + break; // Stop the loop, this is fine + } + else + { + // Keep track of the smallest free slot + if( fmn->size < smallestsize ) + { + smallest = fmn; + smallestsize = fmn->size; + } + } + } + } + + if( !ptr && smallest ) + { + // We found a slot big enough + smallest->size -= allocsize; + endptr = (char *) smallest + smallest->size; + ptr = (int *) endptr; + } + + if( ptr ) + { + freeMem -= allocsize; + memset( ptr, 0, allocsize ); + *ptr++ = allocsize; // Store a copy of size for deallocation + return( (void *) ptr ); + } + + Com_Error( ERR_DROP, "BG_Alloc: failed on allocation of %i bytes", size ); + return( NULL ); +} + +void BG_Free( void *ptr ) +{ + // Release allocated memory, add it to the free list. + + freeMemNode_t *fmn; + char *freeend; + int *freeptr; + + freeptr = ptr; + freeptr--; + + freeMem += *freeptr; + + for( fmn = freeHead; fmn; fmn = fmn->next ) + { + freeend = ((char *) fmn) + fmn->size; + if( freeend == (char *) freeptr ) + { + // Released block can be merged to an existing node + + fmn->size += *freeptr; // Add size of node. + return; + } + } + // No merging, add to head of list + + fmn = (freeMemNode_t *) freeptr; + fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr + fmn->cookie = FREEMEMCOOKIE; + fmn->prev = NULL; + fmn->next = freeHead; + freeHead->prev = fmn; + freeHead = fmn; +} + +void BG_InitMemory( void ) +{ + // Set up the initial node + + freeHead = (freeMemNode_t *)memoryPool; + freeHead->cookie = FREEMEMCOOKIE; + freeHead->size = POOLSIZE; + freeHead->next = NULL; + freeHead->prev = NULL; + freeMem = sizeof( memoryPool ); +} + +void BG_DefragmentMemory( void ) +{ + // If there's a frenzy of deallocation and we want to + // allocate something big, this is useful. Otherwise... + // not much use. + + freeMemNode_t *startfmn, *endfmn, *fmn; + + for( startfmn = freeHead; startfmn; ) + { + endfmn = (freeMemNode_t *)(((char *) startfmn) + startfmn->size); + for( fmn = freeHead; fmn; ) + { + if( fmn->cookie != FREEMEMCOOKIE ) + Com_Error( ERR_DROP, "BG_DefragmentMemory: Memory corruption detected!" ); + + if( fmn == endfmn ) + { + // We can add fmn onto startfmn. + + if( fmn->prev ) + fmn->prev->next = fmn->next; + if( fmn->next ) + { + if( !(fmn->next->prev = fmn->prev) ) + freeHead = fmn->next; // We're removing the head node + } + startfmn->size += fmn->size; + memset( fmn, 0, sizeof(freeMemNode_t) ); // A redundant call, really. + + startfmn = freeHead; + endfmn = fmn = NULL; // Break out of current loop + } + else + fmn = fmn->next; + } + + if( endfmn ) + startfmn = startfmn->next; // endfmn acts as a 'restart' flag here + } +} + +void BG_MemoryInfo( void ) +{ + // Give a breakdown of memory + + freeMemNode_t *fmn = (freeMemNode_t *)memoryPool; + int size, chunks; + freeMemNode_t *end = (freeMemNode_t *)( memoryPool + POOLSIZE ); + void *p; + + Com_Printf( "%p-%p: %d out of %d bytes allocated\n", + fmn, end, POOLSIZE - freeMem, POOLSIZE ); + + while( fmn < end ) + { + size = chunks = 0; + p = fmn; + while( fmn < end && fmn->cookie == FREEMEMCOOKIE ) + { + size += fmn->size; + chunks++; + fmn = (freeMemNode_t *)( (char *)fmn + fmn->size ); + } + if( size ) + Com_Printf( " %p: %d bytes free (%d chunks)\n", p, size, chunks ); + size = chunks = 0; + p = fmn; + while( fmn < end && fmn->cookie != FREEMEMCOOKIE ) + { + size += *(int *)fmn; + chunks++; + fmn = (freeMemNode_t *)( (size_t)fmn + *(int *)fmn ); + } + if( size ) + Com_Printf( " %p: %d bytes allocated (%d chunks)\n", p, size, chunks ); + } +} diff --git a/src/game/bg_lib.c b/src/game/bg_lib.c index 69bca48..73b9a5d 100644 --- a/src/game/bg_lib.c +++ b/src/game/bg_lib.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -25,7 +26,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // compiled for the virtual machine -#include "../qcommon/q_shared.h" +#ifdef Q3_VM + +#include "qcommon/q_shared.h" /*- * Copyright (c) 1992, 1993 @@ -67,21 +70,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; #endif static const char rcsid[] = - "$Id: bg_lib.c 965 2007-08-09 13:54:12Z msk $"; + "$Id$"; #endif /* LIBC_SCCS and not lint */ -// bk001127 - needed for DLL's -#if !defined( Q3_VM ) -typedef int cmp_t(const void *, const void *); -#endif - static char* med3(char *, char *, char *, cmp_t *); static void swapfunc(char *, char *, int, int); -#ifndef min -#define min(a, b) (a) < (b) ? a : b -#endif - /* * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". */ @@ -197,9 +191,9 @@ loop: SWAPINIT(a, es); } pn = (char *)a + n * es; - r = min(pa - (char *)a, pb - pa); + r = MIN(pa - (char *)a, pb - pa); vecswap(a, pb - r, r); - r = min(pd - pc, pn - pd - es); + r = MIN(pd - pc, pn - pd - es); vecswap(pb, pn - r, r); if ((r = pb - pa) > es) qsort(a, r / es, es, cmp); @@ -215,11 +209,6 @@ loop: SWAPINIT(a, es); //================================================================================== -// this file is excluded from release builds because of intrinsics - -// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' -#if defined ( Q3_VM ) - size_t strlen( const char *string ) { const char *s; @@ -272,13 +261,12 @@ int strcmp( const char *string1, const char *string2 ) return *string1 - *string2; } -//TA: char *strrchr( const char *string, int c ) { int i, length = strlen( string ); char *p; - for( i = length - 1; i >= 0; i-- ) + for( i = length /*sic*/; i >= 0; i-- ) { p = (char *)&string[ i ]; @@ -298,7 +286,7 @@ char *strchr( const char *string, int c ) string++; } - return (char *)0; + return c == '\0' ? (char *)string : (char *)0; } char *strstr( const char *string, const char *strCharSet ) @@ -321,10 +309,6 @@ char *strstr( const char *string, const char *strCharSet ) return (char *)0; } -#endif // bk001211 - -#if defined ( Q3_VM ) - int tolower( int c ) { if( c >= 'A' && c <= 'Z' ) @@ -342,21 +326,23 @@ int toupper( int c ) return c; } -#endif - void *memmove( void *dest, const void *src, size_t count ) { - int i; + size_t i; if( dest > src ) { - for( i = count - 1; i >= 0; i-- ) - ( (char *)dest )[ i ] = ( (char *)src )[ i ]; + i = count; + while( i > 0 ) + { + i--; + ((char *)dest)[ i ] = ((char *)src)[ i ]; + } } else { for( i = 0; i < count; i++ ) - ( (char *)dest )[ i ] = ( (char *)src )[ i ]; + ((char *) dest)[ i ] = ((char *)src)[ i ]; } return dest; @@ -816,7 +802,6 @@ double atan2( double y, double x ) { #endif -#ifdef Q3_VM /* =============== rint @@ -830,8 +815,43 @@ double rint( double v ) return floor( v ); } -// bk001127 - guarded this tan replacement -// ld: undefined versioned symbol name tan@@GLIBC_2.0 +/* +=============== +powN + +Raise a double to a integer power +=============== +*/ +static double powN( double base, int exp ) +{ + if( exp >= 0 ) + { + double result = 1.0; + + // calculate x, x^2, x^4, ... by repeated squaring + // and multiply together the ones corresponding to the + // binary digits of the exponent + // e.g. x^73 = x^(1 + 8 + 64) = x * x^8 * x^64 + while( exp > 0 ) + { + if( exp % 2 == 1 ) + result *= base; + + base *= base; + exp /= 2; + } + + return result; + } + // if exp is INT_MIN, the next clause will be upset, + // because -exp isn't representable + else if( exp == INT_MIN ) + return powN( base, exp + 1 ) / base; + // x < 0 + else + return 1.0 / powN( base, -exp ); +} + double tan( double x ) { return sin( x ) / cos( x ); @@ -1316,8 +1336,6 @@ float pow( float x, float y ) return s * z; } -#endif - static int randSeed = 0; @@ -1409,12 +1427,210 @@ double atof( const char *string ) return value * sign; } +/* +============== +strtod + +Without an errno variable, this is a fair bit less useful than it is in libc +but it's still a fair bit more capable than atof or _atof +Handles inf[inity], nan (ignoring case), hexadecimals, and decimals +Handles decimal exponents like 10e10 and hex exponents like 0x7f8p20 +10e10 == 10000000000 (power of ten) +0x7f8p20 == 0x7f800000 (decimal power of two) +The variable pointed to by endptr will hold the location of the first character +in the nptr string that was not used in the conversion +============== +*/ +double strtod( const char *nptr, char **endptr ) +{ + double res; + qboolean neg = qfalse; + + // skip whitespace + while( isspace( *nptr ) ) + nptr++; + + // special string parsing + if( Q_stricmpn( nptr, "nan", 3 ) == 0 ) + { + floatint_t nan; + + if( endptr ) + *endptr = (char *)&nptr[3]; + + // nan can be followed by a bracketed number (in hex, octal, + // or decimal) which is then put in the mantissa + // this can be used to generate signalling or quiet NaNs, for + // example (though I doubt it'll ever be used) + // note that nan(0) is infinity! + if( nptr[3] == '(' ) + { + char *end; + int mantissa = strtol( &nptr[4], &end, 0 ); + + if( *end == ')' ) + { + nan.ui = 0x7f800000 | ( mantissa & 0x7fffff ); + + if( endptr ) + *endptr = &end[1]; + return nan.f; + } + } + + nan.ui = 0x7fffffff; + return nan.f; + } + + if( Q_stricmpn( nptr, "inf", 3 ) == 0 ) + { + floatint_t inf; + inf.ui = 0x7f800000; + + if( endptr == NULL ) + return inf.f; + + if( Q_stricmpn( &nptr[3], "inity", 5 ) == 0 ) + *endptr = (char *)&nptr[8]; + else + *endptr = (char *)&nptr[3]; + + return inf.f; + } + + // normal numeric parsing + // sign + if( *nptr == '-' ) + { + nptr++; + neg = qtrue; + } + else if( *nptr == '+' ) + nptr++; + + // hex + if( Q_stricmpn( nptr, "0x", 2 ) == 0 ) + { + // track if we use any digits + const char *s = &nptr[1], *end = s; + nptr += 2; + + for( res = 0;; ) + { + if( isdigit( *nptr ) ) + res = 16 * res + ( *nptr++ - '0' ); + else if( *nptr >= 'A' && *nptr <= 'F' ) + res = 16 * res + 10 + *nptr++ - 'A'; + else if( *nptr >= 'a' && *nptr <= 'f' ) + res = 16 * res + 10 + *nptr++ - 'a'; + else + break; + } + + // if nptr moved, save it + if( end + 1 < nptr ) + end = nptr; + + if( *nptr == '.' ) + { + float place; + nptr++; + + // 1.0 / 16.0 == 0.0625 + // I don't expect the float accuracy to hold out for + // very long but since we need to know the length of + // the string anyway we keep on going regardless + for( place = 0.0625;; place /= 16.0 ) + { + if( isdigit( *nptr ) ) + res += place * ( *nptr++ - '0' ); + else if( *nptr >= 'A' && *nptr <= 'F' ) + res += place * ( 10 + *nptr++ - 'A' ); + else if( *nptr >= 'a' && *nptr <= 'f' ) + res += place * ( 10 + *nptr++ - 'a' ); + else + break; + } + + if( end < nptr ) + end = nptr; + } + + // parse an optional exponent, representing multiplication + // by a power of two + // exponents are only valid if we encountered at least one + // digit already (and have therefore set end to something) + if( end != s && tolower( *nptr ) == 'p' ) + { + int exp; + // apparently (confusingly) the exponent should be + // decimal + exp = strtol( &nptr[1], (char **)&end, 10 ); + if( &nptr[1] == end ) + { + // no exponent + if( endptr ) + *endptr = (char *)nptr; + return res; + } + + res *= powN( 2, exp ); + } + if( endptr ) + *endptr = (char *)end; + return res; + } + // decimal + else + { + // track if we find any digits + const char *end = nptr, *p = nptr; + // this is most of the work + for( res = 0; isdigit( *nptr ); + res = 10 * res + *nptr++ - '0' ); + // if nptr moved, we read something + if( end < nptr ) + end = nptr; + if( *nptr == '.' ) + { + // fractional part + float place; + nptr++; + for( place = 0.1; isdigit( *nptr ); place /= 10.0 ) + res += ( *nptr++ - '0' ) * place; + // if nptr moved, we read something + if( end + 1 < nptr ) + end = nptr; + } + // exponent + // meaningless without having already read digits, so check + // we've set end to something + if( p != end && tolower( *nptr ) == 'e' ) + { + int exp; + exp = strtol( &nptr[1], (char **)&end, 10 ); + if( &nptr[1] == end ) + { + // no exponent + if( endptr ) + *endptr = (char *)nptr; + return res; + } + + res *= powN( 10, exp ); + } + if( endptr ) + *endptr = (char *)end; + return res; + } +} + double _atof( const char **stringPtr ) { const char *string; float sign; float value; - int c = '0'; // bk001211 - uninitialized use possible + int c = '0'; string = *stringPtr; @@ -1488,8 +1704,108 @@ double _atof( const char **stringPtr ) return value * sign; } +/* +============== +strtol + +Handles any base from 2 to 36. If base is 0 then it guesses +decimal, hex, or octal based on the format of the number (leading 0 or 0x) +Will not overflow - returns LONG_MIN or LONG_MAX as appropriate +*endptr is set to the location of the first character not used +============== +*/ +long strtol( const char *nptr, char **endptr, int base ) +{ + long res; + qboolean pos = qtrue; + + if( endptr ) + *endptr = (char *)nptr; + + // bases other than 0, 2, 8, 16 are very rarely used, but they're + // not much extra effort to support + if( base < 0 || base == 1 || base > 36 ) + return 0; + + // skip leading whitespace + while( isspace( *nptr ) ) + nptr++; + + // sign + if( *nptr == '-' ) + { + nptr++; + pos = qfalse; + } + else if( *nptr == '+' ) + nptr++; -#if defined ( Q3_VM ) + // look for base-identifying sequences e.g. 0x for hex, 0 for octal + if( nptr[0] == '0' ) + { + nptr++; + + // 0 is always a valid digit + if( endptr ) + *endptr = (char *)nptr; + + if( *nptr == 'x' || *nptr == 'X' ) + { + if( base != 0 && base != 16 ) + { + // can't be hex, reject x (accept 0) + if( endptr ) + *endptr = (char *)nptr; + return 0; + } + + nptr++; + base = 16; + } + else if( base == 0 ) + base = 8; + } + else if( base == 0 ) + base = 10; + + for( res = 0;; ) + { + int val; + + if( isdigit( *nptr ) ) + val = *nptr - '0'; + else if( islower( *nptr ) ) + val = 10 + *nptr - 'a'; + else if( isupper( *nptr ) ) + val = 10 + *nptr - 'A'; + else + break; + + if( val >= base ) + break; + + // we go negative because LONG_MIN is further from 0 than + // LONG_MAX + if( res < ( LONG_MIN + val ) / base ) + res = LONG_MIN; // overflow + else + res = res * base - val; + + nptr++; + + if( endptr ) + *endptr = (char *)nptr; + } + if( pos ) + { + // can't represent LONG_MIN positive + if( res == LONG_MIN ) + res = LONG_MAX; + else + res = -res; + } + return res; +} int atoi( const char *string ) { @@ -1613,9 +1929,9 @@ unsigned int _hextoi( const char **stringPtr ) int c; int i; const char *string; - + string = *stringPtr; - + // skip whitespace while( *string <= ' ' ) { @@ -1653,356 +1969,751 @@ unsigned int _hextoi( const char **stringPtr ) //========================================================= +/* + * New implementation by Patrick Powell and others for vsnprintf. + * Supports length checking in strings. + */ -#define ALT 0x00000001 /* alternate form */ -#define HEX 0x00000002 /* hexadecimal */ -#define LADJUST 0x00000004 /* left adjustment */ -#define LONGDBL 0x00000008 /* long double */ -#define LONGINT 0x00000010 /* long integer */ -#define QUADINT 0x00000020 /* quad integer */ -#define SHORTINT 0x00000040 /* short integer */ -#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ -#define FPT 0x00000100 /* floating point number */ -#define UNSIGNED 0x00000200 /* unsigned integer */ +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ -#define to_digit(c) ((c) - '0') -#define is_digit(c) ((unsigned)to_digit(c) <= 9) -#define to_char(n) ((n) + '0') +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh. This sort of thing is always nasty do deal with. Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length. This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + * Brandon Long 9/15/96 for mutt 0.43 + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything + * from the normal C string format, at least as far as I can tell from + * the Solaris 2.5 printf(3S) man page. + * + * Brandon Long 10/22/97 for mutt 0.87.1 + * Ok, added some minimal floating point support, which means this + * probably requires libm on most operating systems. Don't yet + * support the exponent (e,E) and sigfig (g,G). Also, fmtint() + * was pretty badly broken, it just wasn't being exercised in ways + * which showed it, so that's been fixed. Also, formated the code + * to mutt conventions, and removed dead code left over from the + * original. Also, there is now a builtin-test, just compile with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm + * and run snprintf for results. + * + * Thomas Roessler 01/27/98 for mutt 0.89i + * The PGP code was using unsigned hexadecimal formats. + * Unfortunately, unsigned formats simply didn't work. + * + * Michael Elkins 03/05/98 for mutt 0.90.8 + * The original code assumed that both snprintf() and vsnprintf() were + * missing. Some systems only have snprintf() but not vsnprintf(), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * Andrew Tridgell (tridge@samba.org) Oct 1998 + * fixed handling of %.0f + * added test for HAVE_LONG_DOUBLE + * + * Russ Allbery 2000-08-26 + * fixed return value to comply with C99 + * fixed handling of snprintf(NULL, ...) + * + * Hrvoje Niksic 2000-11-04 + * include instead of "config.h". + * moved TEST_SNPRINTF stuff out of HAVE_SNPRINTF ifdef. + * include for NULL. + * added support and test cases for long long. + * don't declare argument types to (v)snprintf if stdarg is not used. + * use int instead of short int as 2nd arg to va_arg. + * + **************************************************************/ -void AddInt( char **buf_p, int val, int width, int flags ) -{ - char text[ 32 ]; - int digits; - char *buf; +/* BDR 2002-01-13 %e and %g were being ignored. Now do something, + if not necessarily correctly */ + +#if (SIZEOF_LONG_DOUBLE > 0) +/* #ifdef HAVE_LONG_DOUBLE */ +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif - digits = 0; +#if (SIZEOF_LONG_LONG > 0) +/* #ifdef HAVE_LONG_LONG */ +# define LLONG long long +#else +# define LLONG long +#endif - if( flags & UNSIGNED ) - val = (unsigned) val; +static int dopr (char *buffer, size_t maxlen, const char *format, + va_list args); +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max); +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, + LLONG value, int base, int min, int max, int flags); +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags); +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c ); - if( flags & HEX ) +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS 1 +#define DP_S_MIN 2 +#define DP_S_DOT 3 +#define DP_S_MAX 4 +#define DP_S_MOD 5 +#define DP_S_MOD_L 6 +#define DP_S_CONV 7 +#define DP_S_DONE 8 + +/* format flags - Bits */ +#define DP_F_MINUS (1 << 0) +#define DP_F_PLUS (1 << 1) +#define DP_F_SPACE (1 << 2) +#define DP_F_NUM (1 << 3) +#define DP_F_ZERO (1 << 4) +#define DP_F_UP (1 << 5) +#define DP_F_UNSIGNED (1 << 6) + +/* Conversion Flags */ +#define DP_C_SHORT 1 +#define DP_C_LONG 2 +#define DP_C_LLONG 3 +#define DP_C_LDOUBLE 4 + +#define char_to_int(p) (p - '0') + +static int dopr (char *buffer, size_t maxlen, const char *format, va_list args) +{ + char ch; + LLONG value; + LDOUBLE fvalue; + char *strvalue; + int min; + int max; + int state; + int flags; + int cflags; + int total; + size_t currlen; + + state = DP_S_DEFAULT; + currlen = flags = cflags = min = 0; + max = -1; + ch = *format++; + total = 0; + + while (state != DP_S_DONE) { - char c; - int n = 0; + if (ch == '\0') + state = DP_S_DONE; - while( n < 32 ) + switch(state) { - c = "0123456789abcdef"[ ( val >> n ) & 0xF ]; - n += 4; - if( c == '0' && !digits ) - continue; - text[ digits++ ] = c; + case DP_S_DEFAULT: + if (ch == '%') + state = DP_S_FLAGS; + else + total += dopr_outch (buffer, &currlen, maxlen, ch); + ch = *format++; + break; + case DP_S_FLAGS: + switch (ch) + { + case '-': + flags |= DP_F_MINUS; + ch = *format++; + break; + case '+': + flags |= DP_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= DP_F_SPACE; + ch = *format++; + break; + case '#': + flags |= DP_F_NUM; + ch = *format++; + break; + case '0': + flags |= DP_F_ZERO; + ch = *format++; + break; + default: + state = DP_S_MIN; + break; + } + break; + case DP_S_MIN: + if ('0' <= ch && ch <= '9') + { + min = 10*min + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + min = va_arg (args, int); + ch = *format++; + state = DP_S_DOT; + } + else + state = DP_S_DOT; + break; + case DP_S_DOT: + if (ch == '.') + { + state = DP_S_MAX; + ch = *format++; + } + else + state = DP_S_MOD; + break; + case DP_S_MAX: + if ('0' <= ch && ch <= '9') + { + if (max < 0) + max = 0; + max = 10*max + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + max = va_arg (args, int); + ch = *format++; + state = DP_S_MOD; + } + else + state = DP_S_MOD; + break; + case DP_S_MOD: + switch (ch) + { + case 'h': + cflags = DP_C_SHORT; + ch = *format++; + break; + case 'l': + cflags = DP_C_LONG; + ch = *format++; + break; + case 'L': + cflags = DP_C_LDOUBLE; + ch = *format++; + break; + default: + break; + } + if (cflags != DP_C_LONG) + state = DP_S_CONV; + else + state = DP_S_MOD_L; + break; + case DP_S_MOD_L: + switch (ch) + { + case 'l': + cflags = DP_C_LLONG; + ch = *format++; + break; + default: + break; + } + state = DP_S_CONV; + break; + case DP_S_CONV: + switch (ch) + { + case 'd': + case 'i': + if (cflags == DP_C_SHORT) + value = (short int)va_arg (args, int); + else if (cflags == DP_C_LONG) + value = va_arg (args, long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, LLONG); + else + value = va_arg (args, int); + total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'o': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + // value = (unsigned short int) va_arg (args, unsigned short int); // Thilo: This does not work because the rcc compiler cannot do that cast correctly. + value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); // Using this workaround instead. + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, unsigned LLONG); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags); + break; + case 'u': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, unsigned LLONG); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'X': + flags |= DP_F_UP; + case 'x': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, unsigned LLONG); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags); + break; + case 'f': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'E': + flags |= DP_F_UP; + case 'e': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'G': + flags |= DP_F_UP; + case 'g': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'c': + total += dopr_outch (buffer, &currlen, maxlen, va_arg (args, int)); + break; + case 's': + strvalue = va_arg (args, char *); + total += fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max); + break; + case 'p': + strvalue = va_arg (args, void *); + total += fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, + max, flags); + break; + case 'n': + if (cflags == DP_C_SHORT) + { + short int *num; + num = va_arg (args, short int *); + *num = currlen; + } + else if (cflags == DP_C_LONG) + { + long int *num; + num = va_arg (args, long int *); + *num = currlen; + } + else if (cflags == DP_C_LLONG) + { + LLONG *num; + num = va_arg (args, LLONG *); + *num = currlen; + } + else + { + int *num; + num = va_arg (args, int *); + *num = currlen; + } + break; + case '%': + total += dopr_outch (buffer, &currlen, maxlen, ch); + break; + case 'w': + /* not supported yet, treat as next char */ + ch = *format++; + break; + default: + /* Unknown, skip */ + break; + } + ch = *format++; + state = DP_S_DEFAULT; + flags = cflags = min = 0; + max = -1; + break; + case DP_S_DONE: + break; + default: + /* hmm? */ + break; /* some picky compilers need this */ } - text[ digits ] = '\0'; } - else - { - int signedVal = val; + if (maxlen > 0) + buffer[currlen] = '\0'; + return total; +} - if( val < 0 ) - val = -val; - do - { - text[ digits++ ] = '0' + val % 10; - val /= 10; - } while( val ); +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max) +{ + int padlen, strln; /* amount to pad */ + int cnt = 0; + int total = 0; - if( signedVal < 0 ) - text[ digits++ ] = '-'; + if (value == 0) + { + value = ""; } - buf = *buf_p; + for (strln = 0; value[strln]; ++strln); /* strlen */ + if (max >= 0 && max < strln) + strln = max; + padlen = min - strln; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ - if( !( flags & LADJUST ) ) + while (padlen > 0) { - while( digits < width ) - { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; - width--; - } + total += dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; } - - while( digits-- ) + while (*value && ((max < 0) || (cnt < max))) { - *buf++ = text[ digits ]; - width--; + total += dopr_outch (buffer, currlen, maxlen, *value++); + ++cnt; } - - if( flags & LADJUST ) + while (padlen < 0) { - while( width-- > 0 ) - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; } - - *buf_p = buf; + return total; } -void AddFloat( char **buf_p, float fval, int width, int prec ) +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, + LLONG value, int base, int min, int max, int flags) { - char text[ 32 ]; - int digits; - float signedVal; - char *buf; - int val; + int signvalue = 0; + unsigned LLONG uvalue; + char convert[24]; + int place = 0; + int spadlen = 0; /* amount to space pad */ + int zpadlen = 0; /* amount to zero pad */ + const char *digits; + int total = 0; - // get the sign - signedVal = fval; - if( fval < 0 ) - fval = -fval; + if (max < 0) + max = 0; - // write the float number - digits = 0; - val = (int)fval; + uvalue = value; - do + if(!(flags & DP_F_UNSIGNED)) { - text[ digits++ ] = '0' + val % 10; - val /= 10; - } while( val ); - - if( signedVal < 0 ) - text[digits++] = '-'; + if( value < 0 ) { + signvalue = '-'; + uvalue = -value; + } + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + } - buf = *buf_p; + if (flags & DP_F_UP) + /* Should characters be upper case? */ + digits = "0123456789ABCDEF"; + else + digits = "0123456789abcdef"; - while( digits < width ) + do { + convert[place++] = digits[uvalue % (unsigned)base]; + uvalue = (uvalue / (unsigned)base ); + } while(uvalue && (place < sizeof (convert))); + if (place == sizeof (convert)) place--; + convert[place] = 0; + + zpadlen = max - place; + spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); + if (zpadlen < 0) zpadlen = 0; + if (spadlen < 0) spadlen = 0; + if (flags & DP_F_ZERO) { - *buf++ = ' '; - width--; + zpadlen = MAX(zpadlen, spadlen); + spadlen = 0; } + if (flags & DP_F_MINUS) + spadlen = -spadlen; /* Left Justifty */ - while( digits-- ) - *buf++ = text[ digits ]; - - *buf_p = buf; +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", + zpadlen, spadlen, min, max, place)); +#endif - if( prec < 0 ) - prec = 6; + /* Spaces */ + while (spadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --spadlen; + } - // write the fraction - digits = 0; + /* Sign */ + if (signvalue) + total += dopr_outch (buffer, currlen, maxlen, signvalue); - while( digits < prec ) + /* Zeros */ + if (zpadlen > 0) { - fval -= (int)fval; - fval *= 10.0; - val = (int)fval; - text[ digits++ ] = '0' + val % 10; + while (zpadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } } - if( digits > 0 ) - { - buf = *buf_p; - *buf++ = '.'; - for( prec = 0; prec < digits; prec++ ) - *buf++ = text[ prec ]; + /* Digits */ + while (place > 0) + total += dopr_outch (buffer, currlen, maxlen, convert[--place]); - *buf_p = buf; + /* Left Justified spaces */ + while (spadlen < 0) { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++spadlen; } + + return total; } -void AddVec3_t( char **buf_p, vec3_t v, int width, int prec ) +static LDOUBLE abs_val (LDOUBLE value) { - char *buf; + LDOUBLE result = value; - buf = *buf_p; + if (value < 0) + result = -value; - *buf++ = '['; - - AddFloat( &buf, v[ 0 ], width, prec ); - buf += width; - *buf++ = ' '; + return result; +} - AddFloat( &buf, v[ 1 ], width, prec ); - buf += width; - *buf++ = ' '; +static long round (LDOUBLE value) +{ + long intpart; - AddFloat( &buf, v[ 2 ], width, prec ); - buf += width; - *buf++ = ']'; + intpart = value; + value = value - intpart; + if (value >= 0.5) + intpart++; - *buf_p = buf; + return intpart; } -void AddString( char **buf_p, char *string, int width, int prec ) +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags) { - int size; - char *buf; + int signvalue = 0; + LDOUBLE ufvalue; + char iconvert[20]; + char fconvert[20]; + int iplace = 0; + int fplace = 0; + int padlen = 0; /* amount to pad */ + int zpadlen = 0; + int caps = 0; + int total = 0; + long intpart; + long fracpart; - buf = *buf_p; + /* + * AIX manpage says the default is 0, but Solaris says the default + * is 6, and sprintf on AIX defaults to 6 + */ + if (max < 0) + max = 6; - if( string == NULL ) - { - string = "(null)"; - prec = -1; - } + ufvalue = abs_val (fvalue); - if( prec >= 0 ) - { - for( size = 0; size < prec; size++ ) - { - if( string[ size ] == '\0' ) - break; - } - } + if (fvalue < 0) + signvalue = '-'; else - size = strlen( string ); - - width -= size; + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; - while( size-- ) - *buf++ = *string++; +#if 0 + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif - while( width-- > 0 ) - *buf++ = ' '; + intpart = ufvalue; - *buf_p = buf; -} + /* + * Sorry, we only support 9 digits past the decimal because of our + * conversion method + */ + if (max > 9) + max = 9; -/* -vsprintf + /* We "cheat" by converting the fractional part to integer by + * multiplying by a factor of 10 + */ + fracpart = round ((powN (10, max)) * (ufvalue - intpart)); -I'm not going to support a bunch of the more arcane stuff in here -just to keep it simpler. For example, the '*' and '$' are not -currently supported. I've tried to make it so that it will just -parse and ignore formats we don't support. -*/ -int vsprintf( char *buffer, const char *fmt, va_list argptr ) -{ - int *arg; - char *buf_p; - char ch; - int flags; - int width; - int prec; - int n; - char sign; - - buf_p = buffer; - arg = (int *)argptr; - - while( qtrue ) + if (fracpart >= powN (10, max)) { - // run through the format string until we hit a '%' or '\0' - for( ch = *fmt; ( ch = *fmt ) != '\0' && ch != '%'; fmt++ ) - *buf_p++ = ch; + intpart++; + fracpart -= powN (10, max); + } - if( ch == '\0' ) - goto done; +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart)); +#endif - // skip over the '%' - fmt++; + /* Convert integer part */ + do { + iconvert[iplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; + intpart = (intpart / 10); + } while(intpart && (iplace < 20)); + if (iplace == 20) iplace--; + iconvert[iplace] = 0; + + /* Convert fractional part */ + do { + fconvert[fplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; + fracpart = (fracpart / 10); + } while(fracpart && (fplace < 20)); + if (fplace == 20) fplace--; + fconvert[fplace] = 0; + + /* -1 for decimal point, another -1 if we are printing a sign */ + padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); + zpadlen = max - fplace; + if (zpadlen < 0) + zpadlen = 0; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justifty */ + + if ((flags & DP_F_ZERO) && (padlen > 0)) + { + if (signvalue) + { + total += dopr_outch (buffer, currlen, maxlen, signvalue); + --padlen; + signvalue = 0; + } + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --padlen; + } + } + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + if (signvalue) + total += dopr_outch (buffer, currlen, maxlen, signvalue); - // reset formatting state - flags = 0; - width = 0; - prec = -1; - sign = '\0'; + while (iplace > 0) + total += dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); -rflag: - ch = *fmt++; -reswitch: - switch( ch ) - { - case '-': - flags |= LADJUST; - goto rflag; - - case '.': - n = 0; - while( is_digit( ( ch = *fmt++ ) ) ) - n = 10 * n + ( ch - '0' ); - - prec = n < 0 ? -1 : n; - goto reswitch; - - case '0': - flags |= ZEROPAD; - goto rflag; - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - n = 0; - do - { - n = 10 * n + ( ch - '0' ); - ch = *fmt++; - } while( is_digit( ch ) ); + /* + * Decimal point. This should probably use locale to find the correct + * char to print out. + */ + if (max > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '.'); - width = n; - goto reswitch; + while (zpadlen-- > 0) + total += dopr_outch (buffer, currlen, maxlen, '0'); - case 'c': - *buf_p++ = (char)*arg; - arg++; - break; + while (fplace > 0) + total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); + } - case 'u': - flags |= UNSIGNED; - case 'd': - case 'i': - AddInt( &buf_p, *arg, width, flags ); - arg++; - break; + while (padlen < 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } - case 'f': - AddFloat( &buf_p, *(double *)arg, width, prec ); -#ifdef Q3_VM - arg += 1; // everything is 32 bit in my compiler -#else - arg += 2; -#endif - break; + return total; +} - case 's': - AddString( &buf_p, (char *)*arg, width, prec ); - arg++; - break; +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c) +{ + if (*currlen + 1 < maxlen) + buffer[(*currlen)++] = c; + return 1; +} - case 'v': - AddVec3_t( &buf_p, (vec_t *)*arg, width, prec ); - arg++; - break; - - case 'x': - flags |= HEX; - AddInt( &buf_p, *arg, width, prec ); - arg++; - break; +int Q_vsnprintf(char *str, size_t length, const char *fmt, va_list args) +{ + return dopr(str, length, fmt, args); +} - case '%': - *buf_p++ = ch; - break; +int Q_snprintf(char *str, size_t length, const char *fmt, ...) +{ + va_list ap; + int retval; - default: - *buf_p++ = (char)*arg; - arg++; - break; - } - } + va_start(ap, fmt); + retval = Q_vsnprintf(str, length, fmt, ap); + va_end(ap); -done: - *buf_p = 0; - return buf_p - buffer; + return retval; } - /* this is really crappy */ -// FIXME: count is still inaccurate in some cases. int sscanf( const char *buffer, const char *fmt, ... ) { int cmd; - int **arg; + va_list ap; int count; + size_t len; - arg = (int **)&fmt + 1; + va_start( ap, fmt ); count = 0; while( *fmt ) @@ -2013,33 +2724,70 @@ int sscanf( const char *buffer, const char *fmt, ... ) continue; } - if( !buffer[ 0 ] ) break; + fmt++; + cmd = *fmt; - cmd = fmt[ 1 ]; - fmt += 2; + if( isdigit( cmd ) ) + { + len = (size_t)_atoi( &fmt ); + cmd = *( fmt - 1 ); + } + else + { + len = MAX_STRING_CHARS - 1; + fmt++; + } switch( cmd ) { case 'i': case 'd': case 'u': - **arg = _atoi( &buffer ); - ++count; + *( va_arg( ap, int * ) ) = _atoi( &buffer ); break; case 'f': - *(float *)*arg = _atof( &buffer ); - ++count; + *( va_arg( ap, float * ) ) = _atof( &buffer ); break; case 'x': - **arg = _hextoi( &buffer ); - ++count; + *( va_arg( ap, unsigned int * ) ) = _hextoi( &buffer ); + break; + case 's': + { + char *s = va_arg( ap, char * ); + while( isspace( *buffer ) ) + buffer++; + while( *buffer && !isspace( *buffer) && len-- > 0 ) + *s++ = *buffer++; + *s++ = '\0'; break; + } } - - arg++; } + va_end( ap ); return count; } +void *bsearch( const void *key, const void *base, size_t nmemb, size_t size, + cmp_t *compar ) +{ + size_t low = 0, high = nmemb, mid; + int comp; + void *ptr; + + while( low < high ) + { + mid = low + (high - low) / 2; + ptr = (void *)((char *)base + ( mid * size )); + comp = compar (key, ptr); + if( comp < 0 ) + high = mid; + else if( comp > 0 ) + low = mid + 1; + else + return ptr; + } + return NULL; +} + #endif diff --git a/src/game/bg_lib.h b/src/game/bg_lib.h index 962a625..a853654 100644 --- a/src/game/bg_lib.h +++ b/src/game/bg_lib.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -25,14 +26,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // compiled for the virtual machine // This file is NOT included on native builds -#ifndef BG_LIB_H +#if !defined( BG_LIB_H ) && defined( Q3_VM ) #define BG_LIB_H #ifndef NULL #define NULL ((void *)0) #endif -typedef int size_t; +typedef unsigned int size_t; typedef char * va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) @@ -40,21 +41,28 @@ typedef char * va_list; #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) -#define CHAR_BIT 8 /* number of bits in a char */ -#define SCHAR_MIN (-128) /* minimum signed char value */ -#define SCHAR_MAX 127 /* maximum signed char value */ -#define UCHAR_MAX 0xff /* maximum unsigned char value */ +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MAX 0x7f /* maximum signed char value */ +#define SCHAR_MIN (-SCHAR_MAX - 1)/* minimum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ -#define SHRT_MIN (-32768) /* minimum (signed) short value */ -#define SHRT_MAX 32767 /* maximum (signed) short value */ +#define SHRT_MAX 0x7fff /* maximum (signed) short value */ +#define SHRT_MIN (-SHRT_MAX - 1) /* minimum (signed) short value */ #define USHRT_MAX 0xffff /* maximum unsigned short value */ -#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ -#define INT_MAX 2147483647 /* maximum (signed) int value */ +#define INT_MAX 0x7fffffff /* maximum (signed) int value */ +#define INT_MIN (-INT_MAX - 1) /* minimum (signed) int value */ #define UINT_MAX 0xffffffff /* maximum unsigned int value */ -#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ -#define LONG_MAX 2147483647L /* maximum (signed) long value */ +#define LONG_MAX 0x7fffffffL /* maximum (signed) long value */ +#define LONG_MIN (-LONG_MAX - 1) /* minimum (signed) long value */ #define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; + #define isalnum(c) (isalpha(c) || isdigit(c)) #define isalpha(c) (isupper(c) || islower(c)) #define isascii(c) ((c) > 0 && (c) <= 0x7f) @@ -72,10 +80,17 @@ typedef char * va_list; #define isxupper(c) (isdigit(c) || (c >= 'A' && c <= 'F')) // Misc functions +#define assert( expr )\ + if( !( expr ) )\ + Com_Error( ERR_DROP, "%s:%d: Assertion `%s' failed",\ + __FILE__, __LINE__, #expr ) typedef int cmp_t( const void *, const void * ); void qsort( void *a, size_t n, size_t es, cmp_t *cmp ); +#define RAND_MAX 0x7fff void srand( unsigned seed ); int rand( void ); +void *bsearch( const void *key, const void *base, size_t nmemb, + size_t size, cmp_t *compar ); // String functions size_t strlen( const char *string ); @@ -91,11 +106,14 @@ int toupper( int c ); double atof( const char *string ); double _atof( const char **stringPtr ); +double strtod( const char *nptr, char **endptr ); int atoi( const char *string ); int _atoi( const char **stringPtr ); +long strtol( const char *nptr, char **endptr, int base ); +int Q_vsnprintf( char *buffer, size_t length, const char *fmt, va_list argptr ); +int Q_snprintf( char *buffer, size_t length, const char *fmt, ... ) __attribute__ ((format (printf, 3, 4))); -int vsprintf( char *buffer, const char *fmt, va_list argptr ); int sscanf( const char *buffer, const char *fmt, ... ); // Memory functions diff --git a/src/game/bg_local.h b/src/game/bg_local.h index 354214c..1a67c49 100644 --- a/src/game/bg_local.h +++ b/src/game/bg_local.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -31,8 +32,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TIMER_GESTURE (34*66+50) #define TIMER_ATTACK 500 //nonsegmented models -#define OVERCLIP 1.001f - #define FALLING_THRESHOLD -900.0f //what vertical speed to start falling sound at @@ -65,20 +64,19 @@ extern pml_t pml; extern float pm_stopspeed; extern float pm_duckScale; extern float pm_swimScale; -extern float pm_wadeScale; extern float pm_accelerate; -extern float pm_airaccelerate; extern float pm_wateraccelerate; extern float pm_flyaccelerate; extern float pm_friction; extern float pm_waterfriction; extern float pm_flightfriction; +extern float pm_spectatorfriction; extern int c_pmove; -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out ); void PM_AddTouchEnt( int entityNum ); void PM_AddEvent( int newEvent ); diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c index fd50509..e30a754 100644 --- a/src/game/bg_misc.c +++ b/src/game/bg_misc.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,45 +17,44 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // bg_misc.c -- both games misc functions, all completely stateless -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h" #include "bg_public.h" -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ); void trap_FS_Read( void *buffer, int len, fileHandle_t f ); void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); -void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +void trap_FS_Seek( fileHandle_t f, long offset, enum FS_Origin origin ); // fsOrigin_t +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); -buildableAttributes_t bg_buildableList[ ] = +static const buildableAttributes_t bg_buildableList[ ] = { { - BA_A_SPAWN, //int buildNum; - "eggpod", //char *buildName; + BA_A_SPAWN, //int number; + "eggpod", //char *name; "Egg", //char *humanName; + "The most basic alien structure. It allows aliens to spawn " + "and protect the Overmind. Without any of these, the Overmind " + "is left nearly defenseless and defeat is imminent.", "team_alien_spawn", //char *entityName; - { "models/buildables/eggpod/eggpod.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -15, -15, -15 }, //vec3_t mins; - { 15, 15, 15 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; ASPAWN_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; ASPAWN_HEALTH, //int health; ASPAWN_REGEN, //int regenRate; ASPAWN_SPLASHDAMAGE, //int splashDamage; ASPAWN_SPLASHRADIUS, //int splashRadius; MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; ASPAWN_BT, //int buildTime; @@ -68,70 +68,66 @@ buildableAttributes_t bg_buildableList[ ] = ASPAWN_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; + qfalse, //qboolean uniqueTest; + ASPAWN_VALUE, //int value; }, { - BA_A_BARRICADE, //int buildNum; - "barricade", //char *buildName; - "Barricade", //char *humanName; - "team_alien_barricade",//char *entityName; - { "models/buildables/barricade/barricade.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -35, -35, -15 }, //vec3_t mins; - { 35, 35, 60 }, //vec3_t maxs; - 0.0f, //float zOffset; + BA_A_OVERMIND, //int number; + "overmind", //char *name; + "Overmind", //char *humanName; + "A collective consciousness that controls all the alien structures " + "in its vicinity. It must be protected at all costs, since its " + "death will render alien structures defenseless.", + "team_alien_overmind", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; - BARRICADE_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - BARRICADE_HEALTH, //int health; - BARRICADE_REGEN, //int regenRate; - BARRICADE_SPLASHDAMAGE,//int splashDamage; - BARRICADE_SPLASHRADIUS,//int splashRadius; + OVERMIND_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + OVERMIND_HEALTH, //int health; + OVERMIND_REGEN, //int regenRate; + OVERMIND_SPLASHDAMAGE, //int splashDamage; + OVERMIND_SPLASHRADIUS, //int splashRadius; MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; - 100, //int nextthink; - BARRICADE_BT, //int buildTime; + OVERMIND_ATTACK_REPEAT, //int nextthink; + OVERMIND_BT, //int buildTime; qfalse, //qboolean usable; 0, //int turretRange; 0, //int turretFireSpeed; WP_NONE, //weapon_t turretProjType; - 0.707f, //float minNormal; + 0.95f, //float minNormal; qfalse, //qboolean invertNormal; - qtrue, //qboolean creepTest; - BARRICADE_CREEPSIZE, //int creepSize; + qfalse, //qboolean creepTest; + OVERMIND_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replaceable; + qtrue, //qboolean uniqueTest; + OVERMIND_VALUE, //int value; }, { - BA_A_BOOSTER, //int buildNum; - "booster", //char *buildName; - "Booster", //char *humanName; - "team_alien_booster", //char *entityName; - { "models/buildables/booster/booster.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -26, -26, -9 }, //vec3_t mins; - { 26, 26, 9 }, //vec3_t maxs; - 0.0f, //float zOffset; + BA_A_BARRICADE, //int number; + "barricade", //char *name; + "Barricade", //char *humanName; + "Used to obstruct corridors and doorways, hindering humans from " + "threatening the spawns and Overmind. Barricades will shrink " + "to allow aliens to pass over them, however.", + "team_alien_barricade", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; - BOOSTER_BP, //int buildPoints; - ( 1 << S2 )|( 1 << S3 ), //int stages - BOOSTER_HEALTH, //int health; - BOOSTER_REGEN, //int regenRate; - BOOSTER_SPLASHDAMAGE, //int splashDamage; - BOOSTER_SPLASHRADIUS, //int splashRadius; + BARRICADE_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + BARRICADE_HEALTH, //int health; + BARRICADE_REGEN, //int regenRate; + BARRICADE_SPLASHDAMAGE, //int splashDamage; + BARRICADE_SPLASHRADIUS, //int splashRadius; MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; - BOOSTER_BT, //int buildTime; + BARRICADE_BT, //int buildTime; qfalse, //qboolean usable; 0, //int turretRange; 0, //int turretFireSpeed; @@ -139,33 +135,31 @@ buildableAttributes_t bg_buildableList[ ] = 0.707f, //float minNormal; qfalse, //qboolean invertNormal; qtrue, //qboolean creepTest; - BOOSTER_CREEPSIZE, //int creepSize; + BARRICADE_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qtrue, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qtrue, //qboolean replacable; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + BARRICADE_VALUE, //int value; }, { - BA_A_ACIDTUBE, //int buildNum; - "acid_tube", //char *buildName; + BA_A_ACIDTUBE, //int number; + "acid_tube", //char *name; "Acid Tube", //char *humanName; - "team_alien_acid_tube",//char *entityName; - { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -25, -25, -25 }, //vec3_t mins; - { 25, 25, 25 }, //vec3_t maxs; - -15.0f, //float zOffset; + "Ejects lethal poisonous acid at an approaching human. These " + "are highly effective when used in conjunction with a trapper " + "to hold the victim in place.", + "team_alien_acid_tube", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; ACIDTUBE_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; ACIDTUBE_HEALTH, //int health; ACIDTUBE_REGEN, //int regenRate; ACIDTUBE_SPLASHDAMAGE, //int splashDamage; ACIDTUBE_SPLASHRADIUS, //int splashRadius; - MOD_ATUBE, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 200, //int nextthink; ACIDTUBE_BT, //int buildTime; @@ -179,67 +173,28 @@ buildableAttributes_t bg_buildableList[ ] = ACIDTUBE_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; - }, - { - BA_A_HIVE, //int buildNum; - "hive", //char *buildName; - "Hive", //char *humanName; - "team_alien_hive", //char *entityName; - { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -35, -35, -25 }, //vec3_t mins; - { 35, 35, 25 }, //vec3_t maxs; - -15.0f, //float zOffset; - TR_GRAVITY, //trType_t traj; - 0.0, //float bounce; - HIVE_BP, //int buildPoints; - ( 1 << S3 ), //int stages - HIVE_HEALTH, //int health; - HIVE_REGEN, //int regenRate; - HIVE_SPLASHDAMAGE, //int splashDamage; - HIVE_SPLASHRADIUS, //int splashRadius; - MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; - BANIM_IDLE1, //int idleAnim; - 500, //int nextthink; - HIVE_BT, //int buildTime; - qfalse, //qboolean usable; - 0, //int turretRange; - 0, //int turretFireSpeed; - WP_HIVE, //weapon_t turretProjType; - 0.0f, //float minNormal; - qtrue, //qboolean invertNormal; - qtrue, //qboolean creepTest; - HIVE_CREEPSIZE, //int creepSize; - qfalse, //qboolean dccTest; - qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; + qfalse, //qboolean uniqueTest; + ACIDTUBE_VALUE, //int value; }, { - BA_A_TRAPPER, //int buildNum; - "trapper", //char *buildName; + BA_A_TRAPPER, //int number; + "trapper", //char *name; "Trapper", //char *humanName; + "Fires a blob of adhesive spit at any non-alien in its line of " + "sight. This hinders their movement, making them an easy target " + "for other defensive structures or aliens.", "team_alien_trapper", //char *entityName; - { "models/buildables/trapper/trapper.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -15, -15, -15 }, //vec3_t mins; - { 15, 15, 15 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; TRAPPER_BP, //int buildPoints; - ( 1 << S2 )|( 1 << S3 ), //int stages //NEEDS ADV BUILDER SO S2 AND UP + ( 1 << S2 )|( 1 << S3 ), //int stages; //NEEDS ADV BUILDER SO S2 AND UP TRAPPER_HEALTH, //int health; TRAPPER_REGEN, //int regenRate; TRAPPER_SPLASHDAMAGE, //int splashDamage; TRAPPER_SPLASHRADIUS, //int splashRadius; MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; TRAPPER_BT, //int buildTime; @@ -253,104 +208,98 @@ buildableAttributes_t bg_buildableList[ ] = TRAPPER_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; qtrue, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; + qfalse, //qboolean uniqueTest; + TRAPPER_VALUE, //int value; }, { - BA_A_OVERMIND, //int buildNum; - "overmind", //char *buildName; - "Overmind", //char *humanName; - "team_alien_overmind", //char *entityName; - { "models/buildables/overmind/overmind.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -45, -45, -15 }, //vec3_t mins; - { 45, 45, 95 }, //vec3_t maxs; - 0.0f, //float zOffset; + BA_A_BOOSTER, //int number; + "booster", //char *name; + "Booster", //char *humanName; + "Laces the attacks of any alien that touches it with a poison " + "that will gradually deal damage to any humans exposed to it. " + "The booster also increases the rate of health regeneration for " + "any nearby aliens.", + "team_alien_booster", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; - OVERMIND_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - OVERMIND_HEALTH, //int health; - OVERMIND_REGEN, //int regenRate; - OVERMIND_SPLASHDAMAGE, //int splashDamage; - OVERMIND_SPLASHRADIUS, //int splashRadius; + BOOSTER_BP, //int buildPoints; + ( 1 << S2 )|( 1 << S3 ), //int stages; + BOOSTER_HEALTH, //int health; + BOOSTER_REGEN, //int regenRate; + BOOSTER_SPLASHDAMAGE, //int splashDamage; + BOOSTER_SPLASHRADIUS, //int splashRadius; MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; - OVERMIND_ATTACK_REPEAT,//int nextthink; - OVERMIND_BT, //int buildTime; + 100, //int nextthink; + BOOSTER_BT, //int buildTime; qfalse, //qboolean usable; 0, //int turretRange; 0, //int turretFireSpeed; WP_NONE, //weapon_t turretProjType; - 0.95f, //float minNormal; + 0.707f, //float minNormal; qfalse, //qboolean invertNormal; - qfalse, //qboolean creepTest; - OVERMIND_CREEPSIZE, //int creepSize; + qtrue, //qboolean creepTest; + BOOSTER_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse, //qboolean transparentTest; - qtrue, //qboolean reactorTest; - qtrue, //qboolean replacable; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + BOOSTER_VALUE, //int value; }, { - BA_A_HOVEL, //int buildNum; - "hovel", //char *buildName; - "Hovel", //char *humanName; - "team_alien_hovel", //char *entityName; - { "models/buildables/hovel/hovel.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -50, -50, -20 }, //vec3_t mins; - { 50, 50, 20 }, //vec3_t maxs; - 0.0f, //float zOffset; + BA_A_HIVE, //int number; + "hive", //char *name; + "Hive", //char *humanName; + "Houses millions of tiny insectoid aliens. When a human " + "approaches this structure, the insectoids attack.", + "team_alien_hive", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; - HOVEL_BP, //int buildPoints; - ( 1 << S3 ), //int stages - HOVEL_HEALTH, //int health; - HOVEL_REGEN, //int regenRate; - HOVEL_SPLASHDAMAGE, //int splashDamage; - HOVEL_SPLASHRADIUS, //int splashRadius; + HIVE_BP, //int buildPoints; + ( 1 << S3 ), //int stages; + HIVE_HEALTH, //int health; + HIVE_REGEN, //int regenRate; + HIVE_SPLASHDAMAGE, //int splashDamage; + HIVE_SPLASHRADIUS, //int splashRadius; MOD_ASPAWN, //int meansOfDeath; - BIT_ALIENS, //int team; - ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; - 150, //int nextthink; - HOVEL_BT, //int buildTime; - qtrue, //qboolean usable; + 500, //int nextthink; + HIVE_BT, //int buildTime; + qfalse, //qboolean usable; 0, //int turretRange; 0, //int turretFireSpeed; - WP_NONE, //weapon_t turretProjType; - 0.95f, //float minNormal; - qfalse, //qboolean invertNormal; + WP_HIVE, //weapon_t turretProjType; + 0.0f, //float minNormal; + qtrue, //qboolean invertNormal; qtrue, //qboolean creepTest; - HOVEL_CREEPSIZE, //int creepSize; + HIVE_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qtrue, //qboolean reactorTest; - qfalse, //qboolean replacable; + qfalse, //qboolean uniqueTest; + HIVE_VALUE, //int value; }, { - BA_H_SPAWN, //int buildNum; - "telenode", //char *buildName; + BA_H_SPAWN, //int number; + "telenode", //char *name; "Telenode", //char *humanName; + "The most basic human structure. It provides a means for humans " + "to enter the battle arena. Without any of these the humans " + "cannot spawn and defeat is imminent.", "team_human_spawn", //char *entityName; - { "models/buildables/telenode/telenode.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -40, -40, -4 }, //vec3_t mins; - { 40, 40, 4 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; HSPAWN_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; HSPAWN_HEALTH, //int health; 0, //int regenRate; HSPAWN_SPLASHDAMAGE, //int splashDamage; HSPAWN_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; HSPAWN_BT, //int buildTime; @@ -364,69 +313,28 @@ buildableAttributes_t bg_buildableList[ ] = 0, //int creepSize; qfalse, //qboolean dccTest; qtrue, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; - }, - { - BA_H_MEDISTAT, //int buildNum; - "medistat", //char *buildName; - "Medistation", //char *humanName; - "team_human_medistat", //char *entityName; - { "models/buildables/medistat/medistat.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -35, -35, -7 }, //vec3_t mins; - { 35, 35, 7 }, //vec3_t maxs; - 0.0f, //float zOffset; - TR_GRAVITY, //trType_t traj; - 0.0, //float bounce; - MEDISTAT_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - MEDISTAT_HEALTH, //int health; - 0, //int regenRate; - MEDISTAT_SPLASHDAMAGE, //int splashDamage; - MEDISTAT_SPLASHRADIUS, //int splashRadius; - MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; - BANIM_IDLE1, //int idleAnim; - 100, //int nextthink; - MEDISTAT_BT, //int buildTime; - qfalse, //qboolean usable; - 0, //int turretRange; - 0, //int turretFireSpeed; - WP_NONE, //weapon_t turretProjType; - 0.95f, //float minNormal; - qfalse, //qboolean invertNormal; - qfalse, //qboolean creepTest; - 0, //int creepSize; - qfalse, //qboolean dccTest; - qtrue, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qtrue, //qboolean replacable; + qfalse, //qboolean uniqueTest; + HSPAWN_VALUE, //int value; }, { - BA_H_MGTURRET, //int buildNum; - "mgturret", //char *buildName; + BA_H_MGTURRET, //int number; + "mgturret", //char *name; "Machinegun Turret", //char *humanName; + "Automated base defense that is effective against large targets " + "but slow to begin firing. Should always be " + "backed up by physical support.", "team_human_mgturret", //char *entityName; - { "models/buildables/mgturret/turret_base.md3", - "models/buildables/mgturret/turret_barrel.md3", - "models/buildables/mgturret/turret_top.md3", 0 }, - 1.0f, //float modelScale; - { -25, -25, -20 }, //vec3_t mins; - { 25, 25, 20 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; MGTURRET_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; MGTURRET_HEALTH, //int health; 0, //int regenRate; MGTURRET_SPLASHDAMAGE, //int splashDamage; MGTURRET_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 50, //int nextthink; MGTURRET_BT, //int buildTime; @@ -440,30 +348,28 @@ buildableAttributes_t bg_buildableList[ ] = 0, //int creepSize; qfalse, //qboolean dccTest; qtrue, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; + qfalse, //qboolean uniqueTest; + MGTURRET_VALUE, //int value; }, { - BA_H_TESLAGEN, //int buildNum; - "tesla", //char *buildName; + BA_H_TESLAGEN, //int number; + "tesla", //char *name; "Tesla Generator", //char *humanName; + "A structure equipped with a strong electrical attack that fires " + "instantly and always hits its target. It is effective against smaller " + "aliens and for consolidating basic defense.", "team_human_tesla", //char *entityName; - { "models/buildables/tesla/tesla.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -22, -22, -40 }, //vec3_t mins; - { 22, 22, 40 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; TESLAGEN_BP, //int buildPoints; - ( 1 << S3 ), //int stages + ( 1 << S3 ), //int stages; TESLAGEN_HEALTH, //int health; 0, //int regenRate; TESLAGEN_SPLASHDAMAGE, //int splashDamage; TESLAGEN_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 150, //int nextthink; TESLAGEN_BT, //int buildTime; @@ -475,32 +381,65 @@ buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean invertNormal; qfalse, //qboolean creepTest; 0, //int creepSize; - qtrue, //qboolean dccTest; + qfalse, //qboolean dccTest; qtrue, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qfalse, //qboolean replacable; + qfalse, //qboolean uniqueTest; + TESLAGEN_VALUE, //int value; + }, + { + BA_H_ARMOURY, //int number; + "arm", //char *name; + "Armoury", //char *humanName; + "An essential part of the human base, providing a means " + "to upgrade the basic human equipment. A range of upgrades " + "and weapons are available for sale from the armoury.", + "team_human_armoury", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + ARMOURY_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + ARMOURY_HEALTH, //int health; + 0, //int regenRate; + ARMOURY_SPLASHDAMAGE, //int splashDamage; + ARMOURY_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + ARMOURY_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + ARMOURY_VALUE, //int value; }, { - BA_H_DCC, //int buildNum; - "dcc", //char *buildName; + BA_H_DCC, //int number; + "dcc", //char *name; "Defence Computer", //char *humanName; + "A structure that enables self-repair functionality in " + "human structures. Each Defence Computer built increases " + "repair rate slightly.", "team_human_dcc", //char *entityName; - { "models/buildables/dcc/dcc.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -35, -35, -13 }, //vec3_t mins; - { 35, 35, 47 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; DC_BP, //int buildPoints; - ( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S2 )|( 1 << S3 ), //int stages; DC_HEALTH, //int health; 0, //int regenRate; DC_SPLASHDAMAGE, //int splashDamage; DC_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; DC_BT, //int buildTime; @@ -514,34 +453,33 @@ buildableAttributes_t bg_buildableList[ ] = 0, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qtrue, //qboolean replacable; + qfalse, //qboolean uniqueTest; + DC_VALUE, //int value; }, { - BA_H_ARMOURY, //int buildNum; - "arm", //char *buildName; - "Armoury", //char *humanName; - "team_human_armoury", //char *entityName; - { "models/buildables/arm/arm.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -40, -40, -13 }, //vec3_t mins; - { 40, 40, 50 }, //vec3_t maxs; - 0.0f, //float zOffset; + BA_H_MEDISTAT, //int number; + "medistat", //char *name; + "Medistation", //char *humanName; + "A structure that automatically restores " + "the health and stamina of any human that stands on it. " + "It may only be used by one person at a time. This structure " + "also issues medkits.", + "team_human_medistat", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; - ARMOURY_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - ARMOURY_HEALTH, //int health; + MEDISTAT_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + MEDISTAT_HEALTH, //int health; 0, //int regenRate; - ARMOURY_SPLASHDAMAGE, //int splashDamage; - ARMOURY_SPLASHRADIUS, //int splashRadius; + MEDISTAT_SPLASHDAMAGE, //int splashDamage; + MEDISTAT_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; - ARMOURY_BT, //int buildTime; - qtrue, //qboolean usable; + MEDISTAT_BT, //int buildTime; + qfalse, //qboolean usable; 0, //int turretRange; 0, //int turretFireSpeed; WP_NONE, //weapon_t turretProjType; @@ -550,33 +488,31 @@ buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qtrue, //qboolean replacable; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + MEDISTAT_VALUE, //int value; }, { - BA_H_REACTOR, //int buildNum; - "reactor", //char *buildName; + BA_H_REACTOR, //int number; + "reactor", //char *name; "Reactor", //char *humanName; + "All structures except the telenode rely on a reactor to operate. " + "The reactor provides power for all the human structures either " + "directly or via repeaters. Only one reactor can be built at a time.", "team_human_reactor", //char *entityName; - { "models/buildables/reactor/reactor.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -50, -50, -15 }, //vec3_t mins; - { 50, 50, 95 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; REACTOR_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; REACTOR_HEALTH, //int health; 0, //int regenRate; REACTOR_SPLASHDAMAGE, //int splashDamage; REACTOR_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; - REACTOR_ATTACK_REPEAT, //int nextthink; + REACTOR_ATTACK_DCC_REPEAT, //int nextthink; REACTOR_BT, //int buildTime; qtrue, //qboolean usable; 0, //int turretRange; @@ -588,30 +524,28 @@ buildableAttributes_t bg_buildableList[ ] = 0, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qtrue, //qboolean reactorTest; - qtrue, //qboolean replacable; + qtrue, //qboolean uniqueTest; + REACTOR_VALUE, //int value; }, { - BA_H_REPEATER, //int buildNum; - "repeater", //char *buildName; + BA_H_REPEATER, //int number; + "repeater", //char *name; "Repeater", //char *humanName; + "A power distributor that transmits power from the reactor " + "to remote locations, so that bases may be built far " + "from the reactor.", "team_human_repeater", //char *entityName; - { "models/buildables/repeater/repeater.md3", 0, 0, 0 }, - 1.0f, //float modelScale; - { -15, -15, -15 }, //vec3_t mins; - { 15, 15, 25 }, //vec3_t maxs; - 0.0f, //float zOffset; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; REPEATER_BP, //int buildPoints; - ( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; REPEATER_HEALTH, //int health; 0, //int regenRate; REPEATER_SPLASHDAMAGE, //int splashDamage; REPEATER_SPLASHRADIUS, //int splashRadius; MOD_HSPAWN, //int meansOfDeath; - BIT_HUMANS, //int team; - ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; BANIM_IDLE1, //int idleAnim; 100, //int nextthink; REPEATER_BT, //int buildTime; @@ -625,757 +559,854 @@ buildableAttributes_t bg_buildableList[ ] = 0, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qfalse, //qboolean reactorTest; - qtrue, //qboolean replacable; + qfalse, //qboolean uniqueTest; + REPEATER_VALUE, //int value; } }; -int bg_numBuildables = sizeof( bg_buildableList ) / sizeof( bg_buildableList[ 0 ] ); +size_t bg_numBuildables = ARRAY_LEN( bg_buildableList ); -//separate from bg_buildableList to work around char struct init bug -buildableAttributeOverrides_t bg_buildableOverrideList[ BA_NUM_BUILDABLES ]; +static const buildableAttributes_t nullBuildable = { 0 }; /* ============== -BG_FindBuildNumForName +BG_BuildableByName ============== */ -int BG_FindBuildNumForName( char *name ) +const buildableAttributes_t *BG_BuildableByName( const char *name ) { int i; for( i = 0; i < bg_numBuildables; i++ ) { - if( !Q_stricmp( bg_buildableList[ i ].buildName, name ) ) - return bg_buildableList[ i ].buildNum; + if( !Q_stricmp( bg_buildableList[ i ].name, name ) ) + return &bg_buildableList[ i ]; } - //wimp out - return BA_NONE; + return &nullBuildable; } /* ============== -BG_FindBuildNumForEntityName +BG_BuildableByEntityName ============== */ -int BG_FindBuildNumForEntityName( char *name ) +const buildableAttributes_t *BG_BuildableByEntityName( const char *name ) { int i; for( i = 0; i < bg_numBuildables; i++ ) { if( !Q_stricmp( bg_buildableList[ i ].entityName, name ) ) - return bg_buildableList[ i ].buildNum; + return &bg_buildableList[ i ]; } - //wimp out - return BA_NONE; + return &nullBuildable; } /* ============== -BG_FindNameForBuildNum +BG_Buildable ============== */ -char *BG_FindNameForBuildable( int bclass ) +const buildableAttributes_t *BG_Buildable( buildable_t buildable ) { - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - return bg_buildableList[ i ].buildName; - } - - //wimp out - return 0; + return ( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES ) ? + &bg_buildableList[ buildable - 1 ] : &nullBuildable; } /* ============== -BG_FindHumanNameForBuildNum +BG_BuildableAllowedInStage ============== */ -char *BG_FindHumanNameForBuildable( int bclass ) +qboolean BG_BuildableAllowedInStage( buildable_t buildable, + stage_t stage ) { - int i; + int stages = BG_Buildable( buildable )->stages; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - return bg_buildableList[ i ].humanName; - } - - //wimp out - return 0; + if( stages & ( 1 << stage ) ) + return qtrue; + else + return qfalse; } +static buildableConfig_t bg_buildableConfigList[ BA_NUM_BUILDABLES ]; + /* ============== -BG_FindEntityNameForBuildNum +BG_BuildableConfig ============== */ -char *BG_FindEntityNameForBuildable( int bclass ) +buildableConfig_t *BG_BuildableConfig( buildable_t buildable ) { - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - return bg_buildableList[ i ].entityName; - } - - //wimp out - return 0; + return &bg_buildableConfigList[ buildable ]; } /* ============== -BG_FindModelsForBuildNum +BG_BuildableBoundingBox ============== */ -char *BG_FindModelsForBuildable( int bclass, int modelNum ) +void BG_BuildableBoundingBox( buildable_t buildable, + vec3_t mins, vec3_t maxs ) { - int i; + buildableConfig_t *buildableConfig = BG_BuildableConfig( buildable ); - if( bg_buildableOverrideList[ bclass ].models[ modelNum ][ 0 ] != 0 ) - return bg_buildableOverrideList[ bclass ].models[ modelNum ]; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - return bg_buildableList[ i ].models[ modelNum ]; - } + if( mins != NULL ) + VectorCopy( buildableConfig->mins, mins ); - //wimp out - return 0; + if( maxs != NULL ) + VectorCopy( buildableConfig->maxs, maxs ); } /* -============== -BG_FindModelScaleForBuildable -============== +====================== +BG_ParseBuildableFile + +Parses a configuration file describing a buildable +====================== */ -float BG_FindModelScaleForBuildable( int bclass ) +static qboolean BG_ParseBuildableFile( const char *filename, buildableConfig_t *bc ) { - int i; + char *text_p; + int i; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + float scale; + int defined = 0; + enum + { + MODEL = 1 << 0, + MODELSCALE = 1 << 1, + MINS = 1 << 2, + MAXS = 1 << 3, + ZOFFSET = 1 << 4 + }; - if( bg_buildableOverrideList[ bclass ].modelScale != 0.0f ) - return bg_buildableOverrideList[ bclass ].modelScale; - for( i = 0; i < bg_numBuildables; i++ ) + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: Buildable file %s doesn't exist\n", filename ); + return qfalse; + } + + if( len == 0 || len >= sizeof( text ) - 1 ) { - if( bg_buildableList[ i ].buildNum == bclass ) - return bg_buildableList[ i ].modelScale; + trap_FS_FCloseFile( f ); + Com_Printf( S_COLOR_RED "ERROR: Buildable file %s is %s\n", filename, + len == 0 ? "empty" : "too long" ); + return qfalse; } - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForBuildable( %d )\n", bclass ); - return 1.0f; -} + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); -/* -============== -BG_FindBBoxForBuildable -============== -*/ -void BG_FindBBoxForBuildable( int bclass, vec3_t mins, vec3_t maxs ) -{ - int i; + // parse the text + text_p = text; - for( i = 0; i < bg_numBuildables; i++ ) + // read optional parameters + while( 1 ) { - if( bg_buildableList[ i ].buildNum == bclass ) - { - if( mins != NULL ) - { - VectorCopy( bg_buildableList[ i ].mins, mins ); + token = COM_Parse( &text_p ); - if( VectorLength( bg_buildableOverrideList[ bclass ].mins ) ) - VectorCopy( bg_buildableOverrideList[ bclass ].mins, mins ); - } + if( !token ) + break; - if( maxs != NULL ) - { - VectorCopy( bg_buildableList[ i ].maxs, maxs ); + if( !Q_stricmp( token, "" ) ) + break; - if( VectorLength( bg_buildableOverrideList[ bclass ].maxs ) ) - VectorCopy( bg_buildableOverrideList[ bclass ].maxs, maxs ); - } + if( !Q_stricmp( token, "model" ) ) + { + int index = 0; - return; - } - } + token = COM_Parse( &text_p ); + if( !token ) + break; - if( mins != NULL ) - VectorCopy( bg_buildableList[ 0 ].mins, mins ); + index = atoi( token ); - if( maxs != NULL ) - VectorCopy( bg_buildableList[ 0 ].maxs, maxs ); -} + if( index < 0 ) + index = 0; + else if( index > 3 ) + index = 3; -/* -============== -BG_FindZOffsetForBuildable -============== -*/ -float BG_FindZOffsetForBuildable( int bclass ) -{ - int i; + token = COM_Parse( &text_p ); + if( !token ) + break; - if( bg_buildableOverrideList[ bclass ].zOffset != 0.0f ) - return bg_buildableOverrideList[ bclass ].zOffset; + Q_strncpyz( bc->models[ index ], token, sizeof( bc->models[ 0 ] ) ); - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].zOffset; + defined |= MODEL; + continue; } - } - - return 0.0f; -} - -/* -============== -BG_FindTrajectoryForBuildable -============== -*/ -trType_t BG_FindTrajectoryForBuildable( int bclass ) -{ - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) + else if( !Q_stricmp( token, "modelScale" ) ) { - return bg_buildableList[ i ].traj; - } - } - - return TR_GRAVITY; -} - -/* -============== -BG_FindBounceForBuildable -============== -*/ -float BG_FindBounceForBuildable( int bclass ) -{ - int i; + token = COM_Parse( &text_p ); + if( !token ) + break; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].bounce; - } - } + scale = atof( token ); - return 0.0; -} + if( scale < 0.0f ) + scale = 0.0f; -/* -============== -BG_FindBuildPointsForBuildable -============== -*/ -int BG_FindBuildPointsForBuildable( int bclass ) -{ - int i; + bc->modelScale = scale; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].buildPoints; + defined |= MODELSCALE; + continue; } - } - - return 1000; -} - -/* -============== -BG_FindStagesForBuildable -============== -*/ -qboolean BG_FindStagesForBuildable( int bclass, stage_t stage ) -{ - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) + else if( !Q_stricmp( token, "mins" ) ) { - if( bg_buildableList[ i ].stages & ( 1 << stage ) ) - return qtrue; - else - return qfalse; - } - } - - return qfalse; -} + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; -/* -============== -BG_FindHealthForBuildable -============== -*/ -int BG_FindHealthForBuildable( int bclass ) -{ - int i; + bc->mins[ i ] = atof( token ); + } - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].health; + defined |= MINS; + continue; } - } - - return 1000; -} - -/* -============== -BG_FindRegenRateForBuildable -============== -*/ -int BG_FindRegenRateForBuildable( int bclass ) -{ - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) + else if( !Q_stricmp( token, "maxs" ) ) { - return bg_buildableList[ i ].regenRate; - } - } - - return 0; -} + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; -/* -============== -BG_FindSplashDamageForBuildable -============== -*/ -int BG_FindSplashDamageForBuildable( int bclass ) -{ - int i; + bc->maxs[ i ] = atof( token ); + } - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].splashDamage; + defined |= MAXS; + continue; } - } - - return 50; -} - -/* -============== -BG_FindSplashRadiusForBuildable -============== -*/ -int BG_FindSplashRadiusForBuildable( int bclass ) -{ - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) + else if( !Q_stricmp( token, "zOffset" ) ) { - return bg_buildableList[ i ].splashRadius; - } - } - - return 200; -} - -/* -============== -BG_FindMODForBuildable -============== -*/ -int BG_FindMODForBuildable( int bclass ) -{ - int i; + float offset; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].meansOfDeath; - } - } + token = COM_Parse( &text_p ); + if( !token ) + break; - return MOD_UNKNOWN; -} + offset = atof( token ); -/* -============== -BG_FindTeamForBuildable -============== -*/ -int BG_FindTeamForBuildable( int bclass ) -{ - int i; + bc->zOffset = offset; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].team; + defined |= ZOFFSET; + continue; } - } - - return BIT_NONE; -} -/* -============== -BG_FindBuildWeaponForBuildable -============== -*/ -weapon_t BG_FindBuildWeaponForBuildable( int bclass ) -{ - int i; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].buildWeapon; - } + Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); + return qfalse; } - return WP_NONE; -} - -/* -============== -BG_FindAnimForBuildable -============== -*/ -int BG_FindAnimForBuildable( int bclass ) -{ - int i; + if( !( defined & MODEL ) ) token = "model"; + else if( !( defined & MODELSCALE ) ) token = "modelScale"; + else if( !( defined & MINS ) ) token = "mins"; + else if( !( defined & MAXS ) ) token = "maxs"; + else if( !( defined & ZOFFSET ) ) token = "zOffset"; + else token = ""; - for( i = 0; i < bg_numBuildables; i++ ) + if( strlen( token ) > 0 ) { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].idleAnim; - } + Com_Printf( S_COLOR_RED "ERROR: %s not defined in %s\n", + token, filename ); + return qfalse; } - return BANIM_IDLE1; + return qtrue; } /* -============== -BG_FindNextThinkForBuildable -============== +=============== +BG_InitBuildableConfigs +=============== */ -int BG_FindNextThinkForBuildable( int bclass ) +void BG_InitBuildableConfigs( void ) { - int i; + int i; + buildableConfig_t *bc; - for( i = 0; i < bg_numBuildables; i++ ) + for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].nextthink; - } - } - - return 100; -} - -/* -============== -BG_FindBuildTimeForBuildable -============== -*/ -int BG_FindBuildTimeForBuildable( int bclass ) -{ - int i; + bc = BG_BuildableConfig( i ); + Com_Memset( bc, 0, sizeof( buildableConfig_t ) ); - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].buildTime; - } + BG_ParseBuildableFile( va( "configs/buildables/%s.cfg", + BG_Buildable( i )->name ), bc ); } - - return 10000; } -/* -============== -BG_FindUsableForBuildable -============== -*/ -qboolean BG_FindUsableForBuildable( int bclass ) -{ - int i; +//////////////////////////////////////////////////////////////////////////////// - for( i = 0; i < bg_numBuildables; i++ ) +static const classAttributes_t bg_classList[ ] = +{ { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].usable; - } + PCL_NONE, //int number; + "spectator", //char *name; + "", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + 0, //int health; + 0.0f, //float fallDamage; + 0.0f, //float regenRate; + 0, //int abilities; + WP_NONE, //weapon_t startWeapon; + 0.0f, //float buildDist; + 90, //int fov; + 0.000f, //float bob; + 1.0f, //float bobCycle; + 0.0f, //float landBob; + 0, //int steptime; + 600, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + 0 //int value; + }, + { + PCL_ALIEN_BUILDER0, //int number; + "builder", //char *name; + "Responsible for building and maintaining all the alien structures. " + "Has a weak melee slash attack.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + ABUILDER_HEALTH, //int health; + 0.2f, //float fallDamage; + ABUILDER_REGEN, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ABUILD, //weapon_t startWeapon; + 95.0f, //float buildDist; + 110, //int fov; + 0.001f, //float bob; + 2.0f, //float bobCycle; + 4.5f, //float landBob; + 150, //int steptime; + ABUILDER_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 195.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_ALIEN_BUILDER0_UPG, PCL_ALIEN_LEVEL0, PCL_NONE }, //int children[ 3 ]; + ABUILDER_COST, //int cost; + ABUILDER_VALUE //int value; + }, + { + PCL_ALIEN_BUILDER0_UPG, //int number; + "builderupg", //char *name; + "Similar to the base Granger, except that in addition to " + "being able to build structures it has a spit attack " + "that slows victims and the ability to crawl on walls.", + ( 1 << S2 )|( 1 << S3 ), //int stages; + ABUILDER_UPG_HEALTH, //int health; + 0.2f, //float fallDamage; + ABUILDER_UPG_REGEN, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; + WP_ABUILD2, //weapon_t startWeapon; + 105.0f, //float buildDist; + 110, //int fov; + 0.001f, //float bob; + 2.0f, //float bobCycle; + 4.5f, //float landBob; + 100, //int steptime; + ABUILDER_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_ALIEN_LEVEL0, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + ABUILDER_UPG_COST, //int cost; + ABUILDER_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL0, //int number; + "level0", //char *name; + "Has a lethal reflexive bite and the ability to crawl on " + "walls and ceilings.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL0_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL0_REGEN, //float regenRate; + SCA_WALLCLIMBER|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL0, //weapon_t startWeapon; + 0.0f, //float buildDist; + 140, //int fov; + 0.0f, //float bob; + 2.5f, //float bobCycle; + 0.0f, //float landBob; + 25, //int steptime; + LEVEL0_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 400.0f, //float stopSpeed; + 250.0f, //float jumpMagnitude; + 2.0f, //float knockbackScale; + { PCL_ALIEN_LEVEL1, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL0_COST, //int cost; + LEVEL0_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL1, //int number; + "level1", //char *name; + "A support class able to crawl on walls and ceilings. Its melee " + "attack is most effective when combined with the ability to grab " + "and hold its victims in place. Provides a weak healing aura " + "that accelerates the healing rate of nearby aliens.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL1_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL1_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL1, //weapon_t startWeapon; + 0.0f, //float buildDist; + 120, //int fov; + 0.001f, //float bob; + 1.8f, //float bobCycle; + 0.0f, //float landBob; + 60, //int steptime; + LEVEL1_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 300.0f, //float stopSpeed; + 310.0f, //float jumpMagnitude; + 1.2f, //float knockbackScale; + { PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE }, //int children[ 3 ]; + LEVEL1_COST, //int cost; + LEVEL1_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL1_UPG, //int number; + "level1upg", //char *name; + "In addition to the basic Basilisk abilities, the Advanced " + "Basilisk sprays a poisonous gas which disorients any " + "nearby humans. Has a strong healing aura that " + "that accelerates the healing rate of nearby aliens.", + ( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL1_UPG_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL1_UPG_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL1_UPG, //weapon_t startWeapon; + 0.0f, //float buildDist; + 120, //int fov; + 0.001f, //float bob; + 1.8f, //float bobCycle; + 0.0f, //float landBob; + 60, //int steptime; + LEVEL1_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 300.0f, //float stopSpeed; + 310.0f, //float jumpMagnitude; + 1.1f, //float knockbackScale; + { PCL_ALIEN_LEVEL2, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL1_UPG_COST, //int cost; + LEVEL1_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL2, //int number; + "level2", //char *name; + "Has a melee attack and the ability to jump off walls. This " + "allows the Marauder to gather great speed in enclosed areas.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL2_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL2_REGEN, //float regenRate; + SCA_WALLJUMPER|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL2, //weapon_t startWeapon; + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.5f, //float bobCycle; + 0.0f, //float landBob; + 80, //int steptime; + LEVEL2_SPEED, //float speed; + 10.0f, //float acceleration; + 3.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 380.0f, //float jumpMagnitude; + 0.8f, //float knockbackScale; + { PCL_ALIEN_LEVEL3, PCL_ALIEN_LEVEL2_UPG, PCL_NONE }, //int children[ 3 ]; + LEVEL2_COST, //int cost; + LEVEL2_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL2_UPG, //int number; + "level2upg", //char *name; + "The Advanced Marauder has all the abilities of the basic Marauder " + "with the addition of an area effect electric shock attack.", + ( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL2_UPG_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL2_UPG_REGEN, //float regenRate; + SCA_WALLJUMPER|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL2_UPG, //weapon_t startWeapon; + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.5f, //float bobCycle; + 0.0f, //float landBob; + 80, //int steptime; + LEVEL2_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 3.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 380.0f, //float jumpMagnitude; + 0.7f, //float knockbackScale; + { PCL_ALIEN_LEVEL3, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL2_UPG_COST, //int cost; + LEVEL2_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL3, //int number; + "level3", //char *name; + "Possesses a melee attack and the pounce ability, which may " + "be used as both an attack and a means to reach remote " + "locations inaccessible from the ground.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL3_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL3_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL3, //weapon_t startWeapon; + 0.0f, //float buildDist; + 110, //int fov; + 0.0005f, //float bob; + 1.3f, //float bobCycle; + 0.0f, //float landBob; + 90, //int steptime; + LEVEL3_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 200.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 0.5f, //float knockbackScale; + { PCL_ALIEN_LEVEL4, PCL_ALIEN_LEVEL3_UPG, PCL_NONE }, //int children[ 3 ]; + LEVEL3_COST, //int cost; + LEVEL3_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL3_UPG, //int number; + "level3upg", //char *name; + "In addition to the basic Dragoon abilities, the Advanced " + "Dragoon has 3 barbs which may be used to attack humans " + "from a distance.", + ( 1 << S2 )|( 1 << S3 ), //int stages; + LEVEL3_UPG_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL3_UPG_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL3_UPG, //weapon_t startWeapon; + 0.0f, //float buildDist; + 110, //int fov; + 0.0005f, //float bob; + 1.3f, //float bobCycle; + 0.0f, //float landBob; + 90, //int steptime; + LEVEL3_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 200.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 0.4f, //float knockbackScale; + { PCL_ALIEN_LEVEL4, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL3_UPG_COST, //int cost; + LEVEL3_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL4, //int number; + "level4", //char *name; + "A large alien with a strong melee attack, this class can " + "also charge at enemy humans and structures, inflicting " + "great damage. Any humans or their structures caught under " + "a falling Tyrant will be crushed by its weight.", + ( 1 << S3 ), //int stages; + LEVEL4_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL4_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL4, //weapon_t startWeapon; + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.1f, //float bobCycle; + 0.0f, //float landBob; + 100, //int steptime; + LEVEL4_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 170.0f, //float jumpMagnitude; + 0.1f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL4_COST, //int cost; + LEVEL4_VALUE //int value; + }, + { + PCL_HUMAN, //int number; + "human_base", //char *name; + "", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + 100, //int health; + 1.0f, //float fallDamage; + 0.0f, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_CANUSELADDERS, //int abilities; + WP_NONE, //special-cased in g_client.c //weapon_t startWeapon; + 110.0f, //float buildDist; + 90, //int fov; + 0.002f, //float bob; + 1.0f, //float bobCycle; + 8.0f, //float landBob; + 100, //int steptime; + 1.0f, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 220.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + ALIEN_CREDITS_PER_KILL //int value; + }, + { + PCL_HUMAN_BSUIT, //int number; + "human_bsuit", //char *name; + "", + ( 1 << S3 ), //int stages; + 100, //int health; + 1.0f, //float fallDamage; + 0.0f, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_CANUSELADDERS, //int abilities; + WP_NONE, //special-cased in g_client.c //weapon_t startWeapon; + 110.0f, //float buildDist; + 90, //int fov; + 0.002f, //float bob; + 1.0f, //float bobCycle; + 5.0f, //float landBob; + 100, //int steptime; + 1.0f, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 220.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + ALIEN_CREDITS_PER_KILL //int value; } +}; - return qfalse; -} +size_t bg_numClasses = ARRAY_LEN( bg_classList ); + +static const classAttributes_t nullClass = { 0 }; /* ============== -BG_FindFireSpeedForBuildable +BG_ClassByName ============== */ -int BG_FindFireSpeedForBuildable( int bclass ) +const classAttributes_t *BG_ClassByName( const char *name ) { int i; - for( i = 0; i < bg_numBuildables; i++ ) + for( i = 0; i < bg_numClasses; i++ ) { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].turretFireSpeed; - } + if( !Q_stricmp( bg_classList[ i ].name, name ) ) + return &bg_classList[ i ]; } - return 1000; + return &nullClass; } /* ============== -BG_FindRangeForBuildable +BG_Class ============== */ -int BG_FindRangeForBuildable( int bclass ) +const classAttributes_t *BG_Class( class_t class ) { - int i; + return ( class >= PCL_NONE && class < PCL_NUM_CLASSES ) ? + &bg_classList[ class ] : &nullClass; +} - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].turretRange; - } - } +/* +============== +BG_ClassAllowedInStage +============== +*/ +qboolean BG_ClassAllowedInStage( class_t class, + stage_t stage ) +{ + int stages = BG_Class( class )->stages; - return 1000; + return stages & ( 1 << stage ); } +static classConfig_t bg_classConfigList[ PCL_NUM_CLASSES ]; + /* ============== -BG_FindProjTypeForBuildable +BG_ClassConfig ============== */ -weapon_t BG_FindProjTypeForBuildable( int bclass ) +classConfig_t *BG_ClassConfig( class_t class ) { - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].turretProjType; - } - } - - return WP_NONE; + return &bg_classConfigList[ class ]; } /* ============== -BG_FindMinNormalForBuildable +BG_ClassBoundingBox ============== */ -float BG_FindMinNormalForBuildable( int bclass ) +void BG_ClassBoundingBox( class_t class, + vec3_t mins, vec3_t maxs, + vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs ) { - int i; + classConfig_t *classConfig = BG_ClassConfig( class ); - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].minNormal; - } - } + if( mins != NULL ) + VectorCopy( classConfig->mins, mins ); - return 0.707f; -} + if( maxs != NULL ) + VectorCopy( classConfig->maxs, maxs ); -/* -============== -BG_FindInvertNormalForBuildable -============== -*/ -qboolean BG_FindInvertNormalForBuildable( int bclass ) -{ - int i; + if( cmaxs != NULL ) + VectorCopy( classConfig->crouchMaxs, cmaxs ); - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].invertNormal; - } - } + if( dmins != NULL ) + VectorCopy( classConfig->deadMins, dmins ); - return qfalse; + if( dmaxs != NULL ) + VectorCopy( classConfig->deadMaxs, dmaxs ); } /* ============== -BG_FindCreepTestForBuildable +BG_ClassHasAbility ============== */ -int BG_FindCreepTestForBuildable( int bclass ) +qboolean BG_ClassHasAbility( class_t class, int ability ) { - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].creepTest; - } - } + int abilities = BG_Class( class )->abilities; - return qfalse; + return abilities & ability; } /* ============== -BG_FindCreepSizeForBuildable +BG_ClassCanEvolveFromTo ============== */ -int BG_FindCreepSizeForBuildable( int bclass ) +int BG_ClassCanEvolveFromTo( class_t fclass, + class_t tclass, + int credits, int stage, + int cost ) { - int i; - - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].creepSize; - } - } + int i, j, best, value; - return CREEP_BASESIZE; -} - -/* -============== -BG_FindDCCTestForBuildable -============== -*/ -int BG_FindDCCTestForBuildable( int bclass ) -{ - int i; + if( credits < cost || fclass == PCL_NONE || tclass == PCL_NONE || + fclass == tclass ) + return -1; - for( i = 0; i < bg_numBuildables; i++ ) + for( i = 0; i < bg_numClasses; i++ ) { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].dccTest; - } - } - - return qfalse; -} - -/* -============== -BG_FindUniqueTestForBuildable -============== -*/ -int BG_FindUniqueTestForBuildable( int bclass ) -{ - int i; + if( bg_classList[ i ].number != fclass ) + continue; - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) + best = credits + 1; + for( j = 0; j < 3; j++ ) { - return bg_buildableList[ i ].reactorTest; - } - } + int thruClass, evolveCost; + + thruClass = bg_classList[ i ].children[ j ]; + if( thruClass == PCL_NONE || !BG_ClassAllowedInStage( thruClass, stage ) || + !BG_ClassIsAllowed( thruClass ) ) + continue; - return qfalse; -} - -/* -============== -BG_FindReplaceableTestForBuildable -============== -*/ -qboolean BG_FindReplaceableTestForBuildable( int bclass ) -{ - int i; + evolveCost = BG_Class( thruClass )->cost * ALIEN_CREDITS_PER_KILL; + if( thruClass == tclass ) + value = cost + evolveCost; + else + value = BG_ClassCanEvolveFromTo( thruClass, tclass, credits, stage, + cost + evolveCost ); - for( i = 0; i < bg_numBuildables; i++ ) - { - if( bg_buildableList[ i ].buildNum == bclass ) - { - return bg_buildableList[ i ].replaceable; + if( value >= 0 && value < best ) + best = value; } + + return best <= credits ? best : -1; } - return qfalse; -} -/* -============== -BG_FindOverrideForBuildable -============== -*/ -static buildableAttributeOverrides_t *BG_FindOverrideForBuildable( int bclass ) -{ - return &bg_buildableOverrideList[ bclass ]; + Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_ClassCanEvolveFromTo\n" ); + return -1; } /* ============== -BG_FindTransparentTestForBuildable +BG_AlienCanEvolve ============== */ -qboolean BG_FindTransparentTestForBuildable( int bclass ) +qboolean BG_AlienCanEvolve( class_t class, int credits, int stage ) { - int i; + int i, j, tclass; - for( i = 0; i < bg_numBuildables; i++ ) + for( i = 0; i < bg_numClasses; i++ ) { - if( bg_buildableList[ i ].buildNum == bclass ) + if( bg_classList[ i ].number != class ) + continue; + + for( j = 0; j < 3; j++ ) { - return bg_buildableList[ i ].transparentTest; + tclass = bg_classList[ i ].children[ j ]; + if( tclass != PCL_NONE && BG_ClassAllowedInStage( tclass, stage ) && + BG_ClassIsAllowed( tclass ) && + credits >= BG_Class( tclass )->cost * ALIEN_CREDITS_PER_KILL ) + return qtrue; } + + return qfalse; } - return qfalse; + + Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_AlienCanEvolve\n" ); + return qfalse; } /* ====================== -BG_ParseBuildableFile +BG_ParseClassFile -Parses a configuration file describing a builable +Parses a configuration file describing a class ====================== */ -static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeOverrides_t *bao ) +static qboolean BG_ParseClassFile( const char *filename, classConfig_t *cc ) { char *text_p; int i; @@ -1383,8 +1414,26 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO char *token; char text[ 20000 ]; fileHandle_t f; - float scale; - + float scale = 0.0f; + int defined = 0; + enum + { + MODEL = 1 << 0, + SKIN = 1 << 1, + HUD = 1 << 2, + MODELSCALE = 1 << 3, + SHADOWSCALE = 1 << 4, + MINS = 1 << 5, + MAXS = 1 << 6, + DEADMINS = 1 << 7, + DEADMAXS = 1 << 8, + CROUCHMAXS = 1 << 9, + VIEWHEIGHT = 1 << 10, + CVIEWHEIGHT = 1 << 11, + ZOFFSET = 1 << 12, + NAME = 1 << 13, + SHOULDEROFFSETS = 1 << 14 + }; // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); @@ -1394,7 +1443,7 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO if( len == 0 || len >= sizeof( text ) - 1 ) { trap_FS_FCloseFile( f ); - Com_Printf( S_COLOR_RED "ERROR: Buildable file %s is %s\n", filename, + Com_Printf( S_COLOR_RED "ERROR: Class file %s is %s\n", filename, len == 0 ? "empty" : "too long" ); return qfalse; } @@ -1419,25 +1468,35 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO if( !Q_stricmp( token, "model" ) ) { - int index = 0; - token = COM_Parse( &text_p ); if( !token ) break; - index = atoi( token ); + Q_strncpyz( cc->modelName, token, sizeof( cc->modelName ) ); - if( index < 0 ) - index = 0; - else if( index > 3 ) - index = 3; + defined |= MODEL; + continue; + } + else if( !Q_stricmp( token, "skin" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cc->skinName, token, sizeof( cc->skinName ) ); + defined |= SKIN; + continue; + } + else if( !Q_stricmp( token, "hud" ) ) + { token = COM_Parse( &text_p ); if( !token ) break; - Q_strncpyz( bao->models[ index ], token, sizeof( bao->models[ 0 ] ) ); + Q_strncpyz( cc->hudName, token, sizeof( cc->hudName ) ); + defined |= HUD; continue; } else if( !Q_stricmp( token, "modelScale" ) ) @@ -1451,8 +1510,25 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO if( scale < 0.0f ) scale = 0.0f; - bao->modelScale = scale; + cc->modelScale = scale; + + defined |= MODELSCALE; + continue; + } + else if( !Q_stricmp( token, "shadowScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + cc->shadowScale = scale; + defined |= SHADOWSCALE; continue; } else if( !Q_stricmp( token, "mins" ) ) @@ -1463,9 +1539,10 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO if( !token ) break; - bao->mins[ i ] = atof( token ); + cc->mins[ i ] = atof( token ); } + defined |= MINS; continue; } else if( !Q_stricmp( token, "maxs" ) ) @@ -1476,9 +1553,66 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO if( !token ) break; - bao->maxs[ i ] = atof( token ); + cc->maxs[ i ] = atof( token ); + } + + defined |= MAXS; + continue; + } + else if( !Q_stricmp( token, "deadMins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->deadMins[ i ] = atof( token ); } + defined |= DEADMINS; + continue; + } + else if( !Q_stricmp( token, "deadMaxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->deadMaxs[ i ] = atof( token ); + } + + defined |= DEADMAXS; + continue; + } + else if( !Q_stricmp( token, "crouchMaxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->crouchMaxs[ i ] = atof( token ); + } + + defined |= CROUCHMAXS; + continue; + } + else if( !Q_stricmp( token, "viewheight" ) ) + { + token = COM_Parse( &text_p ); + cc->viewheight = atoi( token ); + defined |= VIEWHEIGHT; + continue; + } + else if( !Q_stricmp( token, "crouchViewheight" ) ) + { + token = COM_Parse( &text_p ); + cc->crouchViewheight = atoi( token ); + defined |= CVIEWHEIGHT; continue; } else if( !Q_stricmp( token, "zOffset" ) ) @@ -1491,3001 +1625,970 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO offset = atof( token ); - bao->zOffset = offset; + cc->zOffset = offset; + + defined |= ZOFFSET; + continue; + } + else if( !Q_stricmp( token, "name" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cc->humanName, token, sizeof( cc->humanName ) ); + defined |= NAME; continue; } + else if( !Q_stricmp( token, "shoulderOffsets" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->shoulderOffsets[ i ] = atof( token ); + } + defined |= SHOULDEROFFSETS; + continue; + } Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); return qfalse; } + if( !( defined & MODEL ) ) token = "model"; + else if( !( defined & SKIN ) ) token = "skin"; + else if( !( defined & HUD ) ) token = "hud"; + else if( !( defined & MODELSCALE ) ) token = "modelScale"; + else if( !( defined & SHADOWSCALE ) ) token = "shadowScale"; + else if( !( defined & MINS ) ) token = "mins"; + else if( !( defined & MAXS ) ) token = "maxs"; + else if( !( defined & DEADMINS ) ) token = "deadMins"; + else if( !( defined & DEADMAXS ) ) token = "deadMaxs"; + else if( !( defined & CROUCHMAXS ) ) token = "crouchMaxs"; + else if( !( defined & VIEWHEIGHT ) ) token = "viewheight"; + else if( !( defined & CVIEWHEIGHT ) ) token = "crouchViewheight"; + else if( !( defined & ZOFFSET ) ) token = "zOffset"; + else if( !( defined & NAME ) ) token = "name"; + else if( !( defined & SHOULDEROFFSETS ) ) token = "shoulderOffsets"; + else token = ""; + + if( strlen( token ) > 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: %s not defined in %s\n", + token, filename ); + return qfalse; + } + return qtrue; } /* =============== -BG_InitBuildableOverrides - -Set any overrides specfied by file +BG_InitClassConfigs =============== */ -void BG_InitBuildableOverrides( void ) +void BG_InitClassConfigs( void ) { - int i; - buildableAttributeOverrides_t *bao; + int i; + classConfig_t *cc; - for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) + for( i = PCL_NONE; i < PCL_NUM_CLASSES; i++ ) { - bao = BG_FindOverrideForBuildable( i ); + cc = BG_ClassConfig( i ); - BG_ParseBuildableFile( va( "overrides/buildables/%s.cfg", BG_FindNameForBuildable( i ) ), bao ); + BG_ParseClassFile( va( "configs/classes/%s.cfg", + BG_Class( i )->name ), cc ); } } //////////////////////////////////////////////////////////////////////////////// -classAttributes_t bg_classList[ ] = +static const weaponAttributes_t bg_weapons[ ] = { { - PCL_NONE, //int classnum; - "spectator", //char *className; - "Spectator", //char *humanName; - "", //char *modelname; - 1.0f, //float modelScale; - "", //char *skinname; - 1.0f, //float shadowScale; - "", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -15, -15, -15 }, //vec3_t mins; - { 15, 15, 15 }, //vec3_t maxs; - { 15, 15, 15 }, //vec3_t crouchmaxs; - { -15, -15, -15 }, //vec3_t deadmins; - { 15, 15, 15 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 0, 0, //int viewheight, crouchviewheight; - 0, //int health; - 0.0f, //float fallDamage; - 0, //int regenRate; - 0, //int abilities; - WP_NONE, //weapon_t startWeapon - 0.0f, //float buildDist; - 90, //int fov; - 0.000f, //float bob; - 1.0f, //float bobCycle; - 0, //int steptime; - 600, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 1.0f, //float knockbackScale; - { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - 0, //int cost; - 0 //int value; + WP_ALEVEL0, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level0", //char *name; + "Bite", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL0_BITE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL0_BITE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_BUILDER0, //int classnum; - "builder", //char *className; - "Builder", //char *humanName; - "builder", //char *modelname; - 1.0f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "alien_builder_hud", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -15, -15, -20 }, //vec3_t mins; - { 15, 15, 20 }, //vec3_t maxs; - { 15, 15, 20 }, //vec3_t crouchmaxs; - { -15, -15, -4 }, //vec3_t deadmins; - { 15, 15, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 0, 0, //int viewheight, crouchviewheight; - ABUILDER_HEALTH, //int health; - 0.2f, //float fallDamage; - ABUILDER_REGEN, //int regenRate; - SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_ALIENSENSE,//int abilities; - WP_ABUILD, //weapon_t startWeapon - 95.0f, //float buildDist; - 80, //int fov; - 0.001f, //float bob; - 2.0f, //float bobCycle; - 150, //int steptime; - ABUILDER_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 195.0f, //float jumpMagnitude; - 1.0f, //float knockbackScale; - { PCL_ALIEN_BUILDER0_UPG, PCL_ALIEN_LEVEL0, PCL_NONE }, //int children[ 3 ]; - ABUILDER_COST, //int cost; - ABUILDER_VALUE //int value; + WP_ALEVEL1, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level1", //char *name; + "Claws", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL1_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL1_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_BUILDER0_UPG, //int classnum; - "builderupg", //char *classname; - "Advanced Builder", //char *humanname; - "builder", //char *modelname; - 1.0f, //float modelScale; - "advanced", //char *skinname; - 1.0f, //float shadowScale; - "alien_builder_hud", //char *hudname; - ( 1 << S2 )|( 1 << S3 ), //int stages - { -20, -20, -20 }, //vec3_t mins; - { 20, 20, 20 }, //vec3_t maxs; - { 20, 20, 20 }, //vec3_t crouchmaxs; - { -20, -20, -4 }, //vec3_t deadmins; - { 20, 20, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 0, 0, //int viewheight, crouchviewheight; - ABUILDER_UPG_HEALTH, //int health; - 0.0f, //float fallDamage; - ABUILDER_UPG_REGEN, //int regenRate; - SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; - WP_ABUILD2, //weapon_t startWeapon - 105.0f, //float buildDist; - 110, //int fov; - 0.001f, //float bob; - 2.0f, //float bobCycle; - 100, //int steptime; - ABUILDER_UPG_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 1.0f, //float knockbackScale; - { PCL_ALIEN_LEVEL0, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - ABUILDER_UPG_COST, //int cost; - ABUILDER_UPG_VALUE //int value; + WP_ALEVEL1_UPG, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level1upg", //char *name; + "Claws Upgrade", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL1_CLAW_U_REPEAT, //int repeatRate1; + LEVEL1_PCLOUD_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL1_CLAW_U_K_SCALE, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_LEVEL0, //int classnum; - "level0", //char *classname; - "Soldier", //char *humanname; - "jumper", //char *modelname; - 0.2f, //float modelScale; - "default", //char *skinname; - 0.3f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -15, -15, -15 }, //vec3_t mins; - { 15, 15, 15 }, //vec3_t maxs; - { 15, 15, 15 }, //vec3_t crouchmaxs; - { -15, -15, -4 }, //vec3_t deadmins; - { 15, 15, 4 }, //vec3_t deadmaxs; - -8.0f, //float zOffset - 0, 0, //int viewheight, crouchviewheight; - LEVEL0_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL0_REGEN, //int regenRate; - SCA_WALLCLIMBER|SCA_NOWEAPONDRIFT| - SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL0, //weapon_t startWeapon - 0.0f, //float buildDist; - 140, //int fov; - 0.0f, //float bob; - 2.5f, //float bobCycle; - 25, //int steptime; - LEVEL0_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 400.0f, //float stopSpeed; - 250.0f, //float jumpMagnitude; - 2.0f, //float knockbackScale; - { PCL_ALIEN_LEVEL1, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - LEVEL0_COST, //int cost; - LEVEL0_VALUE //int value; + WP_ALEVEL2, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level2", //char *name; + "Bite", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL2_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL2_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_LEVEL1, //int classnum; - "level1", //char *classname; - "Hydra", //char *humanname; - "spitter", //char *modelname; - 0.6f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -18, -18, -18 }, //vec3_t mins; - { 18, 18, 18 }, //vec3_t maxs; - { 18, 18, 18 }, //vec3_t crouchmaxs; - { -18, -18, -4 }, //vec3_t deadmins; - { 18, 18, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 0, 0, //int viewheight, crouchviewheight; - LEVEL1_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL1_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT| - SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL1, //weapon_t startWeapon - 0.0f, //float buildDist; - 120, //int fov; - 0.001f, //float bob; - 1.8f, //float bobCycle; - 60, //int steptime; - LEVEL1_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 300.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 1.2f, //float knockbackScale; - { PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE }, //int children[ 3 ]; - LEVEL1_COST, //int cost; - LEVEL1_VALUE //int value; + WP_ALEVEL2_UPG, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level2upg", //char *name; + "Zap", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL2_CLAW_U_REPEAT, //int repeatRate1; + LEVEL2_AREAZAP_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL2_CLAW_U_K_SCALE, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_LEVEL1_UPG, //int classnum; - "level1upg", //char *classname; - "Hydra Upgrade", //char *humanname; - "spitter", //char *modelname; - 0.7f, //float modelScale; - "blue", //char *skinname; - 1.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S2 )|( 1 << S3 ), //int stages - { -20, -20, -20 }, //vec3_t mins; - { 20, 20, 20 }, //vec3_t maxs; - { 20, 20, 20 }, //vec3_t crouchmaxs; - { -20, -20, -4 }, //vec3_t deadmins; - { 20, 20, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 0, 0, //int viewheight, crouchviewheight; - LEVEL1_UPG_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL1_UPG_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT|SCA_FOVWARPS| - SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL1_UPG, //weapon_t startWeapon - 0.0f, //float buildDist; - 120, //int fov; - 0.001f, //float bob; - 1.8f, //float bobCycle; - 60, //int steptime; - LEVEL1_UPG_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 300.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 1.1f, //float knockbackScale; - { PCL_ALIEN_LEVEL2, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - LEVEL1_UPG_COST, //int cost; - LEVEL1_UPG_VALUE //int value; + WP_ALEVEL3, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level3", //char *name; + "Pounce", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL3_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL3_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_LEVEL2, //int classnum; - "level2", //char *classname; - "Chimera", //char *humanname; - "tarantula", //char *modelname; - 0.75f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -22, -22, -22 }, //vec3_t mins; - { 22, 22, 22 }, //vec3_t maxs; - { 22, 22, 22 }, //vec3_t crouchmaxs; - { -22, -22, -4 }, //vec3_t deadmins; - { 22, 22, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 10, 10, //int viewheight, crouchviewheight; - LEVEL2_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL2_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT|SCA_WALLJUMPER| - SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL2, //weapon_t startWeapon - 0.0f, //float buildDist; - 90, //int fov; - 0.001f, //float bob; - 1.5f, //float bobCycle; - 80, //int steptime; - LEVEL2_SPEED, //float speed; - 10.0f, //float acceleration; - 2.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 400.0f, //float jumpMagnitude; - 0.8f, //float knockbackScale; - { PCL_ALIEN_LEVEL3, PCL_ALIEN_LEVEL2_UPG, PCL_NONE }, //int children[ 3 ]; - LEVEL2_COST, //int cost; - LEVEL2_VALUE //int value; + WP_ALEVEL3_UPG, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level3upg", //char *name; + "Pounce (upgrade)", //char *humanName; + "", + 3, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL3_CLAW_U_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + LEVEL3_BOUNCEBALL_REPEAT, //int repeatRate3; + 0, //int reloadTime; + LEVEL3_CLAW_U_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_LEVEL2_UPG, //int classnum; - "level2upg", //char *classname; - "Chimera Upgrade", //char *humanname; - "tarantula", //char *modelname; - 0.9f, //float modelScale; - "red", //char *skinname; - 1.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S2 )|( 1 << S3 ), //int stages - { -24, -24, -24 }, //vec3_t mins; - { 24, 24, 24 }, //vec3_t maxs; - { 24, 24, 24 }, //vec3_t crouchmaxs; - { -24, -24, -4 }, //vec3_t deadmins; - { 24, 24, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 12, 12, //int viewheight, crouchviewheight; - LEVEL2_UPG_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL2_UPG_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT|SCA_WALLJUMPER| - SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL2_UPG, //weapon_t startWeapon - 0.0f, //float buildDist; - 90, //int fov; - 0.001f, //float bob; - 1.5f, //float bobCycle; - 80, //int steptime; - LEVEL2_UPG_SPEED, //float speed; - 10.0f, //float acceleration; - 2.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 400.0f, //float jumpMagnitude; - 0.7f, //float knockbackScale; - { PCL_ALIEN_LEVEL3, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - LEVEL2_UPG_COST, //int cost; - LEVEL2_UPG_VALUE //int value; + WP_ALEVEL4, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "level4", //char *name; + "Charge", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL4_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL4_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - PCL_ALIEN_LEVEL3, //int classnum; - "level3", //char *classname; - "Dragoon", //char *humanname; - "prowl", //char *modelname; - 1.0f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -32, -32, -21 }, //vec3_t mins; - { 32, 32, 21 }, //vec3_t maxs; - { 32, 32, 21 }, //vec3_t crouchmaxs; - { -32, -32, -4 }, //vec3_t deadmins; - { 32, 32, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 24, 24, //int viewheight, crouchviewheight; - LEVEL3_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL3_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT| - SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL3, //weapon_t startWeapon - 0.0f, //float buildDist; - 110, //int fov; - 0.0005f, //float bob; - 1.3f, //float bobCycle; - 90, //int steptime; - LEVEL3_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 200.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 0.5f, //float knockbackScale; - { PCL_ALIEN_LEVEL4, PCL_ALIEN_LEVEL3_UPG, PCL_NONE }, //int children[ 3 ]; - LEVEL3_COST, //int cost; - LEVEL3_VALUE //int value; + WP_BLASTER, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + 0, //int slots; + "blaster", //char *name; + "Blaster", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + BLASTER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + BLASTER_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - PCL_ALIEN_LEVEL3_UPG, //int classnum; - "level3upg", //char *classname; - "Dragoon Upgrade", //char *humanname; - "prowl", //char *modelname; - 1.0f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S3 ), //int stages - { -32, -32, -21 }, //vec3_t mins; - { 32, 32, 21 }, //vec3_t maxs; - { 32, 32, 21 }, //vec3_t crouchmaxs; - { -32, -32, -4 }, //vec3_t deadmins; - { 32, 32, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 27, 27, //int viewheight, crouchviewheight; - LEVEL3_UPG_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL3_UPG_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT| - SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL3_UPG, //weapon_t startWeapon - 0.0f, //float buildDist; - 110, //int fov; - 0.0005f, //float bob; - 1.3f, //float bobCycle; - 90, //int steptime; - LEVEL3_UPG_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 200.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 0.4f, //float knockbackScale; - { PCL_ALIEN_LEVEL4, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - LEVEL3_UPG_COST, //int cost; - LEVEL3_UPG_VALUE //int value; + WP_MACHINEGUN, //int number; + RIFLE_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "rifle", //char *name; + "Rifle", //char *humanName; + "Basic weapon. Cased projectile weapon, with a slow clip based " + "reload system.", + RIFLE_CLIPSIZE, //int maxAmmo; + RIFLE_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + RIFLE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + RIFLE_RELOAD, //int reloadTime; + RIFLE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - PCL_ALIEN_LEVEL4, //int classnum; - "level4", //char *classname; - "Big Mofo", //char *humanname; - "mofo", //char *modelname; - 1.0f, //float modelScale; - "default", //char *skinname; - 2.0f, //float shadowScale; - "alien_general_hud", //char *hudname; - ( 1 << S3 ), //int stages - { -30, -30, -20 }, //vec3_t mins; - { 30, 30, 20 }, //vec3_t maxs; - { 30, 30, 20 }, //vec3_t crouchmaxs; - { -15, -15, -4 }, //vec3_t deadmins; - { 15, 15, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 35, 35, //int viewheight, crouchviewheight; - LEVEL4_HEALTH, //int health; - 0.0f, //float fallDamage; - LEVEL4_REGEN, //int regenRate; - SCA_NOWEAPONDRIFT| - SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; - WP_ALEVEL4, //weapon_t startWeapon - 0.0f, //float buildDist; - 90, //int fov; - 0.001f, //float bob; - 1.1f, //float bobCycle; - 100, //int steptime; - LEVEL4_SPEED, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 170.0f, //float jumpMagnitude; - 0.1f, //float knockbackScale; - { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - LEVEL4_COST, //int cost; - LEVEL4_VALUE //int value; + WP_PAIN_SAW, //int number; + PAINSAW_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "psaw", //char *name; + "Pain Saw", //char *humanName; + "Similar to a chainsaw, but instead of a chain it has an " + "electric arc capable of dealing a great deal of damage at " + "close range.", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + PAINSAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + PAINSAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - PCL_HUMAN, //int classnum; - "human_base", //char *classname; - "Human", //char *humanname; - "sarge", //char *modelname; - 1.0f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "human_hud", //char *hudname; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - { -15, -15, -24 }, //vec3_t mins; - { 15, 15, 32 }, //vec3_t maxs; - { 15, 15, 16 }, //vec3_t crouchmaxs; - { -15, -15, -4 }, //vec3_t deadmins; - { 15, 15, 4 }, //vec3_t deadmaxs; - 0.0f, //float zOffset - 26, 12, //int viewheight, crouchviewheight; - 100, //int health; - 1.0f, //float fallDamage; - 0, //int regenRate; - SCA_TAKESFALLDAMAGE| - SCA_CANUSELADDERS, //int abilities; - WP_NONE, //special-cased in g_client.c //weapon_t startWeapon - 110.0f, //float buildDist; - 90, //int fov; - 0.002f, //float bob; - 1.0f, //float bobCycle; - 100, //int steptime; - 1.0f, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 220.0f, //float jumpMagnitude; - 1.0f, //float knockbackScale; - { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - 0, //int cost; - 0 //int value; + WP_SHOTGUN, //int number; + SHOTGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "shotgun", //char *name; + "Shotgun", //char *humanName; + "Close range weapon that is useful against larger foes. " + "It has a slow repeat rate, but can be devastatingly " + "effective.", + SHOTGUN_SHELLS, //int maxAmmo; + SHOTGUN_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + SHOTGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + SHOTGUN_RELOAD, //int reloadTime; + SHOTGUN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - PCL_HUMAN_BSUIT, //int classnum; - "human_bsuit", //char *classname; - "bsuit", //char *humanname; - "keel", //char *modelname; - 1.0f, //float modelScale; - "default", //char *skinname; - 1.0f, //float shadowScale; - "human_hud", //char *hudname; - ( 1 << S3 ), //int stages - { -15, -15, -38 }, //vec3_t mins; - { 15, 15, 38 }, //vec3_t maxs; - { 15, 15, 38 }, //vec3_t crouchmaxs; - { -15, -15, -4 }, //vec3_t deadmins; - { 15, 15, 4 }, //vec3_t deadmaxs; - -16.0f, //float zOffset - 35, 35, //int viewheight, crouchviewheight; - 100, //int health; - 1.0f, //float fallDamage; - 0, //int regenRate; - SCA_TAKESFALLDAMAGE| - SCA_CANUSELADDERS, //int abilities; - WP_NONE, //special-cased in g_client.c //weapon_t startWeapon - 110.0f, //float buildDist; - 90, //int fov; - 0.002f, //float bob; - 1.0f, //float bobCycle; - 100, //int steptime; - 1.0f, //float speed; - 10.0f, //float acceleration; - 1.0f, //float airAcceleration; - 6.0f, //float friction; - 100.0f, //float stopSpeed; - 270.0f, //float jumpMagnitude; - 1.0f, //float knockbackScale; - { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; - 0, //int cost; - 0 //int value; + WP_LAS_GUN, //int number; + LASGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "lgun", //char *name; + "Las Gun", //char *humanName; + "Slightly more powerful than the basic rifle, rapidly fires " + "small packets of energy.", + LASGUN_AMMO, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + LASGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + LASGUN_RELOAD, //int reloadTime; + LASGUN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, -}; - -int bg_numPclasses = sizeof( bg_classList ) / sizeof( bg_classList[ 0 ] ); - -//separate from bg_classList to work around char struct init bug -classAttributeOverrides_t bg_classOverrideList[ PCL_NUM_CLASSES ]; - -/* -============== -BG_FindClassNumForName -============== -*/ -int BG_FindClassNumForName( char *name ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) { - if( !Q_stricmp( bg_classList[ i ].className, name ) ) - return bg_classList[ i ].classNum; - } - - //wimp out - return PCL_NONE; -} - -/* -============== -BG_FindNameForClassNum -============== -*/ -char *BG_FindNameForClassNum( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - return bg_classList[ i ].className; - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindNameForClassNum\n" ); - //wimp out - return 0; -} - -/* -============== -BG_FindHumanNameForClassNum -============== -*/ -char *BG_FindHumanNameForClassNum( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].humanName[ 0 ] != 0 ) - return bg_classOverrideList[ pclass ].humanName; - - for( i = 0; i < bg_numPclasses; i++ ) + WP_MASS_DRIVER, //int number; + MDRIVER_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "mdriver", //char *name; + "Mass Driver", //char *humanName; + "A portable particle accelerator which causes minor nuclear " + "reactions at the point of impact. It has a very large " + "payload, but fires slowly.", + MDRIVER_CLIPSIZE, //int maxAmmo; + MDRIVER_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + MDRIVER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + MDRIVER_RELOAD, //int reloadTime; + MDRIVER_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qtrue, //qboolean canZoom; + 20.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, { - if( bg_classList[ i ].classNum == pclass ) - return bg_classList[ i ].humanName; - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHumanNameForClassNum\n" ); - //wimp out - return 0; -} - -/* -============== -BG_FindModelNameForClass -============== -*/ -char *BG_FindModelNameForClass( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].modelName[ 0 ] != 0 ) - return bg_classOverrideList[ pclass ].modelName; - - for( i = 0; i < bg_numPclasses; i++ ) + WP_CHAINGUN, //int number; + CHAINGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "chaingun", //char *name; + "Chaingun", //char *humanName; + "Belt drive, cased projectile weapon. It has a high repeat " + "rate but a wide firing angle and is therefore relatively " + "inaccurate.", + CHAINGUN_BULLETS, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + CHAINGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + CHAINGUN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, { - if( bg_classList[ i ].classNum == pclass ) - return bg_classList[ i ].modelName; - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelNameForClass\n" ); - //note: must return a valid modelName! - return bg_classList[ 0 ].modelName; -} - -/* -============== -BG_FindModelScaleForClass -============== -*/ -float BG_FindModelScaleForClass( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].modelScale != 0.0f ) - return bg_classOverrideList[ pclass ].modelScale; - - for( i = 0; i < bg_numPclasses; i++ ) + WP_FLAMER, //int number; + FLAMER_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "flamer", //char *name; + "Flame Thrower", //char *humanName; + "Sprays fire at its target. It is powered by compressed " + "gas. The relatively low rate of fire means this weapon is most " + "effective against static targets.", + FLAMER_GAS, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + FLAMER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + FLAMER_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].modelScale; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForClass( %d )\n", pclass ); - return 1.0f; -} - -/* -============== -BG_FindSkinNameForClass -============== -*/ -char *BG_FindSkinNameForClass( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].skinName[ 0 ] != 0 ) - return bg_classOverrideList[ pclass ].skinName; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - return bg_classList[ i ].skinName; - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSkinNameForClass\n" ); - //note: must return a valid modelName! - return bg_classList[ 0 ].skinName; -} - -/* -============== -BG_FindShadowScaleForClass -============== -*/ -float BG_FindShadowScaleForClass( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].shadowScale != 0.0f ) - return bg_classOverrideList[ pclass ].shadowScale; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].shadowScale; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindShadowScaleForClass( %d )\n", pclass ); - return 1.0f; -} - -/* -============== -BG_FindHudNameForClass -============== -*/ -char *BG_FindHudNameForClass( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].hudName[ 0 ] != 0 ) - return bg_classOverrideList[ pclass ].hudName; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - return bg_classList[ i ].hudName; - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHudNameForClass\n" ); - //note: must return a valid hudName! - return bg_classList[ 0 ].hudName; -} - -/* -============== -BG_FindStagesForClass -============== -*/ -qboolean BG_FindStagesForClass( int pclass, stage_t stage ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - if( bg_classList[ i ].stages & ( 1 << stage ) ) - return qtrue; - else - return qfalse; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStagesForClass\n" ); - return qfalse; -} - -/* -============== -BG_FindBBoxForClass -============== -*/ -void BG_FindBBoxForClass( int pclass, vec3_t mins, vec3_t maxs, vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - if( mins != NULL ) - { - VectorCopy( bg_classList[ i ].mins, mins ); - - if( VectorLength( bg_classOverrideList[ pclass ].mins ) ) - VectorCopy( bg_classOverrideList[ pclass ].mins, mins ); - } - - if( maxs != NULL ) - { - VectorCopy( bg_classList[ i ].maxs, maxs ); - - if( VectorLength( bg_classOverrideList[ pclass ].maxs ) ) - VectorCopy( bg_classOverrideList[ pclass ].maxs, maxs ); - } - - if( cmaxs != NULL ) - { - VectorCopy( bg_classList[ i ].crouchMaxs, cmaxs ); - - if( VectorLength( bg_classOverrideList[ pclass ].crouchMaxs ) ) - VectorCopy( bg_classOverrideList[ pclass ].crouchMaxs, cmaxs ); - } - - if( dmins != NULL ) - { - VectorCopy( bg_classList[ i ].deadMins, dmins ); - - if( VectorLength( bg_classOverrideList[ pclass ].deadMins ) ) - VectorCopy( bg_classOverrideList[ pclass ].deadMins, dmins ); - } - - if( dmaxs != NULL ) - { - VectorCopy( bg_classList[ i ].deadMaxs, dmaxs ); - - if( VectorLength( bg_classOverrideList[ pclass ].deadMaxs ) ) - VectorCopy( bg_classOverrideList[ pclass ].deadMaxs, dmaxs ); - } - - return; - } - } - - if( mins != NULL ) - VectorCopy( bg_classList[ 0 ].mins, mins ); - - if( maxs != NULL ) - VectorCopy( bg_classList[ 0 ].maxs, maxs ); - - if( cmaxs != NULL ) - VectorCopy( bg_classList[ 0 ].crouchMaxs, cmaxs ); - - if( dmins != NULL ) - VectorCopy( bg_classList[ 0 ].deadMins, dmins ); - - if( dmaxs != NULL ) - VectorCopy( bg_classList[ 0 ].deadMaxs, dmaxs ); -} - -/* -============== -BG_FindZOffsetForClass -============== -*/ -float BG_FindZOffsetForClass( int pclass ) -{ - int i; - - if( bg_classOverrideList[ pclass ].zOffset != 0.0f ) - return bg_classOverrideList[ pclass ].zOffset; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].zOffset; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindZOffsetForClass\n" ); - return 0.0f; -} - -/* -============== -BG_FindViewheightForClass -============== -*/ -void BG_FindViewheightForClass( int pclass, int *viewheight, int *cViewheight ) -{ - int i; - int vh = 0; - int cvh = 0; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - vh = bg_classList[ i ].viewheight; - cvh = bg_classList[ i ].crouchViewheight; - break; - } - } - - if( bg_classOverrideList[ pclass ].viewheight != 0 ) - vh = bg_classOverrideList[ pclass ].viewheight; - if( bg_classOverrideList[ pclass ].crouchViewheight != 0 ) - cvh = bg_classOverrideList[ pclass ].crouchViewheight; - - - if( vh == 0 ) - vh = bg_classList[ 0 ].viewheight; - if( cvh == 0 ) - cvh = bg_classList[ 0 ].crouchViewheight; - - if( viewheight != NULL ) - *viewheight = vh; - if( cViewheight != NULL ) - *cViewheight = cvh; -} - -/* -============== -BG_FindHealthForClass -============== -*/ -int BG_FindHealthForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].health; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHealthForClass\n" ); - return 100; -} - -/* -============== -BG_FindFallDamageForClass -============== -*/ -float BG_FindFallDamageForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].fallDamage; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFallDamageForClass\n" ); - return 100; -} - -/* -============== -BG_FindRegenRateForClass -============== -*/ -int BG_FindRegenRateForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].regenRate; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindRegenRateForClass\n" ); - return 0; -} - -/* -============== -BG_FindFovForClass -============== -*/ -int BG_FindFovForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].fov; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFovForClass\n" ); - return 90; -} - -/* -============== -BG_FindBobForClass -============== -*/ -float BG_FindBobForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].bob; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBobForClass\n" ); - return 0.002; -} - -/* -============== -BG_FindBobCycleForClass -============== -*/ -float BG_FindBobCycleForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].bobCycle; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBobCycleForClass\n" ); - return 1.0f; -} - -/* -============== -BG_FindSpeedForClass -============== -*/ -float BG_FindSpeedForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].speed; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSpeedForClass\n" ); - return 1.0f; -} - -/* -============== -BG_FindAccelerationForClass -============== -*/ -float BG_FindAccelerationForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].acceleration; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindAccelerationForClass\n" ); - return 10.0f; -} - -/* -============== -BG_FindAirAccelerationForClass -============== -*/ -float BG_FindAirAccelerationForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].airAcceleration; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindAirAccelerationForClass\n" ); - return 1.0f; -} - -/* -============== -BG_FindFrictionForClass -============== -*/ -float BG_FindFrictionForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].friction; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFrictionForClass\n" ); - return 6.0f; -} - -/* -============== -BG_FindStopSpeedForClass -============== -*/ -float BG_FindStopSpeedForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].stopSpeed; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStopSpeedForClass\n" ); - return 100.0f; -} - -/* -============== -BG_FindJumpMagnitudeForClass -============== -*/ -float BG_FindJumpMagnitudeForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].jumpMagnitude; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindJumpMagnitudeForClass\n" ); - return 270.0f; -} - -/* -============== -BG_FindKnockbackScaleForClass -============== -*/ -float BG_FindKnockbackScaleForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].knockbackScale; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindKnockbackScaleForClass\n" ); - return 1.0f; -} - -/* -============== -BG_FindSteptimeForClass -============== -*/ -int BG_FindSteptimeForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].steptime; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSteptimeForClass\n" ); - return 200; -} - -/* -============== -BG_ClassHasAbility -============== -*/ -qboolean BG_ClassHasAbility( int pclass, int ability ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return ( bg_classList[ i ].abilities & ability ); - } - } - - return qfalse; -} - -/* -============== -BG_FindStartWeaponForClass -============== -*/ -weapon_t BG_FindStartWeaponForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].startWeapon; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStartWeaponForClass\n" ); - return WP_NONE; -} - -/* -============== -BG_FindBuildDistForClass -============== -*/ -float BG_FindBuildDistForClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].buildDist; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBuildDistForClass\n" ); - return 0.0f; -} - -/* -============== -BG_ClassCanEvolveFromTo -============== -*/ -int BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num ) -{ - int i, j, cost; - - cost = BG_FindCostOfClass( tclass ); - - //base case - if( credits < cost ) - return -1; - - if( fclass == PCL_NONE || tclass == PCL_NONE ) - return -1; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == fclass ) - { - for( j = 0; j < 3; j++ ) - if( bg_classList[ i ].children[ j ] == tclass ) - return num + cost; - - for( j = 0; j < 3; j++ ) - { - int sub; - - cost = BG_FindCostOfClass( bg_classList[ i ].children[ j ] ); - sub = BG_ClassCanEvolveFromTo( bg_classList[ i ].children[ j ], - tclass, credits - cost, num + cost ); - if( sub >= 0 ) - return sub; - } - - return -1; //may as well return by this point - } - } - - return -1; -} - -/* -============== -BG_FindValueOfClass -============== -*/ -int BG_FindValueOfClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].value; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindValueOfClass\n" ); - return 0; -} - -/* -============== -BG_FindCostOfClass -============== -*/ -int BG_FindCostOfClass( int pclass ) -{ - int i; - - for( i = 0; i < bg_numPclasses; i++ ) - { - if( bg_classList[ i ].classNum == pclass ) - { - return bg_classList[ i ].cost; - } - } - - Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindCostOfClass\n" ); - return 0; -} - -/* -============== -BG_FindOverrideForClass -============== -*/ -static classAttributeOverrides_t *BG_FindOverrideForClass( int pclass ) -{ - return &bg_classOverrideList[ pclass ]; -} - -/* -====================== -BG_ParseClassFile - -Parses a configuration file describing a class -====================== -*/ -static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides_t *cao ) -{ - char *text_p; - int i; - int len; - char *token; - char text[ 20000 ]; - fileHandle_t f; - float scale = 0.0f; - - - // load the file - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( len < 0 ) - return qfalse; - - if( len == 0 || len >= sizeof( text ) - 1 ) - { - trap_FS_FCloseFile( f ); - Com_Printf( S_COLOR_RED "ERROR: Class file %s is %s\n", filename, - len == 0 ? "empty" : "too long" ); - return qfalse; - } - - trap_FS_Read( text, len, f ); - text[ len ] = 0; - trap_FS_FCloseFile( f ); - - // parse the text - text_p = text; - - // read optional parameters - while( 1 ) - { - token = COM_Parse( &text_p ); - - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) - break; - - if( !Q_stricmp( token, "model" ) ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - Q_strncpyz( cao->modelName, token, sizeof( cao->modelName ) ); - - continue; - } - else if( !Q_stricmp( token, "skin" ) ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - Q_strncpyz( cao->skinName, token, sizeof( cao->skinName ) ); - - continue; - } - else if( !Q_stricmp( token, "hud" ) ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - Q_strncpyz( cao->hudName, token, sizeof( cao->hudName ) ); - - continue; - } - else if( !Q_stricmp( token, "modelScale" ) ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - scale = atof( token ); - - if( scale < 0.0f ) - scale = 0.0f; - - cao->modelScale = scale; - - continue; - } - else if( !Q_stricmp( token, "shadowScale" ) ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - scale = atof( token ); - - if( scale < 0.0f ) - scale = 0.0f; - - cao->shadowScale = scale; - - continue; - } - else if( !Q_stricmp( token, "mins" ) ) - { - for( i = 0; i <= 2; i++ ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - cao->mins[ i ] = atof( token ); - } - - continue; - } - else if( !Q_stricmp( token, "maxs" ) ) - { - for( i = 0; i <= 2; i++ ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - cao->maxs[ i ] = atof( token ); - } - - continue; - } - else if( !Q_stricmp( token, "deadMins" ) ) - { - for( i = 0; i <= 2; i++ ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - cao->deadMins[ i ] = atof( token ); - } - - continue; - } - else if( !Q_stricmp( token, "deadMaxs" ) ) - { - for( i = 0; i <= 2; i++ ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - cao->deadMaxs[ i ] = atof( token ); - } - - continue; - } - else if( !Q_stricmp( token, "crouchMaxs" ) ) - { - for( i = 0; i <= 2; i++ ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - cao->crouchMaxs[ i ] = atof( token ); - } - - continue; - } - else if( !Q_stricmp( token, "viewheight" ) ) - { - token = COM_Parse( &text_p ); - cao->viewheight = atoi( token ); - continue; - } - else if( !Q_stricmp( token, "crouchViewheight" ) ) - { - token = COM_Parse( &text_p ); - cao->crouchViewheight = atoi( token ); - continue; - } - else if( !Q_stricmp( token, "zOffset" ) ) - { - float offset; - - token = COM_Parse( &text_p ); - if( !token ) - break; - - offset = atof( token ); - - cao->zOffset = offset; - - continue; - } - else if( !Q_stricmp( token, "name" ) ) - { - token = COM_Parse( &text_p ); - if( !token ) - break; - - Q_strncpyz( cao->humanName, token, sizeof( cao->humanName ) ); - - continue; - } - - - Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); - return qfalse; - } - - return qtrue; -} - -/* -=============== -BG_InitClassOverrides - -Set any overrides specfied by file -=============== -*/ -void BG_InitClassOverrides( void ) -{ - int i; - classAttributeOverrides_t *cao; - - for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) - { - cao = BG_FindOverrideForClass( i ); - - BG_ParseClassFile( va( "overrides/classes/%s.cfg", BG_FindNameForClassNum( i ) ), cao ); - } -} - -//////////////////////////////////////////////////////////////////////////////// - -weaponAttributes_t bg_weapons[ ] = -{ - { - WP_BLASTER, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - 0, //int slots; - "blaster", //char *weaponName; - "Blaster", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - BLASTER_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - BLASTER_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_MACHINEGUN, //int weaponNum; - RIFLE_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "rifle", //char *weaponName; - "Rifle", //char *weaponHumanName; - RIFLE_CLIPSIZE, //int maxAmmo; - RIFLE_MAXCLIPS, //int maxClips; - qfalse, //int infiniteAmmo; - qfalse, //int usesEnergy; - RIFLE_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - RIFLE_RELOAD, //int reloadTime; - RIFLE_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_SHOTGUN, //int weaponNum; - SHOTGUN_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "shotgun", //char *weaponName; - "Shotgun", //char *weaponHumanName; - SHOTGUN_SHELLS, //int maxAmmo; - SHOTGUN_MAXCLIPS, //int maxClips; - qfalse, //int infiniteAmmo; - qfalse, //int usesEnergy; - SHOTGUN_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - SHOTGUN_RELOAD, //int reloadTime; - SHOTGUN_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_FLAMER, //int weaponNum; - FLAMER_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "flamer", //char *weaponName; - "Flame Thrower", //char *weaponHumanName; - FLAMER_GAS, //int maxAmmo; - 0, //int maxClips; - qfalse, //int infiniteAmmo; - qfalse, //int usesEnergy; - FLAMER_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - FLAMER_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_CHAINGUN, //int weaponNum; - CHAINGUN_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "chaingun", //char *weaponName; - "Chaingun", //char *weaponHumanName; - CHAINGUN_BULLETS, //int maxAmmo; - 0, //int maxClips; - qfalse, //int infiniteAmmo; - qfalse, //int usesEnergy; - CHAINGUN_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - CHAINGUN_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_MASS_DRIVER, //int weaponNum; - MDRIVER_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "mdriver", //char *weaponName; - "Mass Driver", //char *weaponHumanName; - MDRIVER_CLIPSIZE, //int maxAmmo; - MDRIVER_MAXCLIPS, //int maxClips; - qfalse, //int infiniteAmmo; - qtrue, //int usesEnergy; - MDRIVER_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - MDRIVER_RELOAD, //int reloadTime; - MDRIVER_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qtrue, //qboolean canZoom; - 20.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_PULSE_RIFLE, //int weaponNum; - PRIFLE_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "prifle", //char *weaponName; - "Pulse Rifle", //char *weaponHumanName; - PRIFLE_CLIPS, //int maxAmmo; - PRIFLE_MAXCLIPS, //int maxClips; - qfalse, //int infiniteAmmo; - qtrue, //int usesEnergy; - PRIFLE_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - PRIFLE_RELOAD, //int reloadTime; - PRIFLE_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_LUCIFER_CANNON, //int weaponNum; - LCANNON_PRICE, //int price; - ( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "lcannon", //char *weaponName; - "Lucifer Cannon", //char *weaponHumanName; - LCANNON_AMMO, //int maxAmmo; - 0, //int maxClips; - qfalse, //int infiniteAmmo; - qtrue, //int usesEnergy; - LCANNON_REPEAT, //int repeatRate1; - LCANNON_CHARGEREPEAT, //int repeatRate2; - 0, //int repeatRate3; - LCANNON_RELOAD, //int reloadTime; - LCANNON_K_SCALE, //float knockbackScale; - qtrue, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_LAS_GUN, //int weaponNum; - LASGUN_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "lgun", //char *weaponName; - "Las Gun", //char *weaponHumanName; - LASGUN_AMMO, //int maxAmmo; - 0, //int maxClips; - qfalse, //int infiniteAmmo; - qtrue, //int usesEnergy; - LASGUN_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - LASGUN_RELOAD, //int reloadTime; - LASGUN_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_PAIN_SAW, //int weaponNum; - PAINSAW_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "psaw", //char *weaponName; - "Pain Saw", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - PAINSAW_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - PAINSAW_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_GRENADE, //int weaponNum; - GRENADE_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_NONE, //int slots; - "grenade", //char *weaponName; - "Grenade", //char *weaponHumanName; - 1, //int maxAmmo; - 0, //int maxClips; - qfalse, //int infiniteAmmo; - qfalse, //int usesEnergy; - GRENADE_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - GRENADE_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_HBUILD, //int weaponNum; - HBUILD_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "ckit", //char *weaponName; - "Construction Kit", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - HBUILD_REPEAT, //int repeatRate1; - HBUILD_REPEAT, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - 0.0f, //float knockbackScale; - qtrue, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qfalse, //qboolean longRanged; - HBUILD_DELAY, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_HBUILD2, //int weaponNum; - HBUILD2_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "ackit", //char *weaponName; - "Adv Construction Kit",//char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - HBUILD2_REPEAT, //int repeatRate1; - HBUILD2_REPEAT, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - 0.0f, //float knockbackScale; - qtrue, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qfalse, //qboolean longRanged; - HBUILD2_DELAY, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_ABUILD, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "abuild", //char *weaponName; - "Alien build weapon", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - ABUILDER_BUILD_REPEAT,//int repeatRate1; - ABUILDER_BUILD_REPEAT,//int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - 0.0f, //float knockbackScale; - qtrue, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qfalse, //qboolean longRanged; - ABUILDER_BASE_DELAY, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ABUILD2, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "abuildupg", //char *weaponName; - "Alien build weapon2",//char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - ABUILDER_BUILD_REPEAT,//int repeatRate1; - ABUILDER_CLAW_REPEAT, //int repeatRate2; - ABUILDER_BLOB_REPEAT, //int repeatRate3; - 0, //int reloadTime; - ABUILDER_CLAW_K_SCALE,//float knockbackScale; - qtrue, //qboolean hasAltMode; - qtrue, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qtrue, //qboolean purchasable; - qfalse, //qboolean longRanged; - ABUILDER_ADV_DELAY, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL0, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level0", //char *weaponName; - "Bite", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL0_BITE_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL0_BITE_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL1, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level1", //char *weaponName; - "Claws", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL1_CLAW_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL1_CLAW_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL1_UPG, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level1upg", //char *weaponName; - "Claws Upgrade", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL1_CLAW_U_REPEAT, //int repeatRate1; - LEVEL1_PCLOUD_REPEAT, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL1_CLAW_U_K_SCALE,//float knockbackScale; - qtrue, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL2, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level2", //char *weaponName; - "Bite", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL2_CLAW_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL2_CLAW_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL2_UPG, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level2upg", //char *weaponName; - "Zap", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL2_CLAW_U_REPEAT, //int repeatRate1; - LEVEL2_AREAZAP_REPEAT,//int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL2_CLAW_U_K_SCALE,//float knockbackScale; - qtrue, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL3, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level3", //char *weaponName; - "Pounce", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL3_CLAW_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL3_CLAW_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL3_UPG, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level3upg", //char *weaponName; - "Pounce (upgrade)", //char *weaponHumanName; - 3, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL3_CLAW_U_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - LEVEL3_BOUNCEBALL_REPEAT,//int repeatRate3; - 0, //int reloadTime; - LEVEL3_CLAW_U_K_SCALE,//float knockbackScale; - qfalse, //qboolean hasAltMode; - qtrue, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qtrue, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_ALEVEL4, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "level4", //char *weaponName; - "Charge", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - LEVEL4_CLAW_REPEAT, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - LEVEL4_CLAW_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_LOCKBLOB_LAUNCHER, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "lockblob", //char *weaponName; - "Lock Blob", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - 500, //int repeatRate1; - 500, //int repeatRate2; - 500, //int repeatRate3; - 0, //int reloadTime; - LOCKBLOB_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_HIVE, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "hive", //char *weaponName; - "Hive", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - 500, //int repeatRate1; - 500, //int repeatRate2; - 500, //int repeatRate3; - 0, //int reloadTime; - HIVE_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_ALIENS //WUTeam_t team; - }, - { - WP_MGTURRET, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "mgturret", //char *weaponName; - "Machinegun Turret", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qfalse, //int usesEnergy; - 0, //int repeatRate1; - 0, //int repeatRate2; - 0, //int repeatRate3; - 0, //int reloadTime; - MGTURRET_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - }, - { - WP_TESLAGEN, //int weaponNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_WEAPON, //int slots; - "teslagen", //char *weaponName; - "Tesla Generator", //char *weaponHumanName; - 0, //int maxAmmo; - 0, //int maxClips; - qtrue, //int infiniteAmmo; - qtrue, //int usesEnergy; - 500, //int repeatRate1; - 500, //int repeatRate2; - 500, //int repeatRate3; - 0, //int reloadTime; - TESLAGEN_K_SCALE, //float knockbackScale; - qfalse, //qboolean hasAltMode; - qfalse, //qboolean hasThirdMode; - qfalse, //qboolean canZoom; - 90.0f, //float zoomFov; - qfalse, //qboolean purchasable; - qfalse, //qboolean longRanged; - 0, //int buildDelay; - WUT_HUMANS //WUTeam_t team; - } -}; - -int bg_numWeapons = sizeof( bg_weapons ) / sizeof( bg_weapons[ 0 ] ); - -/* -============== -BG_FindPriceForWeapon -============== -*/ -int BG_FindPriceForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].price; - } - } - - return 100; -} - -/* -============== -BG_FindStagesForWeapon -============== -*/ -qboolean BG_FindStagesForWeapon( int weapon, stage_t stage ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - if( bg_weapons[ i ].stages & ( 1 << stage ) ) - return qtrue; - else - return qfalse; - } - } - - return qfalse; -} - -/* -============== -BG_FindSlotsForWeapon -============== -*/ -int BG_FindSlotsForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].slots; - } - } - - return SLOT_WEAPON; -} - -/* -============== -BG_FindNameForWeapon -============== -*/ -char *BG_FindNameForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - return bg_weapons[ i ].weaponName; - } - - //wimp out - return 0; -} - -/* -============== -BG_FindWeaponNumForName -============== -*/ -int BG_FindWeaponNumForName( char *name ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( !Q_stricmp( bg_weapons[ i ].weaponName, name ) ) - return bg_weapons[ i ].weaponNum; - } - - //wimp out - return WP_NONE; -} - -/* -============== -BG_FindHumanNameForWeapon -============== -*/ -char *BG_FindHumanNameForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - return bg_weapons[ i ].weaponHumanName; - } - - //wimp out - return 0; -} - -/* -============== -BG_FindAmmoForWeapon -============== -*/ -void BG_FindAmmoForWeapon( int weapon, int *maxAmmo, int *maxClips ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - if( maxAmmo != NULL ) - *maxAmmo = bg_weapons[ i ].maxAmmo; - if( maxClips != NULL ) - *maxClips = bg_weapons[ i ].maxClips; - - //no need to keep going - break; - } - } -} - -/* -============== -BG_FindInfinteAmmoForWeapon -============== -*/ -qboolean BG_FindInfinteAmmoForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].infiniteAmmo; - } - } - - return qfalse; -} - -/* -============== -BG_FindUsesEnergyForWeapon -============== -*/ -qboolean BG_FindUsesEnergyForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].usesEnergy; - } - } - - return qfalse; -} - -/* -============== -BG_FindRepeatRate1ForWeapon -============== -*/ -int BG_FindRepeatRate1ForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - return bg_weapons[ i ].repeatRate1; - } - - return 1000; -} - -/* -============== -BG_FindRepeatRate2ForWeapon -============== -*/ -int BG_FindRepeatRate2ForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - return bg_weapons[ i ].repeatRate2; - } - - return 1000; -} - -/* -============== -BG_FindRepeatRate3ForWeapon -============== -*/ -int BG_FindRepeatRate3ForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - return bg_weapons[ i ].repeatRate3; - } - - return 1000; -} - -/* -============== -BG_FindReloadTimeForWeapon -============== -*/ -int BG_FindReloadTimeForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].reloadTime; - } - } - - return 1000; -} - -/* -============== -BG_FindKnockbackScaleForWeapon -============== -*/ -float BG_FindKnockbackScaleForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].knockbackScale; - } - } - - return 1.0f; -} - -/* -============== -BG_WeaponHasAltMode -============== -*/ -qboolean BG_WeaponHasAltMode( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].hasAltMode; - } - } - - return qfalse; -} - -/* -============== -BG_WeaponHasThirdMode -============== -*/ -qboolean BG_WeaponHasThirdMode( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].hasThirdMode; - } - } - - return qfalse; -} - -/* -============== -BG_WeaponCanZoom -============== -*/ -qboolean BG_WeaponCanZoom( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].canZoom; - } - } - - return qfalse; -} - -/* -============== -BG_FindZoomFovForWeapon -============== -*/ -float BG_FindZoomFovForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].zoomFov; - } - } - - return qfalse; -} - -/* -============== -BG_FindPurchasableForWeapon -============== -*/ -qboolean BG_FindPurchasableForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].purchasable; - } - } - - return qfalse; -} - -/* -============== -BG_FindLongRangeForWeapon -============== -*/ -qboolean BG_FindLongRangedForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].longRanged; - } - } - - return qfalse; -} - -/* -============== -BG_FindBuildDelayForWeapon -============== -*/ -int BG_FindBuildDelayForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) - { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].buildDelay; - } - } - - return 0; -} - -/* -============== -BG_FindTeamForWeapon -============== -*/ -WUTeam_t BG_FindTeamForWeapon( int weapon ) -{ - int i; - - for( i = 0; i < bg_numWeapons; i++ ) + WP_PULSE_RIFLE, //int number; + PRIFLE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "prifle", //char *name; + "Pulse Rifle", //char *humanName; + "An energy weapon that fires rapid pulses of concentrated energy.", + PRIFLE_CLIPS, //int maxAmmo; + PRIFLE_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + PRIFLE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + PRIFLE_RELOAD, //int reloadTime; + PRIFLE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, { - if( bg_weapons[ i ].weaponNum == weapon ) - { - return bg_weapons[ i ].team; - } - } - - return WUT_NONE; -} - -//////////////////////////////////////////////////////////////////////////////// - -upgradeAttributes_t bg_upgrades[ ] = -{ + WP_LUCIFER_CANNON, //int number; + LCANNON_PRICE, //int price; + ( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "lcannon", //char *name; + "Lucifer Cannon", //char *humanName; + "Blaster technology scaled up to deliver devastating power. " + "Primary fire must be charged before firing. It has a quick " + "secondary attack that does not require charging.", + LCANNON_AMMO, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + LCANNON_REPEAT, //int repeatRate1; + LCANNON_SECONDARY_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + LCANNON_RELOAD, //int reloadTime; + LCANNON_K_SCALE, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, { - UP_LIGHTARMOUR, //int upgradeNum; - LIGHTARMOUR_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_TORSO|SLOT_ARMS|SLOT_LEGS, //int slots; - "larmour", //char *upgradeName; - "Light Armour", //char *upgradeHumanName; - "icons/iconu_larmour", - qtrue, //qboolean purchasable - qfalse, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_GRENADE, //int number; + GRENADE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_NONE, //int slots; + "grenade", //char *name; + "Grenade", //char *humanName; + "", + 1, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + GRENADE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + GRENADE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - UP_HELMET, //int upgradeNum; - HELMET_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_HEAD, //int slots; - "helmet", //char *upgradeName; - "Helmet", //char *upgradeHumanName; - "icons/iconu_helmet", - qtrue, //qboolean purchasable - qfalse, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_LOCKBLOB_LAUNCHER, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "lockblob", //char *name; + "Lock Blob", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + LOCKBLOB_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - UP_MEDKIT, //int upgradeNum; - MEDKIT_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_NONE, //int slots; - "medkit", //char *upgradeName; - "Medkit", //char *upgradeHumanName; - "icons/iconu_atoxin", - qfalse, //qboolean purchasable - qtrue, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_HIVE, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "hive", //char *name; + "Hive", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + HIVE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - UP_BATTPACK, //int upgradeNum; - BATTPACK_PRICE, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_BACKPACK, //int slots; - "battpack", //char *upgradeName; - "Battery Pack", //char *upgradeHumanName; - "icons/iconu_battpack", - qtrue, //qboolean purchasable - qfalse, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_TESLAGEN, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "teslagen", //char *name; + "Tesla Generator", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qtrue, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + TESLAGEN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - UP_JETPACK, //int upgradeNum; - JETPACK_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_BACKPACK, //int slots; - "jetpack", //char *upgradeName; - "Jet Pack", //char *upgradeHumanName; - "icons/iconu_jetpack", - qtrue, //qboolean purchasable - qtrue, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_MGTURRET, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "mgturret", //char *name; + "Machinegun Turret", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 0, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + MGTURRET_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; }, { - UP_BATTLESUIT, //int upgradeNum; - BSUIT_PRICE, //int price; - ( 1 << S3 ), //int stages - SLOT_HEAD|SLOT_TORSO|SLOT_ARMS|SLOT_LEGS|SLOT_BACKPACK, //int slots; - "bsuit", //char *upgradeName; - "Battlesuit", //char *upgradeHumanName; - "icons/iconu_bsuit", - qtrue, //qboolean purchasable - qfalse, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_ABUILD, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "abuild", //char *name; + "Alien build weapon", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + ABUILDER_BUILD_REPEAT, //int repeatRate1; + ABUILDER_CLAW_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + ABUILDER_CLAW_K_SCALE, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - UP_GRENADE, //int upgradeNum; - GRENADE_PRICE, //int price; - ( 1 << S2 )|( 1 << S3 ),//int stages - SLOT_NONE, //int slots; - "gren", //char *upgradeName; - "Grenade", //char *upgradeHumanName; - 0, - qtrue, //qboolean purchasable - qtrue, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_ABUILD2, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "abuildupg", //char *name; + "Alien build weapon2", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + ABUILDER_BUILD_REPEAT, //int repeatRate1; + ABUILDER_CLAW_REPEAT, //int repeatRate2; + ABUILDER_BLOB_REPEAT, //int repeatRate3; + 0, //int reloadTime; + ABUILDER_CLAW_K_SCALE, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; }, { - UP_AMMO, //int upgradeNum; - 0, //int price; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages - SLOT_NONE, //int slots; - "ammo", //char *upgradeName; - "Ammunition", //char *upgradeHumanName; - 0, - qtrue, //qboolean purchasable - qfalse, //qboolean usable - WUT_HUMANS //WUTeam_t team; + WP_HBUILD, //int number; + HBUILD_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_WEAPON, //int slots; + "ckit", //char *name; + "Construction Kit", //char *humanName; + "Used for building structures. This includes " + "spawns, power and basic defense. More structures become " + "available with new stages.", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + HBUILD_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + 0.0f, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; } }; -int bg_numUpgrades = sizeof( bg_upgrades ) / sizeof( bg_upgrades[ 0 ] ); - -/* -============== -BG_FindPriceForUpgrade -============== -*/ -int BG_FindPriceForUpgrade( int upgrade ) -{ - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - { - return bg_upgrades[ i ].price; - } - } - - return 100; -} - -/* -============== -BG_FindStagesForUpgrade -============== -*/ -qboolean BG_FindStagesForUpgrade( int upgrade, stage_t stage ) -{ - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - { - if( bg_upgrades[ i ].stages & ( 1 << stage ) ) - return qtrue; - else - return qfalse; - } - } +size_t bg_numWeapons = ARRAY_LEN( bg_weapons ); - return qfalse; -} +static const weaponAttributes_t nullWeapon = { 0 }; /* ============== -BG_FindSlotsForUpgrade +BG_WeaponByName ============== */ -int BG_FindSlotsForUpgrade( int upgrade ) +const weaponAttributes_t *BG_WeaponByName( const char *name ) { int i; - for( i = 0; i < bg_numUpgrades; i++ ) + for( i = 0; i < bg_numWeapons; i++ ) { - if( bg_upgrades[ i ].upgradeNum == upgrade ) + if( !Q_stricmp( bg_weapons[ i ].name, name ) ) { - return bg_upgrades[ i ].slots; + return &bg_weapons[ i ]; } } - return SLOT_NONE; -} - -/* -============== -BG_FindNameForUpgrade -============== -*/ -char *BG_FindNameForUpgrade( int upgrade ) -{ - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - return bg_upgrades[ i ].upgradeName; - } - - //wimp out - return 0; -} - -/* -============== -BG_FindUpgradeNumForName -============== -*/ -int BG_FindUpgradeNumForName( char *name ) -{ - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( !Q_stricmp( bg_upgrades[ i ].upgradeName, name ) ) - return bg_upgrades[ i ].upgradeNum; - } - - //wimp out - return UP_NONE; + return &nullWeapon; } /* ============== -BG_FindHumanNameForUpgrade +BG_Weapon ============== -*/ -char *BG_FindHumanNameForUpgrade( int upgrade ) -{ - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - return bg_upgrades[ i ].upgradeHumanName; - } - - //wimp out - return 0; +*/ +const weaponAttributes_t *BG_Weapon( weapon_t weapon ) +{ + return ( weapon > WP_NONE && weapon < WP_NUM_WEAPONS ) ? + &bg_weapons[ weapon - 1 ] : &nullWeapon; } /* ============== -BG_FindIconForUpgrade +BG_WeaponAllowedInStage ============== */ -char *BG_FindIconForUpgrade( int upgrade ) +qboolean BG_WeaponAllowedInStage( weapon_t weapon, stage_t stage ) { - int i; + int stages = BG_Weapon( weapon )->stages; - for( i = 0; i < bg_numUpgrades; i++ ) + return stages & ( 1 << stage ); +} + +//////////////////////////////////////////////////////////////////////////////// + +static const upgradeAttributes_t bg_upgrades[ ] = +{ + { + UP_LIGHTARMOUR, //int number; + LIGHTARMOUR_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_TORSO|SLOT_ARMS|SLOT_LEGS, //int slots; + "larmour", //char *name; + "Light Armour", //char *humanName; + "Protective armour that helps to defend against light alien melee " + "attacks.", + "icons/iconu_larmour", + qtrue, //qboolean purchasable; + qfalse, //qboolean usable; + TEAM_HUMANS //team_t team; + }, + { + UP_HELMET, //int number; + HELMET_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_HEAD, //int slots; + "helmet", //char *name; + "Helmet", //char *humanName; + "In addition to protecting your head, the helmet provides a " + "scanner indicating the presence of any friendly or hostile " + "lifeforms and structures in your immediate vicinity.", + "icons/iconu_helmet", + qtrue, //qboolean purchasable; + qfalse, //qboolean usable; + TEAM_HUMANS //team_t team; + }, + { + UP_MEDKIT, //int number; + MEDKIT_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_NONE, //int slots; + "medkit", //char *name; + "Medkit", //char *humanName; + "", + "icons/iconu_atoxin", + qfalse, //qboolean purchasable; + qtrue, //qboolean usable; + TEAM_HUMANS //team_t team; + }, + { + UP_BATTPACK, //int number; + BATTPACK_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_BACKPACK, //int slots; + "battpack", //char *name; + "Battery Pack", //char *humanName; + "Back-mounted battery pack that permits storage of one and a half " + "times the normal energy capacity for energy weapons.", + "icons/iconu_battpack", + qtrue, //qboolean purchasable; + qfalse, //qboolean usable; + TEAM_HUMANS //team_t team; + }, + { + UP_JETPACK, //int number; + JETPACK_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_BACKPACK, //int slots; + "jetpack", //char *name; + "Jet Pack", //char *humanName; + "Back-mounted jet pack that enables the user to fly to remote " + "locations. It is very useful against alien spawns in hard " + "to reach spots.", + "icons/iconu_jetpack", + qtrue, //qboolean purchasable; + qtrue, //qboolean usable; + TEAM_HUMANS //team_t team; + }, + { + UP_BATTLESUIT, //int number; + BSUIT_PRICE, //int price; + ( 1 << S3 ), //int stages; + SLOT_HEAD|SLOT_TORSO|SLOT_ARMS|SLOT_LEGS|SLOT_BACKPACK, //int slots; + "bsuit", //char *name; + "Battlesuit", //char *humanName; + "A full body armour that is highly effective at repelling alien attacks. " + "It allows the user to enter hostile situations with a greater degree " + "of confidence.", + "icons/iconu_bsuit", + qtrue, //qboolean purchasable; + qfalse, //qboolean usable; + TEAM_HUMANS //team_t team; + }, + { + UP_GRENADE, //int number; + GRENADE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_NONE, //int slots; + "gren", //char *name; + "Grenade", //char *humanName; + "A small incendinary device ideal for damaging tightly packed " + "alien structures. Has a five second timer.", + 0, + qtrue, //qboolean purchasable; + qtrue, //qboolean usable; + TEAM_HUMANS //team_t team; + }, { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - return bg_upgrades[ i ].icon; + UP_AMMO, //int number; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages; + SLOT_NONE, //int slots; + "ammo", //char *name; + "Ammunition", //char *humanName; + "Ammunition for the currently held weapon.", + 0, + qtrue, //qboolean purchasable; + qfalse, //qboolean usable; + TEAM_HUMANS //team_t team; } +}; - //wimp out - return 0; -} +size_t bg_numUpgrades = ARRAY_LEN( bg_upgrades ); + +static const upgradeAttributes_t nullUpgrade = { 0 }; /* ============== -BG_FindPurchasableForUpgrade +BG_UpgradeByName ============== */ -qboolean BG_FindPurchasableForUpgrade( int upgrade ) +const upgradeAttributes_t *BG_UpgradeByName( const char *name ) { int i; for( i = 0; i < bg_numUpgrades; i++ ) { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - return bg_upgrades[ i ].purchasable; + if( !Q_stricmp( bg_upgrades[ i ].name, name ) ) + { + return &bg_upgrades[ i ]; + } } - return qfalse; + return &nullUpgrade; } /* ============== -BG_FindUsableForUpgrade +BG_Upgrade ============== */ -qboolean BG_FindUsableForUpgrade( int upgrade ) +const upgradeAttributes_t *BG_Upgrade( upgrade_t upgrade ) { - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - return bg_upgrades[ i ].usable; - } - - return qfalse; + return ( upgrade > UP_NONE && upgrade < UP_NUM_UPGRADES ) ? + &bg_upgrades[ upgrade - 1 ] : &nullUpgrade; } /* ============== -BG_FindTeamForUpgrade +BG_UpgradeAllowedInStage ============== */ -WUTeam_t BG_FindTeamForUpgrade( int upgrade ) +qboolean BG_UpgradeAllowedInStage( upgrade_t upgrade, stage_t stage ) { - int i; - - for( i = 0; i < bg_numUpgrades; i++ ) - { - if( bg_upgrades[ i ].upgradeNum == upgrade ) - { - return bg_upgrades[ i ].team; - } - } + int stages = BG_Upgrade( upgrade )->stages; - return WUT_NONE; + return stages & ( 1 << stage ); } //////////////////////////////////////////////////////////////////////////////// @@ -4574,12 +2677,12 @@ void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t resu case TR_SINE: deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration; phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos - phase *= 0.5; + phase *= 2 * M_PI * 1000 / (float)tr->trDuration; VectorScale( tr->trDelta, phase, result ); break; case TR_LINEAR_STOP: - if( atTime > tr->trTime + tr->trDuration ) + if( atTime > tr->trTime + tr->trDuration || atTime < tr->trTime ) { VectorClear( result ); return; @@ -4643,7 +2746,7 @@ char *eventnames[ ] = "EV_FIRE_WEAPON2", "EV_FIRE_WEAPON3", - "EV_PLAYER_RESPAWN", //TA: for fovwarp effects + "EV_PLAYER_RESPAWN", // for fovwarp effects "EV_PLAYER_TELEPORT_IN", "EV_PLAYER_TELEPORT_OUT", @@ -4656,6 +2759,7 @@ char *eventnames[ ] = "EV_BULLET_HIT_WALL", "EV_SHOTGUN", + "EV_MASS_DRIVER", "EV_MISSILE_HIT", "EV_MISSILE_MISS", @@ -4664,8 +2768,8 @@ char *eventnames[ ] = "EV_BULLET", // otherEntity is the shooter "EV_LEV1_GRAB", - "EV_LEV4_CHARGE_PREPARE", - "EV_LEV4_CHARGE_START", + "EV_LEV4_TRAMPLE_PREPARE", + "EV_LEV4_TRAMPLE_START", "EV_PAIN", "EV_DEATH1", @@ -4673,13 +2777,13 @@ char *eventnames[ ] = "EV_DEATH3", "EV_OBITUARY", - "EV_GIB_PLAYER", // gib a previously living player + "EV_GIB_PLAYER", - "EV_BUILD_CONSTRUCT", //TA - "EV_BUILD_DESTROY", //TA - "EV_BUILD_DELAY", //TA: can't build yet - "EV_BUILD_REPAIR", //TA: repairing buildable - "EV_BUILD_REPAIRED", //TA: buildable has full health + "EV_BUILD_CONSTRUCT", + "EV_BUILD_DESTROY", + "EV_BUILD_DELAY", // can't build yet + "EV_BUILD_REPAIR", // repairing buildable + "EV_BUILD_REPAIRED", // buildable has full health "EV_HUMAN_BUILDABLE_EXPLOSION", "EV_ALIEN_BUILDABLE_EXPLOSION", "EV_ALIEN_ACIDTUBE", @@ -4693,15 +2797,31 @@ char *eventnames[ ] = "EV_STOPLOOPINGSOUND", "EV_TAUNT", - "EV_OVERMIND_ATTACK", //TA: overmind under attack - "EV_OVERMIND_DYING", //TA: overmind close to death - "EV_OVERMIND_SPAWNS", //TA: overmind needs spawns + "EV_OVERMIND_ATTACK", // overmind under attack + "EV_OVERMIND_DYING", // overmind close to death + "EV_OVERMIND_SPAWNS", // overmind needs spawns + + "EV_DCC_ATTACK", // dcc under attack - "EV_DCC_ATTACK", //TA: dcc under attack + "EV_MGTURRET_SPINUP", // trigger a sound - "EV_RPTUSE_SOUND" //TA: trigger a sound + "EV_RPTUSE_SOUND", // trigger a sound + "EV_LEV2_ZAP" }; +/* +=============== +BG_EventName +=============== +*/ +const char *BG_EventName( int num ) +{ + if( num < 0 || num >= ARRAY_LEN( eventnames ) ) + return "UNKNOWN"; + + return eventnames[ num ]; +} + /* =============== BG_AddPredictableEventToPlayerstate @@ -4714,19 +2834,21 @@ void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bu void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { -#ifdef _DEBUG +#ifdef DEBUG_EVENTS { char buf[ 256 ]; trap_Cvar_VariableStringBuffer( "showevents", buf, sizeof( buf ) ); if( atof( buf ) != 0 ) { -#ifdef QAGAME +#ifdef GAME Com_Printf( " game event svt %5d -> %5d: num = %20s parm %d\n", - ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ newEvent ], eventParm); + ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, + BG_EventName( newEvent ), eventParm ); #else Com_Printf( "Cgame event svt %5d -> %5d: num = %20s parm %d\n", - ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ newEvent ], eventParm); + ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, + BG_EventName( newEvent ), eventParm ); #endif } } @@ -4751,7 +2873,7 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE ) s->eType = ET_INVISIBLE; - else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + else if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) s->eType = ET_INVISIBLE; else s->eType = ET_PLAYER; @@ -4773,11 +2895,10 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean if( snap ) SnapVector( s->apos.trBase ); - //TA: i need for other things :) - //s->angles2[YAW] = ps->movementDir; s->time2 = ps->movementDir; s->legsAnim = ps->legsAnim; s->torsoAnim = ps->torsoAnim; + s->weaponAnim = ps->weaponAnim; s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number // so corpses can also reference the proper config s->eFlags = ps->eFlags; @@ -4827,12 +2948,10 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean } // use misc field to store team/class info: - s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); + s->misc = ps->stats[ STAT_TEAM ] | ( ps->stats[ STAT_CLASS ] << 8 ); - //TA: have to get the surfNormal thru somehow... + // have to get the surfNormal through somehow... VectorCopy( ps->grapplePoint, s->angles2 ); - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - s->eFlags |= EF_WALLCLIMBCEILING; s->loopSound = ps->loopSound; s->generic1 = ps->generic1; @@ -4840,7 +2959,7 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES ) s->generic1 = WPM_PRIMARY; - s->otherEntityNum = ps->otherEntityNum; + s->otherEntityNum = ps->otherEntityNum; } @@ -4858,7 +2977,7 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE ) s->eType = ET_INVISIBLE; - else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + else if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) s->eType = ET_INVISIBLE; else s->eType = ET_PLAYER; @@ -4883,11 +3002,10 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s if( snap ) SnapVector( s->apos.trBase ); - //TA: i need for other things :) - //s->angles2[YAW] = ps->movementDir; s->time2 = ps->movementDir; s->legsAnim = ps->legsAnim; s->torsoAnim = ps->torsoAnim; + s->weaponAnim = ps->weaponAnim; s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number // so corpses can also reference the proper config s->eFlags = ps->eFlags; @@ -4939,12 +3057,10 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s } // use misc field to store team/class info: - s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); + s->misc = ps->stats[ STAT_TEAM ] | ( ps->stats[ STAT_CLASS ] << 8 ); - //TA: have to get the surfNormal thru somehow... + // have to get the surfNormal through somehow... VectorCopy( ps->grapplePoint, s->angles2 ); - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - s->eFlags |= EF_WALLCLIMBCEILING; s->loopSound = ps->loopSound; s->generic1 = ps->generic1; @@ -4966,7 +3082,8 @@ qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips ) { int maxAmmo, maxClips; - BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; if( BG_InventoryContainsUpgrade( UP_BATTPACK, stats ) ) maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); @@ -4976,63 +3093,53 @@ qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips ) /* ======================== -BG_AddWeaponToInventory +BG_InventoryContainsWeapon -Give a player a weapon +Does the player hold a weapon? ======================== */ -void BG_AddWeaponToInventory( int weapon, int stats[ ] ) +qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ) { - int weaponList; + // humans always have a blaster + if( stats[ STAT_TEAM ] == TEAM_HUMANS && weapon == WP_BLASTER ) + return qtrue; - weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); - - weaponList |= ( 1 << weapon ); - - stats[ STAT_WEAPONS ] = weaponList & 0x0000FFFF; - stats[ STAT_WEAPONS2 ] = ( weaponList & 0xFFFF0000 ) >> 16; - - if( stats[ STAT_SLOTS ] & BG_FindSlotsForWeapon( weapon ) ) - Com_Printf( S_COLOR_YELLOW "WARNING: Held items conflict with weapon %d\n", weapon ); - - stats[ STAT_SLOTS ] |= BG_FindSlotsForWeapon( weapon ); + return ( stats[ STAT_WEAPON ] == weapon ); } /* ======================== -BG_RemoveWeaponToInventory +BG_SlotsForInventory -Take a weapon from a player +Calculate the slots used by an inventory and warn of conflicts ======================== */ -void BG_RemoveWeaponFromInventory( int weapon, int stats[ ] ) +int BG_SlotsForInventory( int stats[ ] ) { - int weaponList; + int i, slot, slots; - weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); - - weaponList &= ~( 1 << weapon ); - - stats[ STAT_WEAPONS ] = weaponList & 0x0000FFFF; - stats[ STAT_WEAPONS2 ] = ( weaponList & 0xFFFF0000 ) >> 16; - - stats[ STAT_SLOTS ] &= ~BG_FindSlotsForWeapon( weapon ); -} + slots = BG_Weapon( stats[ STAT_WEAPON ] )->slots; + if( stats[ STAT_TEAM ] == TEAM_HUMANS ) + slots |= BG_Weapon( WP_BLASTER )->slots; -/* -======================== -BG_InventoryContainsWeapon + for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, stats ) ) + { + slot = BG_Upgrade( i )->slots; -Does the player hold a weapon? -======================== -*/ -qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ) -{ - int weaponList; + // this check should never be true + if( slots & slot ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: held item %d conflicts with " + "inventory slot %d\n", i, slot ); + } - weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); + slots |= slot; + } + } - return( weaponList & ( 1 << weapon ) ); + return slots; } /* @@ -5045,11 +3152,6 @@ Give the player an upgrade void BG_AddUpgradeToInventory( int item, int stats[ ] ) { stats[ STAT_ITEMS ] |= ( 1 << item ); - - if( stats[ STAT_SLOTS ] & BG_FindSlotsForUpgrade( item ) ) - Com_Printf( S_COLOR_YELLOW "WARNING: Held items conflict with upgrade %d\n", item ); - - stats[ STAT_SLOTS ] |= BG_FindSlotsForUpgrade( item ); } /* @@ -5062,8 +3164,6 @@ Take an upgrade from the player void BG_RemoveUpgradeFromInventory( int item, int stats[ ] ) { stats[ STAT_ITEMS ] &= ~( 1 << item ); - - stats[ STAT_SLOTS ] &= ~BG_FindSlotsForUpgrade( item ); } /* @@ -5164,6 +3264,40 @@ qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ], return qtrue; } +/* +=============== +BG_GetClientNormal + +Get the normal for the surface the client is walking on +=============== +*/ +void BG_GetClientNormal( const playerState_t *ps, vec3_t normal ) +{ + if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + { + if( ps->eFlags & EF_WALLCLIMBCEILING ) + VectorSet( normal, 0.0f, 0.0f, -1.0f ); + else + VectorCopy( ps->grapplePoint, normal ); + } + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); +} + +/* +=============== +BG_GetClientViewOrigin + +Get the position of the client's eye, based on the client's position, the surface's normal, and client's view height +=============== +*/ +void BG_GetClientViewOrigin( const playerState_t *ps, vec3_t viewOrigin ) +{ + vec3_t normal; + BG_GetClientNormal( ps, normal ); + VectorMA( ps->origin, ps->viewheight, normal, viewOrigin ); +} + /* =============== BG_PositionBuildableRelativeToPlayer @@ -5181,19 +3315,11 @@ void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps, vec3_t angles, playerOrigin, playerNormal; float buildDist; - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( playerNormal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( ps->grapplePoint, playerNormal ); - } - else - VectorSet( playerNormal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( ps, playerNormal ); VectorCopy( ps->viewangles, angles ); VectorCopy( ps->origin, playerOrigin ); - buildDist = BG_FindBuildDistForClass( ps->stats[ STAT_PCLASS ] ); + buildDist = BG_Class( ps->stats[ STAT_CLASS ] )->buildDist; AngleVectors( angles, forward, NULL, NULL ); ProjectPointOnPlane( forward, forward, playerNormal ); @@ -5209,54 +3335,90 @@ void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps, //so buildings drop to floor VectorMA( targetOrigin, -128, playerNormal, targetOrigin ); - (*trace)( tr, entityOrigin, mins, maxs, targetOrigin, ps->clientNum, MASK_PLAYERSOLID ); - VectorCopy( tr->endpos, entityOrigin ); - VectorMA( entityOrigin, 0.1f, playerNormal, outOrigin ); + // The mask is MASK_DEADSOLID on purpose to avoid collisions with other entities + (*trace)( tr, entityOrigin, mins, maxs, targetOrigin, ps->clientNum, MASK_DEADSOLID ); + VectorCopy( tr->endpos, outOrigin ); vectoangles( forward, outAngles ); } /* =============== -BG_GetValueOfEquipment +BG_GetValueOfPlayer -Returns the equipment value of some human player's gear +Returns the credit value of a player =============== */ - int BG_GetValueOfEquipment( playerState_t *ps ) { - int i, worth = 0; +int BG_GetValueOfPlayer( playerState_t *ps ) +{ + int worth = 0; - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, ps->stats ) ) - worth += BG_FindPriceForUpgrade( i ); - } + worth = BG_Class( ps->stats[ STAT_CLASS ] )->value; - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + // Humans have worth from their equipment as well + if( ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) { - if( BG_InventoryContainsWeapon( i, ps->stats ) ) - worth += BG_FindPriceForWeapon( i ); - } + upgrade_t i; + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + worth += BG_Upgrade( i )->price; + } - return worth; + if( ps->stats[ STAT_WEAPON ] != WP_NONE ) + worth += BG_Weapon( ps->stats[ STAT_WEAPON ] )->price; } + + return worth; +} + /* -=============== -BG_GetValueOfHuman +================= +BG_PlayerCanChangeWeapon +================= +*/ +qboolean BG_PlayerCanChangeWeapon( playerState_t *ps ) +{ + // Do not allow Lucifer Cannon "canceling" via weapon switch + if( ps->weapon == WP_LUCIFER_CANNON && + ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_MIN ) + return qfalse; -Returns the kills value of some human player -=============== + return ps->weaponTime <= 0 || ps->weaponstate != WEAPON_FIRING; +} + +/* +================= +BG_PlayerPoisonCloudTime +================= */ -int BG_GetValueOfHuman( playerState_t *ps ) +int BG_PlayerPoisonCloudTime( playerState_t *ps ) { - float portion = BG_GetValueOfEquipment( ps ) / (float)HUMAN_MAXED; + int time = LEVEL1_PCLOUD_TIME; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ps->stats ) ) + time -= BSUIT_PCLOUD_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_HELMET, ps->stats ) ) + time -= HELMET_PCLOUD_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, ps->stats ) ) + time -= LIGHTARMOUR_PCLOUD_PROTECTION; + + return time; +} +/* +================= +BG_GetPlayerWeapon - if( portion < 0.01f ) - portion = 0.01f; - else if( portion > 1.0f ) - portion = 1.0f; +Returns the players current weapon or the weapon they are switching to. +Only needs to be used for human weapons. +================= +*/ +weapon_t BG_GetPlayerWeapon( playerState_t *ps ) +{ + if( ps->persistant[ PERS_NEWWEAPON ] ) + return ps->persistant[ PERS_NEWWEAPON ]; - return ceil( ALIEN_MAX_SINGLE_KILLS * portion ); + return ps->weapon; } /* @@ -5297,6 +3459,103 @@ int atoi_neg( char *token, qboolean allowNegative ) return value; } +#define MAX_NUM_PACKED_ENTITY_NUMS 10 + +/* +=============== +BG_PackEntityNumbers + +Pack entity numbers into an entityState_t +=============== +*/ +void BG_PackEntityNumbers( entityState_t *es, const int *entityNums, int count ) +{ + int i; + + if( count > MAX_NUM_PACKED_ENTITY_NUMS ) + { + count = MAX_NUM_PACKED_ENTITY_NUMS; + Com_Printf( S_COLOR_YELLOW "WARNING: A maximum of %d entity numbers can be " + "packed, but BG_PackEntityNumbers was passed %d entities", + MAX_NUM_PACKED_ENTITY_NUMS, count ); + } + + es->misc = es->time = es->time2 = es->constantLight = 0; + + for( i = 0; i < MAX_NUM_PACKED_ENTITY_NUMS; i++ ) + { + int entityNum; + + if( i < count ) + entityNum = entityNums[ i ]; + else + entityNum = ENTITYNUM_NONE; + + if( entityNum & ~GENTITYNUM_MASK ) + { + Com_Error( ERR_FATAL, "BG_PackEntityNumbers passed an entity number (%d) which " + "exceeds %d bits", entityNum, GENTITYNUM_BITS ); + } + + switch( i ) + { + case 0: es->misc |= entityNum; break; + case 1: es->time |= entityNum; break; + case 2: es->time |= entityNum << GENTITYNUM_BITS; break; + case 3: es->time |= entityNum << (GENTITYNUM_BITS * 2); break; + case 4: es->time2 |= entityNum; break; + case 5: es->time2 |= entityNum << GENTITYNUM_BITS; break; + case 6: es->time2 |= entityNum << (GENTITYNUM_BITS * 2); break; + case 7: es->constantLight |= entityNum; break; + case 8: es->constantLight |= entityNum << GENTITYNUM_BITS; break; + case 9: es->constantLight |= entityNum << (GENTITYNUM_BITS * 2); break; + default: Com_Error( ERR_FATAL, "Entity index %d not handled", i ); break; + } + } +} + +/* +=============== +BG_UnpackEntityNumbers + +Unpack entity numbers from an entityState_t +=============== +*/ +int BG_UnpackEntityNumbers( entityState_t *es, int *entityNums, int count ) +{ + int i; + + if( count > MAX_NUM_PACKED_ENTITY_NUMS ) + count = MAX_NUM_PACKED_ENTITY_NUMS; + + for( i = 0; i < count; i++ ) + { + int *entityNum = &entityNums[ i ]; + + switch( i ) + { + case 0: *entityNum = es->misc; break; + case 1: *entityNum = es->time; break; + case 2: *entityNum = (es->time >> GENTITYNUM_BITS); break; + case 3: *entityNum = (es->time >> (GENTITYNUM_BITS * 2)); break; + case 4: *entityNum = es->time2; break; + case 5: *entityNum = (es->time2 >> GENTITYNUM_BITS); break; + case 6: *entityNum = (es->time2 >> (GENTITYNUM_BITS * 2)); break; + case 7: *entityNum = es->constantLight; break; + case 8: *entityNum = (es->constantLight >> GENTITYNUM_BITS); break; + case 9: *entityNum = (es->constantLight >> (GENTITYNUM_BITS * 2)); break; + default: Com_Error( ERR_FATAL, "Entity index %d not handled", i ); break; + } + + *entityNum &= GENTITYNUM_MASK; + + if( *entityNum == ENTITYNUM_NONE ) + break; + } + + return i; +} + /* =============== BG_ParseCSVEquipmentList @@ -5330,10 +3589,10 @@ void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weapon q++; if( weaponsSize ) - weapons[ i ] = BG_FindWeaponNumForName( q ); + weapons[ i ] = BG_WeaponByName( q )->number; if( upgradesSize ) - upgrades[ j ] = BG_FindUpgradeNumForName( q ); + upgrades[ j ] = BG_UpgradeByName( q )->number; if( weaponsSize && weapons[ i ] == WP_NONE && upgradesSize && upgrades[ j ] == UP_NONE ) @@ -5367,7 +3626,7 @@ void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weapon BG_ParseCSVClassList =============== */ -void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize ) +void BG_ParseCSVClassList( const char *string, class_t *classes, int classesSize ) { char buffer[ MAX_STRING_CHARS ]; int i = 0; @@ -5378,7 +3637,7 @@ void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSiz p = q = buffer; - while( *p != '\0' ) + while( *p != '\0' && i < classesSize - 1 ) { //skip to first , or EOS while( *p != ',' && *p != '\0' ) @@ -5393,7 +3652,7 @@ void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSiz while( *q == ' ' ) q++; - classes[ i ] = BG_FindClassNumForName( q ); + classes[ i ] = BG_ClassByName( q )->number; if( classes[ i ] == PCL_NONE ) Com_Printf( S_COLOR_YELLOW "WARNING: unknown class %s\n", q ); @@ -5428,7 +3687,7 @@ void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int p = q = buffer; - while( *p != '\0' ) + while( *p != '\0' && i < buildablesSize - 1 ) { //skip to first , or EOS while( *p != ',' && *p != '\0' ) @@ -5443,7 +3702,7 @@ void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int while( *q == ' ' ) q++; - buildables[ i ] = BG_FindBuildNumForName( q ); + buildables[ i ] = BG_BuildableByName( q )->number; if( buildables[ i ] == BA_NONE ) Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable %s\n", q ); @@ -5462,38 +3721,10 @@ void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildables[ i ] = BA_NONE; } -/* -============ -BG_UpgradeClassAvailable -============ -*/ -qboolean BG_UpgradeClassAvailable( playerState_t *ps ) -{ - int i; - char buffer[ MAX_STRING_CHARS ]; - stage_t currentStage; - - trap_Cvar_VariableStringBuffer( "g_alienStage", buffer, MAX_STRING_CHARS ); - currentStage = atoi( buffer ); - - for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) - { - if( BG_ClassCanEvolveFromTo( ps->stats[ STAT_PCLASS ], i, - ps->persistant[ PERS_CREDIT ], 0 ) >= 0 && - BG_FindStagesForClass( i, currentStage ) && - BG_ClassIsAllowed( i ) ) - { - return qtrue; - } - } - - return qfalse; -} - typedef struct gameElements_s { buildable_t buildables[ BA_NUM_BUILDABLES ]; - pClass_t classes[ PCL_NUM_CLASSES ]; + class_t classes[ PCL_NUM_CLASSES ]; weapon_t weapons[ WP_NUM_WEAPONS ]; upgrade_t upgrades[ UP_NUM_UPGRADES ]; } gameElements_t; @@ -5572,7 +3803,7 @@ qboolean BG_UpgradeIsAllowed( upgrade_t upgrade ) BG_ClassIsAllowed ============ */ -qboolean BG_ClassIsAllowed( pClass_t class ) +qboolean BG_ClassIsAllowed( class_t class ) { int i; @@ -5607,81 +3838,86 @@ qboolean BG_BuildableIsAllowed( buildable_t buildable ) /* ============ -BG_ClientListTest +BG_LoadEmoticons ============ */ -qboolean BG_ClientListTest( clientList_t *list, int clientNum ) +int BG_LoadEmoticons( emoticon_t *emoticons, int num ) { - if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) - return qfalse; - if( clientNum < 32 ) - return ( ( list->lo & ( 1 << clientNum ) ) != 0 ); - else - return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 ); -} + int numFiles; + char fileList[ MAX_EMOTICONS * ( MAX_EMOTICON_NAME_LEN + 9 ) ] = {""}; + int i; + char *filePtr; + int fileLen; + int count; -/* -============ -BG_ClientListAdd -============ -*/ -void BG_ClientListAdd( clientList_t *list, int clientNum ) -{ - if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) - return; - if( clientNum < 32 ) - list->lo |= ( 1 << clientNum ); - else - list->hi |= ( 1 << ( clientNum - 32 ) ); + numFiles = trap_FS_GetFileList( "emoticons", "x1.tga", fileList, + sizeof( fileList ) ); + + if( numFiles < 1 ) + return 0; + + filePtr = fileList; + fileLen = 0; + count = 0; + for( i = 0; i < numFiles && count < num; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + if( fileLen < 9 || filePtr[ fileLen - 8 ] != '_' || + filePtr[ fileLen - 7 ] < '1' || filePtr[ fileLen - 7 ] > '9' ) + { + Com_Printf( S_COLOR_YELLOW "skipping invalidly named emoticon \"%s\"\n", + filePtr ); + continue; + } + if( fileLen - 8 > MAX_EMOTICON_NAME_LEN ) + { + Com_Printf( S_COLOR_YELLOW "emoticon file name \"%s\" too long (>%d)\n", + filePtr, MAX_EMOTICON_NAME_LEN + 8 ); + continue; + } + if( !trap_FS_FOpenFile( va( "emoticons/%s", filePtr ), NULL, FS_READ ) ) + { + Com_Printf( S_COLOR_YELLOW "could not open \"emoticons/%s\"\n", filePtr ); + continue; + } + + Q_strncpyz( emoticons[ count ].name, filePtr, fileLen - 8 + 1 ); +#ifndef GAME + emoticons[ count ].width = filePtr[ fileLen - 7 ] - '0'; +#endif + count++; + } + + Com_Printf( "Loaded %d of %d emoticons (MAX_EMOTICONS is %d)\n", + count, numFiles, MAX_EMOTICONS ); + return count; } /* ============ -BG_ClientListRemove +BG_TeamName ============ */ -void BG_ClientListRemove( clientList_t *list, int clientNum ) +char *BG_TeamName( team_t team ) { - if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) - return; - if( clientNum < 32 ) - list->lo &= ~( 1 << clientNum ); - else - list->hi &= ~( 1 << ( clientNum - 32 ) ); + if( team == TEAM_NONE ) + return "spectator"; + if( team == TEAM_ALIENS ) + return "alien"; + if( team == TEAM_HUMANS ) + return "human"; + return ""; } -/* -============ -BG_ClientListString -============ -*/ -char *BG_ClientListString( clientList_t *list ) +int cmdcmp( const void *a, const void *b ) { - static char s[ 17 ]; - - s[ 0 ] = '\0'; - if( !list ) - return s; - Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo ); - return s; + return Q_stricmp( (const char *)a, ((dummyCmd_t *)b)->name ); } -/* -============ -BG_ClientListParse -============ -*/ -void BG_ClientListParse( clientList_t *list, const char *s ) +char *G_CopyString( const char *str ) { - if( !list ) - return; - list->lo = 0; - list->hi = 0; - if( !s ) - return; - if( strlen( s ) != 16 ) - return; - sscanf( s, "%x%x", &list->hi, &list->lo ); + size_t size = strlen( str ) + 1; + char *cp = BG_Alloc( size ); + memcpy( cp, str, size ); + return cp; } - - diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c index 542b585..c5f2293 100644 --- a/src/game/bg_pmove.c +++ b/src/game/bg_pmove.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,15 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // bg_pmove.c -- both games player movement code // takes a playerstate and a usercmd as input and returns a modifed playerstate -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h" #include "bg_public.h" #include "bg_local.h" @@ -35,10 +36,8 @@ pml_t pml; float pm_stopspeed = 100.0f; float pm_duckScale = 0.25f; float pm_swimScale = 0.50f; -float pm_wadeScale = 0.70f; float pm_accelerate = 10.0f; -float pm_airaccelerate = 1.0f; float pm_wateraccelerate = 4.0f; float pm_flyaccelerate = 4.0f; @@ -92,15 +91,29 @@ void PM_AddTouchEnt( int entityNum ) PM_StartTorsoAnim =================== */ -static void PM_StartTorsoAnim( int anim ) +void PM_StartTorsoAnim( int anim ) { - if( pm->ps->pm_type >= PM_DEAD ) + if( PM_Paralyzed( pm->ps->pm_type ) ) return; pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } +/* +=================== +PM_StartWeaponAnim +=================== +*/ +static void PM_StartWeaponAnim( int anim ) +{ + if( PM_Paralyzed( pm->ps->pm_type ) ) + return; + + pm->ps->weaponAnim = ( ( pm->ps->weaponAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + /* =================== PM_StartLegsAnim @@ -108,7 +121,7 @@ PM_StartLegsAnim */ static void PM_StartLegsAnim( int anim ) { - if( pm->ps->pm_type >= PM_DEAD ) + if( PM_Paralyzed( pm->ps->pm_type ) ) return; //legsTimer is clamped too tightly for nonsegmented models @@ -168,6 +181,19 @@ static void PM_ContinueTorsoAnim( int anim ) PM_StartTorsoAnim( anim ); } +/* +=================== +PM_ContinueWeaponAnim +=================== +*/ +static void PM_ContinueWeaponAnim( int anim ) +{ + if( ( pm->ps->weaponAnim & ~ANIM_TOGGLEBIT ) == anim ) + return; + + PM_StartWeaponAnim( anim ); +} + /* =================== PM_ForceLegsAnim @@ -192,29 +218,10 @@ PM_ClipVelocity Slide off of the impacting surface ================== */ -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out ) { - float backoff; - float change; - int i; - - backoff = DotProduct( in, normal ); - - //Com_Printf( "%1.0f ", backoff ); - - if( backoff < 0 ) - backoff *= overbounce; - else - backoff /= overbounce; - - for( i = 0; i < 3; i++ ) - { - change = normal[ i ] * backoff; - //Com_Printf( "%1.0f ", change ); - out[ i ] = in[ i ] - change; - } - - //Com_Printf( " " ); + float t = -DotProduct( in, normal ); + VectorMA( in, t, normal, out ); } @@ -234,20 +241,15 @@ static void PM_Friction( void ) vel = pm->ps->velocity; - //TA: make sure vertical velocity is NOT set to zero when wall climbing + // make sure vertical velocity is NOT set to zero when wall climbing VectorCopy( vel, vec ); if( pml.walking && !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) vec[ 2 ] = 0; // ignore slope movement speed = VectorLength( vec ); - if( speed < 1 ) - { - vel[ 0 ] = 0; - vel[ 1 ] = 0; // allow sinking underwater - // FIXME: still have z friction underwater? + if( speed < 0.1 ) return; - } drop = 0; @@ -259,10 +261,11 @@ static void PM_Friction( void ) // if getting knocked back, no friction if( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) { - float stopSpeed = BG_FindStopSpeedForClass( pm->ps->stats[ STAT_PCLASS ] ); + float stopSpeed = BG_Class( pm->ps->stats[ STAT_CLASS ] )->stopSpeed; + float friction = BG_Class( pm->ps->stats[ STAT_CLASS ] )->friction; control = speed < stopSpeed ? stopSpeed : speed; - drop += control * BG_FindFrictionForClass( pm->ps->stats[ STAT_PCLASS ] ) * pml.frametime; + drop += control * friction * pml.frametime; } } } @@ -346,16 +349,43 @@ This allows the clients to use axial -127 to 127 values for all directions without getting a sqrt(2) distortion in speed. ============ */ -static float PM_CmdScale( usercmd_t *cmd ) +static float PM_CmdScale( usercmd_t *cmd, qboolean zFlight ) { int max; float total; float scale; float modifier = 1.0f; - - if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS && pm->ps->pm_type == PM_NORMAL ) + + if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS && pm->ps->pm_type == PM_NORMAL ) { - if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST ) + qboolean wasSprinting; + qboolean sprint; + wasSprinting = sprint = pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST; + + if( pm->ps->persistant[ PERS_STATE ] & PS_SPRINTTOGGLE ) + { + if( cmd->buttons & BUTTON_SPRINT && + !( pm->ps->pm_flags & PMF_SPRINTHELD ) ) + { + sprint = !sprint; + pm->ps->pm_flags |= PMF_SPRINTHELD; + } + else if( pm->ps->pm_flags & PMF_SPRINTHELD && + !( cmd->buttons & BUTTON_SPRINT ) ) + pm->ps->pm_flags &= ~PMF_SPRINTHELD; + } + else + sprint = cmd->buttons & BUTTON_SPRINT; + + if( sprint ) + pm->ps->stats[ STAT_STATE ] |= SS_SPEEDBOOST; + else if( wasSprinting && !sprint ) + pm->ps->stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; + + // Walk overrides sprint. We keep the state that we want to be sprinting + // (above), but don't apply the modifier, and in g_active we skip taking + // the stamina too. + if( sprint && !( cmd->buttons & BUTTON_WALKING ) ) modifier *= HUMAN_SPRINT_MODIFIER; else modifier *= HUMAN_JOG_MODIFIER; @@ -371,13 +401,16 @@ static float PM_CmdScale( usercmd_t *cmd ) modifier *= HUMAN_SIDE_MODIFIER; } - //must have +ve stamina to jump - if( pm->ps->stats[ STAT_STAMINA ] < 0 ) - cmd->upmove = 0; + if( !zFlight ) + { + //must have have stamina to jump + if( pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_JUMP_TAKE ) + cmd->upmove = 0; + } //slow down once stamina depletes - if( pm->ps->stats[ STAT_STAMINA ] <= -500 ) - modifier *= (float)( pm->ps->stats[ STAT_STAMINA ] + 1000 ) / 500.0f; + if( pm->ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) + modifier *= (float)( pm->ps->stats[ STAT_STAMINA ] + STAMINA_MAX ) / (float)(STAMINA_SLOW_LEVEL + STAMINA_MAX); if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED ) { @@ -387,11 +420,20 @@ static float PM_CmdScale( usercmd_t *cmd ) else modifier *= CREEP_MODIFIER; } + if( pm->ps->eFlags & EF_POISONCLOUDED ) + { + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) || + BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + modifier *= PCLOUD_ARMOUR_MODIFIER; + else + modifier *= PCLOUD_MODIFIER; + } } if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE ) - modifier *= ( 1.0f + ( pm->ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * - ( LEVEL4_CHARGE_SPEED - 1.0f ) ); + modifier *= 1.0f + ( pm->ps->stats[ STAT_MISC ] * + ( LEVEL4_TRAMPLE_SPEED - 1.0f ) / + LEVEL4_TRAMPLE_DURATION ); //slow player if charging up for a pounce if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) && @@ -405,28 +447,22 @@ static float PM_CmdScale( usercmd_t *cmd ) if( pm->ps->pm_type == PM_GRABBED ) modifier = 0.0f; - if( pm->ps->pm_type != PM_SPECTATOR && pm->ps->pm_type != PM_NOCLIP ) - { - if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f ) - cmd->upmove = 0; - - //prevent speed distortions for non ducking classes - if( !( pm->ps->pm_flags & PMF_DUCKED ) && pm->ps->pm_type != PM_JETPACK && cmd->upmove < 0 ) - cmd->upmove = 0; - } - max = abs( cmd->forwardmove ); if( abs( cmd->rightmove ) > max ) max = abs( cmd->rightmove ); + total = cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove; - if( abs( cmd->upmove ) > max ) - max = abs( cmd->upmove ); + if( zFlight ) + { + if( abs( cmd->upmove ) > max ) + max = abs( cmd->upmove ); + total += cmd->upmove * cmd->upmove; + } if( !max ) return 0; - total = sqrt( cmd->forwardmove * cmd->forwardmove - + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + total = sqrt( total ); scale = (float)pm->ps->speed * max / ( 127.0 * total ) * modifier; @@ -438,7 +474,7 @@ static float PM_CmdScale( usercmd_t *cmd ) ================ PM_SetMovementDir -Determine the rotation of the legs reletive +Determine the rotation of the legs relative to the facing dir ================ */ @@ -506,40 +542,49 @@ PM_CheckPounce */ static qboolean PM_CheckPounce( void ) { + int jumpMagnitude; + if( pm->ps->weapon != WP_ALEVEL3 && pm->ps->weapon != WP_ALEVEL3_UPG ) return qfalse; - // we were pouncing, but we've landed - if( pm->ps->groundEntityNum != ENTITYNUM_NONE - && ( pm->ps->pm_flags & PMF_CHARGE ) ) + // We were pouncing, but we've landed + if( pm->ps->groundEntityNum != ENTITYNUM_NONE && + ( pm->ps->pm_flags & PMF_CHARGE ) ) { - pm->ps->weaponTime += LEVEL3_POUNCE_TIME; pm->ps->pm_flags &= ~PMF_CHARGE; + pm->ps->weaponTime += LEVEL3_POUNCE_REPEAT; + return qfalse; } - // we're building up for a pounce + // We're building up for a pounce if( pm->cmd.buttons & BUTTON_ATTACK2 ) + { + pm->ps->pm_flags &= ~PMF_CHARGE; return qfalse; + } - // already a pounce in progress - if( pm->ps->pm_flags & PMF_CHARGE ) - return qfalse; - - if( pm->ps->stats[ STAT_MISC ] == 0 ) + // Can't start a pounce + if( ( pm->ps->pm_flags & PMF_CHARGE ) || + pm->ps->stats[ STAT_MISC ] < LEVEL3_POUNCE_TIME_MIN || + pm->ps->groundEntityNum == ENTITYNUM_NONE ) return qfalse; - pml.groundPlane = qfalse; // jumping away + // Give the player forward velocity and simulate a jump + pml.groundPlane = qfalse; pml.walking = qfalse; - pm->ps->pm_flags |= PMF_CHARGE; - pm->ps->groundEntityNum = ENTITYNUM_NONE; - - VectorMA( pm->ps->velocity, pm->ps->stats[ STAT_MISC ], pml.forward, pm->ps->velocity ); - + if( pm->ps->weapon == WP_ALEVEL3 ) + jumpMagnitude = pm->ps->stats[ STAT_MISC ] * + LEVEL3_POUNCE_JUMP_MAG / LEVEL3_POUNCE_TIME; + else + jumpMagnitude = pm->ps->stats[ STAT_MISC ] * + LEVEL3_POUNCE_JUMP_MAG_UPG / LEVEL3_POUNCE_TIME_UPG; + VectorMA( pm->ps->velocity, jumpMagnitude, pml.forward, pm->ps->velocity ); PM_AddEvent( EV_JUMP ); + // Play jumping animation if( pm->cmd.forwardmove >= 0 ) { if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) @@ -572,15 +617,47 @@ PM_CheckWallJump */ static qboolean PM_CheckWallJump( void ) { - vec3_t dir, forward, right; + vec3_t dir, forward, right, movedir, point; vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; float normalFraction = 1.5f; float cmdFraction = 1.0f; float upFraction = 1.5f; + trace_t trace; + + if( !( BG_Class( pm->ps->stats[ STAT_CLASS ] )->abilities & SCA_WALLJUMPER ) ) + return qfalse; + ProjectPointOnPlane( movedir, pml.forward, refNormal ); + VectorNormalize( movedir ); + + if( pm->cmd.forwardmove < 0 ) + VectorNegate( movedir, movedir ); + + //allow strafe transitions + if( pm->cmd.rightmove ) + { + VectorCopy( pml.right, movedir ); + + if( pm->cmd.rightmove < 0 ) + VectorNegate( movedir, movedir ); + } + + //trace into direction we are moving + VectorMA( pm->ps->origin, 0.25f, movedir, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + if( trace.fraction < 1.0f && + !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && + trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); + } + else + return qfalse; + if( pm->ps->pm_flags & PMF_RESPAWNED ) return qfalse; // don't allow jump until all buttons are up - + if( pm->cmd.upmove < 10 ) // not holding jump return qfalse; @@ -592,8 +669,6 @@ static qboolean PM_CheckWallJump( void ) if( pm->ps->pm_flags & PMF_JUMP_HELD && pm->ps->grapplePoint[ 2 ] == 1.0f ) { - // clear upmove so cmdscale doesn't lower running speed - pm->cmd.upmove = 0; return qfalse; } @@ -624,7 +699,7 @@ static qboolean PM_CheckWallJump( void ) VectorMA( dir, upFraction, refNormal, dir ); VectorNormalize( dir ); - VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ), + VectorMA( pm->ps->velocity, BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude, dir, pm->ps->velocity ); //for a long run of wall jumps the velocity can get pretty large, this caps it @@ -665,11 +740,13 @@ PM_CheckJump */ static qboolean PM_CheckJump( void ) { - if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f ) + vec3_t normal; + + if( pm->ps->groundEntityNum == ENTITYNUM_NONE ) return qfalse; - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) - return PM_CheckWallJump( ); + if( BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude == 0.0f ) + return qfalse; //can't jump and pounce at the same time if( ( pm->ps->weapon == WP_ALEVEL3 || @@ -682,8 +759,13 @@ static qboolean PM_CheckJump( void ) pm->ps->stats[ STAT_MISC ] > 0 ) return qfalse; - if( ( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) && - ( pm->ps->stats[ STAT_STAMINA ] < 0 ) ) + if( ( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) && + ( pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_JUMP_TAKE ) ) + return qfalse; + + //no bunny hopping off a dodge + if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS && + pm->ps->pm_time ) return qfalse; if( pm->ps->pm_flags & PMF_RESPAWNED ) @@ -695,42 +777,37 @@ static qboolean PM_CheckJump( void ) //can't jump whilst grabbed if( pm->ps->pm_type == PM_GRABBED ) - { - pm->cmd.upmove = 0; return qfalse; - } // must wait for jump to be released if( pm->ps->pm_flags & PMF_JUMP_HELD ) - { - // clear upmove so cmdscale doesn't lower running speed - pm->cmd.upmove = 0; return qfalse; + + //don't allow walljump for a short while after jumping from the ground + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + { + pm->ps->pm_flags |= PMF_TIME_WALLJUMP; + pm->ps->pm_time = 200; } pml.groundPlane = qfalse; // jumping away pml.walking = qfalse; pm->ps->pm_flags |= PMF_JUMP_HELD; - //TA: take some stamina off - if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) - pm->ps->stats[ STAT_STAMINA ] -= 500; + // take some stamina off + if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) + pm->ps->stats[ STAT_STAMINA ] -= STAMINA_JUMP_TAKE; pm->ps->groundEntityNum = ENTITYNUM_NONE; - //TA: jump away from wall - if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - vec3_t normal = { 0, 0, -1 }; + // jump away from wall + BG_GetClientNormal( pm->ps, normal ); + + if( pm->ps->velocity[ 2 ] < 0 ) + pm->ps->velocity[ 2 ] = 0; - if( !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) - VectorCopy( pm->ps->grapplePoint, normal ); - - VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ), - normal, pm->ps->velocity ); - } - else - pm->ps->velocity[ 2 ] = BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ); + VectorMA( pm->ps->velocity, BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude, + normal, pm->ps->velocity ); PM_AddEvent( EV_JUMP ); @@ -802,6 +879,108 @@ static qboolean PM_CheckWaterJump( void ) return qtrue; } + +/* +================== +PM_CheckDodge + +Checks the dodge key and starts a human dodge or sprint +================== +*/ +static qboolean PM_CheckDodge( void ) +{ + vec3_t right, forward, velocity = { 0.0f, 0.0f, 0.0f }; + float jump, sideModifier; + int i; + + if( pm->ps->stats[ STAT_TEAM ] != TEAM_HUMANS ) + return qfalse; + + // Landed a dodge + if( ( pm->ps->pm_flags & PMF_CHARGE ) && + pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + pm->ps->pm_flags = ( pm->ps->pm_flags & ~PMF_CHARGE ) | PMF_TIME_LAND; + pm->ps->pm_time = HUMAN_DODGE_TIMEOUT; + } + + // Reasons why we can't start a dodge or sprint + if( pm->ps->pm_type != PM_NORMAL || pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_DODGE_TAKE || + ( pm->ps->pm_flags & PMF_DUCKED ) ) + return qfalse; + + // Can't dodge forward + if( pm->cmd.forwardmove > 0 ) + return qfalse; + + // Reasons why we can't start a dodge only + if( pm->ps->pm_flags & ( PMF_TIME_LAND | PMF_CHARGE ) || + pm->ps->groundEntityNum == ENTITYNUM_NONE || + !( pm->cmd.buttons & BUTTON_DODGE ) ) + return qfalse; + + // Dodge direction specified with movement keys + if( ( !pm->cmd.rightmove && !pm->cmd.forwardmove ) || pm->cmd.upmove ) + return qfalse; + + AngleVectors( pm->ps->viewangles, NULL, right, NULL ); + forward[ 0 ] = -right[ 1 ]; + forward[ 1 ] = right[ 0 ]; + forward[ 2 ] = 0.0f; + + // Dodge magnitude is based on the jump magnitude scaled by the modifiers + jump = BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude; + if( pm->cmd.rightmove && pm->cmd.forwardmove ) + jump *= ( 0.5f * M_SQRT2 ); + + // Weaken dodge if slowed + if( ( pm->ps->stats[ STAT_STATE ] & SS_SLOWLOCKED ) || + ( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED ) || + ( pm->ps->eFlags & EF_POISONCLOUDED ) ) + sideModifier = HUMAN_DODGE_SLOWED_MODIFIER; + else + sideModifier = HUMAN_DODGE_SIDE_MODIFIER; + + // The dodge sets minimum velocity + if( pm->cmd.rightmove ) + { + if( pm->cmd.rightmove < 0 ) + VectorNegate( right, right ); + VectorMA( velocity, jump * sideModifier, right, velocity ); + } + + if( pm->cmd.forwardmove ) + { + if( pm->cmd.forwardmove < 0 ) + VectorNegate( forward, forward ); + VectorMA( velocity, jump * sideModifier, forward, velocity ); + } + + velocity[ 2 ] = jump * HUMAN_DODGE_UP_MODIFIER; + + // Make sure client has minimum velocity + for( i = 0; i < 3; i++ ) + { + if( ( velocity[ i ] < 0.0f && + pm->ps->velocity[ i ] > velocity[ i ] ) || + ( velocity[ i ] > 0.0f && + pm->ps->velocity[ i ] < velocity[ i ] ) ) + pm->ps->velocity[ i ] = velocity[ i ]; + } + + // Jumped away + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->pm_flags |= PMF_CHARGE; + pm->ps->stats[ STAT_STAMINA ] -= STAMINA_DODGE_TAKE; + pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ + ANIM_TOGGLEBIT ) | LEGS_JUMP; + PM_AddEvent( EV_JUMP ); + + return qtrue; +} + //============================================================================ @@ -863,23 +1042,15 @@ static void PM_WaterMove( void ) #endif PM_Friction( ); - scale = PM_CmdScale( &pm->cmd ); + scale = PM_CmdScale( &pm->cmd, qtrue ); // // user intentions // - if( !scale ) - { - wishvel[ 0 ] = 0; - wishvel[ 1 ] = 0; - wishvel[ 2 ] = -60; // sink towards bottom - } - else - { - for( i = 0; i < 3; i++ ) - wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; - wishvel[ 2 ] += scale * pm->cmd.upmove; - } + for( i = 0; i < 3; i++ ) + wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + + wishvel[ 2 ] += scale * pm->cmd.upmove; VectorCopy( wishvel, wishdir ); wishspeed = VectorNormalize( wishdir ); @@ -894,8 +1065,7 @@ static void PM_WaterMove( void ) { vel = VectorLength( pm->ps->velocity ); // slide along the ground plane - PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity ); VectorNormalize( pm->ps->velocity ); VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); @@ -922,7 +1092,7 @@ static void PM_JetPackMove( void ) //normal slowdown PM_Friction( ); - scale = PM_CmdScale( &pm->cmd ); + scale = PM_CmdScale( &pm->cmd, qfalse ); // user intentions for( i = 0; i < 2; i++ ) @@ -969,7 +1139,7 @@ static void PM_FlyMove( void ) // normal slowdown PM_Friction( ); - scale = PM_CmdScale( &pm->cmd ); + scale = PM_CmdScale( &pm->cmd, qtrue ); // // user intentions // @@ -1012,13 +1182,14 @@ static void PM_AirMove( void ) float scale; usercmd_t cmd; + PM_CheckWallJump( ); PM_Friction( ); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); + scale = PM_CmdScale( &cmd, qfalse ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir( ); @@ -1040,14 +1211,13 @@ static void PM_AirMove( void ) // not on ground, so little effect on velocity PM_Accelerate( wishdir, wishspeed, - BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ) ); + BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration ); // we may have a ground plane that is very steep, even // though we don't have a groundentity // slide along the steep plane if( pml.groundPlane ) - PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity ); PM_StepSlideMove( qtrue, qfalse ); } @@ -1095,14 +1265,14 @@ static void PM_ClimbMove( void ) smove = pm->cmd.rightmove; cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); + scale = PM_CmdScale( &cmd, qfalse ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir( ); // project the forward and right directions onto the ground plane - PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); - PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward ); + PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right ); // VectorNormalize( pml.forward ); VectorNormalize( pml.right ); @@ -1138,9 +1308,9 @@ static void PM_ClimbMove( void ) // when a player gets hit, they temporarily lose // full control, which allows them to be moved a bit if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) - accelerate = BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration; else - accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->acceleration; PM_Accelerate( wishdir, wishspeed, accelerate ); @@ -1150,8 +1320,7 @@ static void PM_ClimbMove( void ) vel = VectorLength( pm->ps->velocity ); // slide along the ground plane - PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity ); // don't decrease velocity when going up or down a slope VectorNormalize( pm->ps->velocity ); @@ -1189,7 +1358,6 @@ static void PM_WalkMove( void ) return; } - if( PM_CheckJump( ) || PM_CheckPounce( ) ) { // jumped away @@ -1210,7 +1378,7 @@ static void PM_WalkMove( void ) smove = pm->cmd.rightmove; cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); + scale = PM_CmdScale( &cmd, qfalse ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir( ); @@ -1220,8 +1388,8 @@ static void PM_WalkMove( void ) pml.right[ 2 ] = 0; // project the forward and right directions onto the ground plane - PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); - PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward ); + PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right ); // VectorNormalize( pml.forward ); VectorNormalize( pml.right ); @@ -1257,9 +1425,9 @@ static void PM_WalkMove( void ) // when a player gets hit, they temporarily lose // full control, which allows them to be moved a bit if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) - accelerate = BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration; else - accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->acceleration; PM_Accelerate( wishdir, wishspeed, accelerate ); @@ -1275,8 +1443,7 @@ static void PM_WalkMove( void ) } // slide along the ground plane - PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity ); // don't do anything if standing still if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] ) @@ -1307,7 +1474,7 @@ static void PM_LadderMove( void ) PM_Friction( ); - scale = PM_CmdScale( &pm->cmd ); + scale = PM_CmdScale( &pm->cmd, qtrue ); for( i = 0; i < 3; i++ ) wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; @@ -1328,8 +1495,7 @@ static void PM_LadderMove( void ) vel = VectorLength( pm->ps->velocity ); // slide along the ground plane - PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity ); VectorNormalize( pm->ps->velocity ); VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); @@ -1352,7 +1518,7 @@ static void PM_CheckLadder( void ) trace_t trace; //test if class can use ladders - if( !BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_CANUSELADDERS ) ) + if( !BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_CANUSELADDERS ) ) { pml.ladder = qfalse; return; @@ -1414,8 +1580,6 @@ static void PM_NoclipMove( void ) float wishspeed; float scale; - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - // friction speed = VectorLength( pm->ps->velocity ); @@ -1444,7 +1608,7 @@ static void PM_NoclipMove( void ) } // accelerate - scale = PM_CmdScale( &pm->cmd ); + scale = PM_CmdScale( &pm->cmd, qtrue ); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; @@ -1475,7 +1639,6 @@ Returns an event number apropriate for the groundsurface */ static int PM_FootstepForSurface( void ) { - //TA: if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED ) return EV_FOOTSTEP_SQUELCH; @@ -1543,10 +1706,6 @@ static void PM_CrashLand( void ) delta = vel + t * acc; delta = delta*delta * 0.0001; - // ducking while falling doubles damage - if( pm->ps->pm_flags & PMF_DUCKED ) - delta *= 2; - // never take falling damage if completely underwater if( pm->waterlevel == 3 ) return; @@ -1571,12 +1730,12 @@ static void PM_CrashLand( void ) if( delta > AVG_FALL_DISTANCE ) { - PM_AddEvent( EV_FALL_FAR ); + if( PM_Alive( pm->ps->pm_type ) ) + PM_AddEvent( EV_FALL_FAR ); } else if( delta > MIN_FALL_DISTANCE ) { - // this is a pain grunt, so don't play it if dead - if( pm->ps->stats[STAT_HEALTH] > 0 ) + if( PM_Alive( pm->ps->pm_type ) ) PM_AddEvent( EV_FALL_MEDIUM ); } else @@ -1688,7 +1847,7 @@ static void PM_GroundTraceMissed( void ) } } - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) ) + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_TAKESFALLDAMAGE ) ) { if( pm->ps->velocity[ 2 ] < FALLING_THRESHOLD && pml.previous_velocity[ 2 ] >= FALLING_THRESHOLD ) PM_AddEvent( EV_FALLING ); @@ -1699,7 +1858,6 @@ static void PM_GroundTraceMissed( void ) pml.walking = qfalse; } - /* ============= PM_GroundClimbTrace @@ -1707,12 +1865,13 @@ PM_GroundClimbTrace */ static void PM_GroundClimbTrace( void ) { - vec3_t surfNormal, movedir, lookdir, point; - vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; - vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; - vec3_t toAngles, surfAngles; - trace_t trace; - int i; + vec3_t surfNormal, movedir, lookdir, point; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + vec3_t toAngles, surfAngles; + trace_t trace; + int i; + const float eps = 0.000001f; //used for delta correction vec3_t traceCROSSsurf, traceCROSSref, surfCROSSref; @@ -1724,12 +1883,7 @@ static void PM_GroundClimbTrace( void ) float ldDOTtCs, d; vec3_t abc; - //TA: If we're on the ceiling then grapplePoint is a rotation normal.. otherwise its a surface normal. - // would have been nice if Carmack had left a few random variables in the ps struct for mod makers - if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorCopy( ceilingNormal, surfNormal ); - else - VectorCopy( pm->ps->grapplePoint, surfNormal ); + BG_GetClientNormal( pm->ps, surfNormal ); //construct a vector which reflects the direction the player is looking wrt the surface normal ProjectPointOnPlane( movedir, pml.forward, surfNormal ); @@ -1765,8 +1919,10 @@ static void PM_GroundClimbTrace( void ) case 1: //trace straight down anto "ground" surface + //mask out CONTENTS_BODY to not hit other players and avoid the camera flipping out when + // wallwalkers touch VectorMA( pm->ps->origin, -0.25f, surfNormal, point ); - pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask & ~CONTENTS_BODY ); break; case 2: @@ -1801,7 +1957,7 @@ static void PM_GroundClimbTrace( void ) } //if we hit something - if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && + if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && !( trace.entityNum != ENTITYNUM_WORLD && i != 4 ) ) { if( i == 2 || i == 3 ) @@ -1811,7 +1967,7 @@ static void PM_GroundClimbTrace( void ) VectorCopy( trace.endpos, pm->ps->origin ); } - + //calculate a bunch of stuff... CrossProduct( trace.plane.normal, surfNormal, traceCROSSsurf ); VectorNormalize( traceCROSSsurf ); @@ -1845,11 +2001,11 @@ static void PM_GroundClimbTrace( void ) //if the trace result and old surface normal are different then we must have transided to a new //surface... do some stuff... - if( !VectorCompare( trace.plane.normal, surfNormal ) ) + if( !VectorCompareEpsilon( trace.plane.normal, surfNormal, eps ) ) { //if the trace result or the old vector is not the floor or ceiling correct the YAW angle - if( !VectorCompare( trace.plane.normal, refNormal ) && !VectorCompare( surfNormal, refNormal ) && - !VectorCompare( trace.plane.normal, ceilingNormal ) && !VectorCompare( surfNormal, ceilingNormal ) ) + if( !VectorCompareEpsilon( trace.plane.normal, refNormal, eps ) && !VectorCompareEpsilon( surfNormal, refNormal, eps ) && + !VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) && !VectorCompareEpsilon( surfNormal, ceilingNormal, eps ) ) { //behold the evil mindfuck from hell //it has fucked mind like nothing has fucked mind before @@ -1907,16 +2063,16 @@ static void PM_GroundClimbTrace( void ) //transition from wall to ceiling //normal for subsequent viewangle rotations - if( VectorCompare( trace.plane.normal, ceilingNormal ) ) + if( VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) ) { CrossProduct( surfNormal, trace.plane.normal, pm->ps->grapplePoint ); VectorNormalize( pm->ps->grapplePoint ); - pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBINGCEILING; + pm->ps->eFlags |= EF_WALLCLIMBCEILING; } //transition from ceiling to wall //we need to do some different angle correction here cos GPISROTVEC - if( VectorCompare( surfNormal, ceilingNormal ) ) + if( VectorCompareEpsilon( surfNormal, ceilingNormal, eps ) ) { vectoangles( trace.plane.normal, toAngles ); vectoangles( pm->ps->grapplePoint, surfAngles ); @@ -1931,11 +2087,11 @@ static void PM_GroundClimbTrace( void ) pm->ps->eFlags |= EF_WALLCLIMB; //if we're not stuck to the ceiling then set grapplePoint to be a surface normal - if( !VectorCompare( trace.plane.normal, ceilingNormal ) ) + if( !VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) ) { //so we know what surface we're stuck to VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); - pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; + pm->ps->eFlags &= ~EF_WALLCLIMBCEILING; } //IMPORTANT: break out of the for loop if we've hit something @@ -1958,7 +2114,7 @@ static void PM_GroundClimbTrace( void ) pm->ps->eFlags &= ~EF_WALLCLIMB; //just transided from ceiling to floor... apply delta correction - if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) + if( pm->ps->eFlags & EF_WALLCLIMBCEILING ) { vec3_t forward, rotated, angles; @@ -1970,7 +2126,7 @@ static void PM_GroundClimbTrace( void ) pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] ); } - pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; + pm->ps->eFlags &= ~EF_WALLCLIMBCEILING; //we get very bizarre effects if we don't do this :0 VectorCopy( refNormal, pm->ps->grapplePoint ); @@ -1983,7 +2139,7 @@ static void PM_GroundClimbTrace( void ) // hitting solid ground will end a waterjump if( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) { - pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP; pm->ps->pm_time = 0; } @@ -1995,7 +2151,6 @@ static void PM_GroundClimbTrace( void ) PM_AddTouchEnt( trace.entityNum ); } - /* ============= PM_GroundTrace @@ -2004,11 +2159,10 @@ PM_GroundTrace static void PM_GroundTrace( void ) { vec3_t point; - vec3_t movedir; vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; trace_t trace; - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) ) { if( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGTOGGLE ) { @@ -2043,7 +2197,7 @@ static void PM_GroundTrace( void ) } //just transided from ceiling to floor... apply delta correction - if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) + if( pm->ps->eFlags & EF_WALLCLIMBCEILING ) { vec3_t forward, rotated, angles; @@ -2057,8 +2211,7 @@ static void PM_GroundTrace( void ) } pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; - pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; - pm->ps->eFlags &= ~EF_WALLCLIMB; + pm->ps->eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); point[ 0 ] = pm->ps->origin[ 0 ]; point[ 1 ] = pm->ps->origin[ 1 ]; @@ -2105,38 +2258,6 @@ static void PM_GroundTrace( void ) pml.groundPlane = qfalse; pml.walking = qfalse; - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) - { - ProjectPointOnPlane( movedir, pml.forward, refNormal ); - VectorNormalize( movedir ); - - if( pm->cmd.forwardmove < 0 ) - VectorNegate( movedir, movedir ); - - //allow strafe transitions - if( pm->cmd.rightmove ) - { - VectorCopy( pml.right, movedir ); - - if( pm->cmd.rightmove < 0 ) - VectorNegate( movedir, movedir ); - } - - //trace into direction we are moving - VectorMA( pm->ps->origin, 0.25f, movedir, point ); - pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); - - if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && - ( trace.entityNum == ENTITYNUM_WORLD ) ) - { - if( !VectorCompare( trace.plane.normal, pm->ps->grapplePoint ) ) - { - VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); - PM_CheckWallJump( ); - } - } - } - return; } } @@ -2193,7 +2314,7 @@ static void PM_GroundTrace( void ) // hitting solid ground will end a waterjump if( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) { - pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP; pm->ps->pm_time = 0; } @@ -2203,16 +2324,11 @@ static void PM_GroundTrace( void ) if( pm->debugLevel ) Com_Printf( "%i:Land\n", c_pmove ); - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) ) - PM_CrashLand( ); + // communicate the fall velocity to the server + pm->pmext->fallVelocity = pml.previous_velocity[ 2 ]; - // don't do landing time if we were just going down a slope - if( pml.previous_velocity[ 2 ] < -200 ) - { - // don't allow another jump for a little while - pm->ps->pm_flags |= PMF_TIME_LAND; - pm->ps->pm_time = 250; - } + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_TAKESFALLDAMAGE ) ) + PM_CrashLand( ); } pm->ps->groundEntityNum = trace.entityNum; @@ -2271,25 +2387,31 @@ static void PM_SetWaterLevel( void ) +/* +============== +PM_SetViewheight +============== +*/ +static void PM_SetViewheight( void ) +{ + pm->ps->viewheight = ( pm->ps->pm_flags & PMF_DUCKED ) + ? BG_ClassConfig( pm->ps->stats[ STAT_CLASS ] )->crouchViewheight + : BG_ClassConfig( pm->ps->stats[ STAT_CLASS ] )->viewheight; +} + /* ============== PM_CheckDuck -Sets mins, maxs, and pm->ps->viewheight +Sets mins and maxs, and calls PM_SetViewheight ============== */ static void PM_CheckDuck (void) { trace_t trace; vec3_t PCmins, PCmaxs, PCcmaxs; - int PCvh, PCcvh; - BG_FindBBoxForClass( pm->ps->stats[ STAT_PCLASS ], PCmins, PCmaxs, PCcmaxs, NULL, NULL ); - BG_FindViewheightForClass( pm->ps->stats[ STAT_PCLASS ], &PCvh, &PCcvh ); - - //TA: iD bug? you can still crouch when you're a spectator - if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) - PCcvh = PCvh; + BG_ClassBoundingBox( pm->ps->stats[ STAT_CLASS ], PCmins, PCmaxs, PCcmaxs, NULL, NULL ); pm->mins[ 0 ] = PCmins[ 0 ]; pm->mins[ 1 ] = PCmins[ 1 ]; @@ -2302,14 +2424,13 @@ static void PM_CheckDuck (void) if( pm->ps->pm_type == PM_DEAD ) { pm->maxs[ 2 ] = -8; - pm->ps->viewheight = DEAD_VIEWHEIGHT; + pm->ps->viewheight = PCmins[ 2 ] + DEAD_VIEWHEIGHT; return; } - //TA: If the standing and crouching viewheights are the same the class can't crouch - if( ( pm->cmd.upmove < 0 ) && ( PCvh != PCcvh ) && - pm->ps->pm_type != PM_JETPACK && - !BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + // If the standing and crouching bboxes are the same the class can't crouch + if( ( pm->cmd.upmove < 0 ) && !VectorCompare( PCmaxs, PCcmaxs ) && + pm->ps->pm_type != PM_JETPACK ) { // duck pm->ps->pm_flags |= PMF_DUCKED; @@ -2328,15 +2449,11 @@ static void PM_CheckDuck (void) } if( pm->ps->pm_flags & PMF_DUCKED ) - { pm->maxs[ 2 ] = PCcmaxs[ 2 ]; - pm->ps->viewheight = PCcvh; - } else - { pm->maxs[ 2 ] = PCmaxs[ 2 ]; - pm->ps->viewheight = PCvh; - } + + PM_SetViewheight( ); } @@ -2359,9 +2476,9 @@ static void PM_Footsteps( void ) // calculate speed and cycle to be used for // all cyclic walking effects // - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) ) + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) ) { - //TA: FIXME: yes yes i know this is wrong + // FIXME: yes yes i know this is wrong pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ] + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ] + pm->ps->velocity[ 2 ] * pm->ps->velocity[ 2 ] ); @@ -2519,7 +2636,7 @@ static void PM_Footsteps( void ) } } - bobmove *= BG_FindBobCycleForClass( pm->ps->stats[ STAT_PCLASS ] ); + bobmove *= BG_Class( pm->ps->stats[ STAT_CLASS ] )->bobCycle; if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST ) bobmove *= HUMAN_SPRINT_MODIFIER; @@ -2606,19 +2723,15 @@ static void PM_BeginWeaponChange( int weapon ) if( pm->ps->weaponstate == WEAPON_DROPPING ) return; - //special case to prevent storing a charged up lcannon - if( pm->ps->weapon == WP_LUCIFER_CANNON ) - pm->ps->stats[ STAT_MISC ] = 0; - // cancel a reload pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD; if( pm->ps->weaponstate == WEAPON_RELOADING ) pm->ps->weaponTime = 0; - // force this here to prevent flamer effect from continuing, among other issues - pm->ps->generic1 = WPM_NOTFIRING; + //special case to prevent storing a charged up lcannon + if( pm->ps->weapon == WP_LUCIFER_CANNON ) + pm->ps->stats[ STAT_MISC ] = 0; - PM_AddEvent( EV_CHANGE_WEAPON ); pm->ps->weaponstate = WEAPON_DROPPING; pm->ps->weaponTime += 200; pm->ps->persistant[ PERS_NEWWEAPON ] = weapon; @@ -2627,7 +2740,10 @@ static void PM_BeginWeaponChange( int weapon ) pm->ps->stats[ STAT_BUILDABLE ] = BA_NONE; if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { PM_StartTorsoAnim( TORSO_DROP ); + PM_StartWeaponAnim( WANIM_DROP ); + } } @@ -2640,6 +2756,7 @@ static void PM_FinishWeaponChange( void ) { int weapon; + PM_AddEvent( EV_CHANGE_WEAPON ); weapon = pm->ps->persistant[ PERS_NEWWEAPON ]; if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) weapon = WP_NONE; @@ -2652,7 +2769,10 @@ static void PM_FinishWeaponChange( void ) pm->ps->weaponTime += 250; if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { PM_StartTorsoAnim( TORSO_RAISE ); + PM_StartWeaponAnim( WANIM_RAISE ); + } } @@ -2664,15 +2784,17 @@ PM_TorsoAnimation */ static void PM_TorsoAnimation( void ) { - if( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) - return; - if( pm->ps->weaponstate == WEAPON_READY ) { - if( pm->ps->weapon == WP_BLASTER ) - PM_ContinueTorsoAnim( TORSO_STAND2 ); - else - PM_ContinueTorsoAnim( TORSO_STAND ); + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->weapon == WP_BLASTER ) + PM_ContinueTorsoAnim( TORSO_STAND2 ); + else + PM_ContinueTorsoAnim( TORSO_STAND ); + } + + PM_ContinueWeaponAnim( WANIM_IDLE ); } } @@ -2687,61 +2809,169 @@ Generates weapon events and modifes the weapon counter static void PM_Weapon( void ) { int addTime = 200; //default addTime - should never be used - int ammo, clips, maxClips; - qboolean attack1 = qfalse; - qboolean attack2 = qfalse; - qboolean attack3 = qfalse; + qboolean attack1 = pm->cmd.buttons & BUTTON_ATTACK; + qboolean attack2 = pm->cmd.buttons & BUTTON_ATTACK2; + qboolean attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; - // don't allow attack until all buttons are up - if( pm->ps->pm_flags & PMF_RESPAWNED ) + // Ignore weapons in some cases + if( pm->ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) return; - // ignore if spectator - if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + // Check for dead player + if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) + { + pm->ps->weapon = WP_NONE; return; + } - if( pm->ps->stats[ STAT_STATE ] & SS_INFESTING ) - return; + // Charging for a pounce or canceling a pounce + if( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) + { + int max; + + max = pm->ps->weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : + LEVEL3_POUNCE_TIME_UPG; + if( pm->cmd.buttons & BUTTON_ATTACK2 ) + pm->ps->stats[ STAT_MISC ] += pml.msec; + else + pm->ps->stats[ STAT_MISC ] -= pml.msec; - if( pm->ps->stats[ STAT_STATE ] & SS_HOVELING ) - return; + if( pm->ps->stats[ STAT_MISC ] > max ) + pm->ps->stats[ STAT_MISC ] = max; + else if( pm->ps->stats[ STAT_MISC ] < 0 ) + pm->ps->stats[ STAT_MISC ] = 0; + } - // check for dead player - if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) + // Trample charge mechanics + if( pm->ps->weapon == WP_ALEVEL4 ) { - pm->ps->weapon = WP_NONE; + // Charging up + if( !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) + { + // Charge button held + if( pm->ps->stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_TRIGGER && + ( pm->cmd.buttons & BUTTON_ATTACK2 ) ) + { + pm->ps->stats[ STAT_STATE ] &= ~SS_CHARGING; + if( pm->cmd.forwardmove > 0 ) + { + int charge = pml.msec; + vec3_t dir,vel; + + AngleVectors( pm->ps->viewangles, dir, NULL, NULL ); + VectorCopy( pm->ps->velocity, vel ); + vel[2] = 0; + dir[2] = 0; + VectorNormalize( vel ); + VectorNormalize( dir ); + + charge *= DotProduct( dir, vel ); + + pm->ps->stats[ STAT_MISC ] += charge; + } + else + pm->ps->stats[ STAT_MISC ] = 0; + } + + // Charge button released + else if( !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) + { + if( pm->ps->stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MIN ) + { + if( pm->ps->stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MAX ) + pm->ps->stats[ STAT_MISC ] = LEVEL4_TRAMPLE_CHARGE_MAX; + pm->ps->stats[ STAT_MISC ] = pm->ps->stats[ STAT_MISC ] * + LEVEL4_TRAMPLE_DURATION / + LEVEL4_TRAMPLE_CHARGE_MAX; + pm->ps->stats[ STAT_STATE ] |= SS_CHARGING; + PM_AddEvent( EV_LEV4_TRAMPLE_START ); + } + else + pm->ps->stats[ STAT_MISC ] -= pml.msec; + } + } + + // Discharging + else + { + if( pm->ps->stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_MIN ) + pm->ps->stats[ STAT_MISC ] = 0; + else + pm->ps->stats[ STAT_MISC ] -= pml.msec; + + // If the charger has stopped moving take a chunk of charge away + if( VectorLength( pm->ps->velocity ) < 64.0f || pm->cmd.rightmove ) + pm->ps->stats[ STAT_MISC ] -= LEVEL4_TRAMPLE_STOP_PENALTY * pml.msec; + } + + // Charge is over + if( pm->ps->stats[ STAT_MISC ] <= 0 || pm->cmd.forwardmove <= 0 ) + { + pm->ps->stats[ STAT_MISC ] = 0; + pm->ps->stats[ STAT_STATE ] &= ~SS_CHARGING; + } + } + + // Charging up a Lucifer Cannon + pm->ps->eFlags &= ~EF_WARN_CHARGE; + + // don't allow attack until all buttons are up + if( pm->ps->pm_flags & PMF_RESPAWNED ) return; + + if( pm->ps->weapon == WP_LUCIFER_CANNON ) + { + // Charging up + if( !pm->ps->weaponTime && + ( pm->cmd.buttons & BUTTON_ATTACK ) ) + { + pm->ps->stats[ STAT_MISC ] += pml.msec; + if( pm->ps->stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_MAX ) + pm->ps->stats[ STAT_MISC ] = LCANNON_CHARGE_TIME_MAX; + if( pm->ps->stats[ STAT_MISC ] > pm->ps->ammo * LCANNON_CHARGE_TIME_MAX / + LCANNON_CHARGE_AMMO ) + pm->ps->stats[ STAT_MISC ] = pm->ps->ammo * LCANNON_CHARGE_TIME_MAX / + LCANNON_CHARGE_AMMO; + } + + // Set overcharging flag so other players can hear the warning beep + if( pm->ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_WARN ) + pm->ps->eFlags |= EF_WARN_CHARGE; } - // no bite during pounce - if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) - && ( pm->cmd.buttons & BUTTON_ATTACK ) - && ( pm->ps->pm_flags & PMF_CHARGE ) ) - { + if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) + && ( pm->cmd.buttons & BUTTON_ATTACK ) + && ( pm->ps->pm_flags & PMF_CHARGE ) ) return; - } + // pump weapon delays (repeat times etc) if( pm->ps->weaponTime > 0 ) pm->ps->weaponTime -= pml.msec; + if( pm->ps->weaponTime < 0 ) + pm->ps->weaponTime = 0; + + // no slash during charge + if( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) + return; // check for weapon change // can't change if weapon is firing, but can change // again if lowering or raising - if( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) + if( BG_PlayerCanChangeWeapon( pm->ps ) ) { - //TA: must press use to switch weapons + // must press use to switch weapons if( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { if( !( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { - if( pm->cmd.weapon <= 32 ) + if( pm->cmd.weapon < 32 ) { //if trying to select a weapon, select it if( pm->ps->weapon != pm->cmd.weapon ) PM_BeginWeaponChange( pm->cmd.weapon ); } - else if( pm->cmd.weapon > 32 ) + else { //if trying to toggle an upgrade, toggle it if( BG_InventoryContainsUpgrade( pm->cmd.weapon - 32, pm->ps->stats ) ) //sanity check @@ -2762,7 +2992,16 @@ static void PM_Weapon( void ) if( pm->ps->pm_flags & PMF_WEAPON_SWITCH ) { pm->ps->pm_flags &= ~PMF_WEAPON_SWITCH; - PM_BeginWeaponChange( pm->ps->persistant[ PERS_NEWWEAPON ] ); + if( pm->ps->weapon != WP_NONE ) + { + // drop the current weapon + PM_BeginWeaponChange( pm->ps->persistant[ PERS_NEWWEAPON ] ); + } + else + { + // no current weapon, so just raise the new one + PM_FinishWeaponChange( ); + } } } @@ -2776,10 +3015,10 @@ static void PM_Weapon( void ) return; } + // Set proper animation if( pm->ps->weaponstate == WEAPON_RAISING ) { pm->ps->weaponstate = WEAPON_READY; - if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) { if( pm->ps->weapon == WP_BLASTER ) @@ -2788,19 +3027,21 @@ static void PM_Weapon( void ) PM_ContinueTorsoAnim( TORSO_STAND ); } + PM_ContinueWeaponAnim( WANIM_IDLE ); + return; } - // start the animation even if out of ammo - ammo = pm->ps->ammo; - clips = pm->ps->clips; - BG_FindAmmoForWeapon( pm->ps->weapon, NULL, &maxClips ); - // check for out of ammo - if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) + if( !pm->ps->ammo && !pm->ps->clips && !BG_Weapon( pm->ps->weapon )->infiniteAmmo ) { - PM_AddEvent( EV_NOAMMO ); - pm->ps->weaponTime += 200; + if( attack1 || + ( BG_Weapon( pm->ps->weapon )->hasAltMode && attack2 ) || + ( BG_Weapon( pm->ps->weapon )->hasThirdMode && attack3 ) ) + { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + } if( pm->ps->weaponstate == WEAPON_FIRING ) pm->ps->weaponstate = WEAPON_READY; @@ -2811,18 +3052,12 @@ static void PM_Weapon( void ) //done reloading so give em some ammo if( pm->ps->weaponstate == WEAPON_RELOADING ) { - if( maxClips > 0 ) - { - clips--; - BG_FindAmmoForWeapon( pm->ps->weapon, &ammo, NULL ); - } + pm->ps->clips--; + pm->ps->ammo = BG_Weapon( pm->ps->weapon )->maxAmmo; - if( BG_FindUsesEnergyForWeapon( pm->ps->weapon ) && + if( BG_Weapon( pm->ps->weapon )->usesEnergy && BG_InventoryContainsUpgrade( UP_BATTPACK, pm->ps->stats ) ) - ammo = (int)( (float)ammo * BATTPACK_MODIFIER ); - - pm->ps->ammo = ammo; - pm->ps->clips = clips; + pm->ps->ammo *= BATTPACK_MODIFIER; //allow some time for the weapon to be raised pm->ps->weaponstate = WEAPON_RAISING; @@ -2832,19 +3067,18 @@ static void PM_Weapon( void ) } // check for end of clip - if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) && - ( !ammo || pm->ps->pm_flags & PMF_WEAPON_RELOAD ) && clips ) + if( !BG_Weapon( pm->ps->weapon )->infiniteAmmo && + ( pm->ps->ammo <= 0 || ( pm->ps->pm_flags & PMF_WEAPON_RELOAD ) ) && + pm->ps->clips > 0 ) { pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD; - pm->ps->weaponstate = WEAPON_RELOADING; //drop the weapon PM_StartTorsoAnim( TORSO_DROP ); + PM_StartWeaponAnim( WANIM_RELOAD ); - addTime = BG_FindReloadTimeForWeapon( pm->ps->weapon ); - - pm->ps->weaponTime += addTime; + pm->ps->weaponTime += BG_Weapon( pm->ps->weapon )->reloadTime; return; } @@ -2853,62 +3087,60 @@ static void PM_Weapon( void ) { case WP_ALEVEL0: //venom is only autohit - attack1 = attack2 = attack3 = qfalse; - - if( !pm->autoWeaponHit[ pm->ps->weapon ] ) - { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - break; + return; case WP_ALEVEL3: case WP_ALEVEL3_UPG: //pouncing has primary secondary AND autohit procedures - attack1 = pm->cmd.buttons & BUTTON_ATTACK; - attack2 = pm->cmd.buttons & BUTTON_ATTACK2; - attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; - - if( !pm->autoWeaponHit[ pm->ps->weapon ] && !attack1 && !attack2 && !attack3 ) - { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; + // pounce is autohit + if( !attack1 && !attack2 && !attack3 ) return; - } break; case WP_LUCIFER_CANNON: - attack1 = pm->cmd.buttons & BUTTON_ATTACK; - attack2 = pm->cmd.buttons & BUTTON_ATTACK2; - attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; + attack3 = qfalse; - if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 && !attack3 ) + // Can't fire secondary while primary is charging + if( attack1 || pm->ps->stats[ STAT_MISC ] > 0 ) + attack2 = qfalse; + + if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 ) { - if( pm->ps->stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE ) + pm->ps->weaponTime = 0; + + // Charging + if( pm->ps->stats[ STAT_MISC ] < LCANNON_CHARGE_TIME_MAX ) { - pm->ps->weaponTime = 0; pm->ps->weaponstate = WEAPON_READY; return; } - else - attack1 = !attack1; } - //erp this looks confusing - if( pm->ps->stats[ STAT_MISC ] > LCANNON_MIN_CHARGE ) - attack1 = !attack1; + if( pm->ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_MIN ) + { + // Fire primary attack + attack1 = qtrue; + attack2 = qfalse; + } else if( pm->ps->stats[ STAT_MISC ] > 0 ) { + // Not enough charge pm->ps->stats[ STAT_MISC ] = 0; pm->ps->weaponTime = 0; pm->ps->weaponstate = WEAPON_READY; return; } + else if( !attack2 ) + { + // Idle + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } break; case WP_MASS_DRIVER: - attack1 = pm->cmd.buttons & BUTTON_ATTACK; + attack2 = attack3 = qfalse; // attack2 is handled on the client for zooming (cg_view.c) if( !attack1 ) @@ -2920,11 +3152,6 @@ static void PM_Weapon( void ) break; default: - //by default primary and secondary attacks are allowed - attack1 = pm->cmd.buttons & BUTTON_ATTACK; - attack2 = pm->cmd.buttons & BUTTON_ATTACK2; - attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; - if( !attack1 && !attack2 && !attack3 ) { pm->ps->weaponTime = 0; @@ -2933,29 +3160,22 @@ static void PM_Weapon( void ) } break; } - - if ( pm->ps->weapon == WP_LUCIFER_CANNON && pm->ps->stats[ STAT_MISC ] > 0 && attack3 ) - { - attack1 = qtrue; - attack3 = qfalse; - } - - //TA: fire events for non auto weapons + + // fire events for non auto weapons if( attack3 ) { - if( BG_WeaponHasThirdMode( pm->ps->weapon ) ) + if( BG_Weapon( pm->ps->weapon )->hasThirdMode ) { //hacky special case for slowblob - if( pm->ps->weapon == WP_ALEVEL3_UPG && !ammo ) + if( pm->ps->weapon == WP_ALEVEL3_UPG && !pm->ps->ammo ) { - PM_AddEvent( EV_NOAMMO ); pm->ps->weaponTime += 200; return; } pm->ps->generic1 = WPM_TERTIARY; PM_AddEvent( EV_FIRE_WEAPON3 ); - addTime = BG_FindRepeatRate3ForWeapon( pm->ps->weapon ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate3; } else { @@ -2967,11 +3187,11 @@ static void PM_Weapon( void ) } else if( attack2 ) { - if( BG_WeaponHasAltMode( pm->ps->weapon ) ) + if( BG_Weapon( pm->ps->weapon )->hasAltMode ) { pm->ps->generic1 = WPM_SECONDARY; PM_AddEvent( EV_FIRE_WEAPON2 ); - addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate2; } else { @@ -2985,10 +3205,10 @@ static void PM_Weapon( void ) { pm->ps->generic1 = WPM_PRIMARY; PM_AddEvent( EV_FIRE_WEAPON ); - addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate1; } - //TA: fire events for autohit weapons + // fire events for autohit weapons if( pm->autoWeaponHit[ pm->ps->weapon ] ) { switch( pm->ps->weapon ) @@ -2996,14 +3216,14 @@ static void PM_Weapon( void ) case WP_ALEVEL0: pm->ps->generic1 = WPM_PRIMARY; PM_AddEvent( EV_FIRE_WEAPON ); - addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate1; break; case WP_ALEVEL3: case WP_ALEVEL3_UPG: pm->ps->generic1 = WPM_SECONDARY; PM_AddEvent( EV_FIRE_WEAPON2 ); - addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate2; break; default: @@ -3020,41 +3240,77 @@ static void PM_Weapon( void ) if( pm->ps->weaponstate == WEAPON_READY ) { PM_StartTorsoAnim( TORSO_ATTACK ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); } break; case WP_BLASTER: PM_StartTorsoAnim( TORSO_ATTACK2 ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); break; default: PM_StartTorsoAnim( TORSO_ATTACK ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); break; } } else { - if( pm->ps->weapon == WP_ALEVEL4 ) - { - //hack to get random attack animations - //FIXME: does pm->ps->weaponTime cycle enough? - int num = abs( pm->ps->weaponTime ) % 3; + int num = rand( ); - if( num == 0 ) - PM_ForceLegsAnim( NSPA_ATTACK1 ); - else if( num == 1 ) - PM_ForceLegsAnim( NSPA_ATTACK2 ); - else if( num == 2 ) - PM_ForceLegsAnim( NSPA_ATTACK3 ); - } - else + //FIXME: it would be nice to have these hard coded policies in + // weapon.cfg + switch( pm->ps->weapon ) { - if( attack1 ) - PM_ForceLegsAnim( NSPA_ATTACK1 ); - else if( attack2 ) - PM_ForceLegsAnim( NSPA_ATTACK2 ); - else if( attack3 ) - PM_ForceLegsAnim( NSPA_ATTACK3 ); + case WP_ALEVEL1_UPG: + case WP_ALEVEL1: + if( attack1 ) + { + num /= RAND_MAX / 6 + 1; + PM_ForceLegsAnim( NSPA_ATTACK1 ); + PM_StartWeaponAnim( WANIM_ATTACK1 + num ); + } + break; + + case WP_ALEVEL2_UPG: + if( attack2 ) + { + PM_ForceLegsAnim( NSPA_ATTACK2 ); + PM_StartWeaponAnim( WANIM_ATTACK7 ); + } + case WP_ALEVEL2: + if( attack1 ) + { + num /= RAND_MAX / 6 + 1; + PM_ForceLegsAnim( NSPA_ATTACK1 ); + PM_StartWeaponAnim( WANIM_ATTACK1 + num ); + } + break; + + case WP_ALEVEL4: + num /= RAND_MAX / 3 + 1; + PM_ForceLegsAnim( NSPA_ATTACK1 + num ); + PM_StartWeaponAnim( WANIM_ATTACK1 + num ); + break; + + default: + if( attack1 ) + { + PM_ForceLegsAnim( NSPA_ATTACK1 ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); + } + else if( attack2 ) + { + PM_ForceLegsAnim( NSPA_ATTACK2 ); + PM_StartWeaponAnim( WANIM_ATTACK2 ); + } + else if( attack3 ) + { + PM_ForceLegsAnim( NSPA_ATTACK3 ); + PM_StartWeaponAnim( WANIM_ATTACK3 ); + } + break; } pm->ps->torsoTimer = TIMER_ATTACK; @@ -3063,29 +3319,19 @@ static void PM_Weapon( void ) pm->ps->weaponstate = WEAPON_FIRING; // take an ammo away if not infinite - if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) + if( !BG_Weapon( pm->ps->weapon )->infiniteAmmo || + ( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 ) ) { - //special case for lCanon + // Special case for lcannon if( pm->ps->weapon == WP_LUCIFER_CANNON && attack1 && !attack2 ) - { - ammo -= (int)( ceil( ( (float)pm->ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE ) * 10.0f ) ); - - //stay on the safe side - if( ammo < 0 ) - ammo = 0; - } + pm->ps->ammo -= ( pm->ps->stats[ STAT_MISC ] * LCANNON_CHARGE_AMMO + + LCANNON_CHARGE_TIME_MAX - 1 ) / LCANNON_CHARGE_TIME_MAX; else - ammo--; + pm->ps->ammo--; - pm->ps->ammo = ammo; - pm->ps->clips = clips; - } - else if( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 ) - { - //special case for slowblob - ammo--; - pm->ps->ammo = ammo; - pm->ps->clips = clips; + // Stay on the safe side + if( pm->ps->ammo < 0 ) + pm->ps->ammo = 0; } //FIXME: predicted angles miss a problem?? @@ -3114,14 +3360,21 @@ PM_Animate */ static void PM_Animate( void ) { + if( PM_Paralyzed( pm->ps->pm_type ) ) + return; + if( pm->cmd.buttons & BUTTON_GESTURE ) { + if( pm->ps->tauntTimer > 0 ) + return; + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) { if( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_GESTURE ); pm->ps->torsoTimer = TIMER_GESTURE; + pm->ps->tauntTimer = TIMER_GESTURE; PM_AddEvent( EV_TAUNT ); } @@ -3132,6 +3385,7 @@ static void PM_Animate( void ) { PM_ForceLegsAnim( NSPA_GESTURE ); pm->ps->torsoTimer = TIMER_GESTURE; + pm->ps->tauntTimer = TIMER_GESTURE; PM_AddEvent( EV_TAUNT ); } @@ -3175,6 +3429,16 @@ static void PM_DropTimers( void ) if( pm->ps->torsoTimer < 0 ) pm->ps->torsoTimer = 0; } + + if( pm->ps->tauntTimer > 0 ) + { + pm->ps->tauntTimer -= pml.msec; + + if( pm->ps->tauntTimer < 0 ) + { + pm->ps->tauntTimer = 0; + } + } } @@ -3193,7 +3457,7 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) vec3_t axis[ 3 ], rotaxis[ 3 ]; vec3_t tempang; - if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION ) + if( ps->pm_type == PM_INTERMISSION ) return; // no view changes at all if( ps->pm_type != PM_SPECTATOR && ps->stats[ STAT_HEALTH ] <= 0 ) @@ -3202,7 +3466,21 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) // circularly clamp the angles with deltas for( i = 0; i < 3; i++ ) { - temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ]; + if( i == ROLL ) + { + // Guard against speed hack + temp[ i ] = ps->delta_angles[ i ]; + +#ifdef CGAME + // Assert here so that if cmd->angles[ i ] becomes non-zero + // for a legitimate reason we can tell where and why it's + // being ignored + assert( cmd->angles[ i ] == 0 ); +#endif + + } + else + temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ]; if( i == PITCH ) { @@ -3226,7 +3504,7 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) || !BG_RotateAxis( ps->grapplePoint, axis, rotaxis, qfalse, - ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) + ps->eFlags & EF_WALLCLIMBCEILING ) ) AxisCopy( axis, rotaxis ); //convert the new axis back to angles @@ -3238,7 +3516,7 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) while( tempang[ i ] > 180.0f ) tempang[ i ] -= 360.0f; - while( tempang[ i ] < 180.0f ) + while( tempang[ i ] < -180.0f ) tempang[ i ] += 360.0f; } @@ -3287,15 +3565,10 @@ void trap_SnapVector( float *v ); void PmoveSingle( pmove_t *pmove ) { - int ammo, clips; - pm = pmove; - ammo = pm->ps->ammo; - clips = pm->ps->clips; - // this counter lets us debug movement problems with a journal - // by setting a conditional breakpoint fot the previous frame + // by setting a conditional breakpoint for the previous frame c_pmove++; // clear results @@ -3311,16 +3584,10 @@ void PmoveSingle( pmove_t *pmove ) if( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) pm->cmd.buttons &= ~BUTTON_WALKING; - // set the talk balloon flag - if( pm->cmd.buttons & BUTTON_TALK ) - pm->ps->eFlags |= EF_TALK; - else - pm->ps->eFlags &= ~EF_TALK; - // set the firing flag for continuous beam weapons if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && ( pm->cmd.buttons & BUTTON_ATTACK ) && - ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) ) + ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) ) pm->ps->eFlags |= EF_FIRING; else pm->ps->eFlags &= ~EF_FIRING; @@ -3328,7 +3595,7 @@ void PmoveSingle( pmove_t *pmove ) // set the firing flag for continuous beam weapons if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && ( pm->cmd.buttons & BUTTON_ATTACK2 ) && - ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) ) + ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) ) pm->ps->eFlags |= EF_FIRING2; else pm->ps->eFlags &= ~EF_FIRING2; @@ -3336,7 +3603,7 @@ void PmoveSingle( pmove_t *pmove ) // set the firing flag for continuous beam weapons if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) && - ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) ) + ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) ) pm->ps->eFlags |= EF_FIRING3; else pm->ps->eFlags &= ~EF_FIRING3; @@ -3355,7 +3622,9 @@ void PmoveSingle( pmove_t *pmove ) pmove->cmd.buttons = BUTTON_TALK; pmove->cmd.forwardmove = 0; pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; + + if( pmove->cmd.upmove > 0 ) + pmove->cmd.upmove = 0; } // clear all pmove local vars @@ -3393,7 +3662,7 @@ void PmoveSingle( pmove_t *pmove ) else if( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; - if( pm->ps->pm_type >= PM_DEAD ) + if( PM_Paralyzed( pm->ps->pm_type ) ) { pm->cmd.forwardmove = 0; pm->cmd.rightmove = 0; @@ -3414,6 +3683,8 @@ void PmoveSingle( pmove_t *pmove ) { PM_UpdateViewAngles( pm->ps, &pm->cmd ); PM_NoclipMove( ); + PM_SetViewheight( ); + PM_Weapon( ); PM_DropTimers( ); return; } @@ -3421,7 +3692,7 @@ void PmoveSingle( pmove_t *pmove ) if( pm->ps->pm_type == PM_FREEZE) return; // no movement at all - if( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION ) + if( pm->ps->pm_type == PM_INTERMISSION ) return; // no movement at all // set watertype, and waterlevel @@ -3443,6 +3714,7 @@ void PmoveSingle( pmove_t *pmove ) PM_DeadMove( ); PM_DropTimers( ); + PM_CheckDodge( ); if( pm->ps->pm_type == PM_JETPACK ) PM_JetPackMove( ); @@ -3454,9 +3726,9 @@ void PmoveSingle( pmove_t *pmove ) PM_LadderMove( ); else if( pml.walking ) { - if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) && ( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) - PM_ClimbMove( ); //TA: walking on any surface + PM_ClimbMove( ); // walking on any surface else PM_WalkMove( ); // walking on ground } @@ -3467,7 +3739,7 @@ void PmoveSingle( pmove_t *pmove ) // set groundentity, watertype, and waterlevel PM_GroundTrace( ); - //TA: must update after every GroundTrace() - yet more clock cycles down the drain :( (14 vec rotations/frame) + // update the viewangles PM_UpdateViewAngles( pm->ps, &pm->cmd ); @@ -3530,11 +3802,7 @@ void Pmove( pmove_t *pmove ) msec = 66; } - pmove->cmd.serverTime = pmove->ps->commandTime + msec; PmoveSingle( pmove ); - - if( pmove->ps->pm_flags & PMF_JUMP_HELD ) - pmove->cmd.upmove = 20; } } diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 462bf95..f850a5d 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,19 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ +#ifndef _BG_PUBLIC_H_ +#define _BG_PUBLIC_H_ + // bg_public.h -- definitions shared by both the server game and client game modules //tremulous balance header -#include "tremulous.h" +#include "qcommon/q_shared.h" +#include "game/tremulous.h" // because games can change separately from the main system version, we need a // second version that must match between game and cgame @@ -37,7 +42,17 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MINS_Z -24 #define DEFAULT_VIEWHEIGHT 26 #define CROUCH_VIEWHEIGHT 12 -#define DEAD_VIEWHEIGHT -14 //TA: watch for mins[ 2 ] less than this causing +#define DEAD_VIEWHEIGHT 4 // height from ground + +// player teams +typedef enum +{ + TEAM_NONE, + TEAM_ALIENS, + TEAM_HUMANS, + + NUM_TEAMS +} team_t; // // config strings are a general means of communicating variable length strings @@ -45,47 +60,42 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // // CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h -#define CS_MUSIC 2 -#define CS_MESSAGE 3 // from the map worldspawn's message field -#define CS_MOTD 4 // g_motd string for server message of the day -#define CS_WARMUP 5 // server time when the match will be restarted -// 6 UNUSED -// 7 UNUSED -#define CS_VOTE_TIME 8 -#define CS_VOTE_STRING 9 -#define CS_VOTE_YES 10 -#define CS_VOTE_NO 11 - -#define CS_TEAMVOTE_TIME 12 -#define CS_TEAMVOTE_STRING 14 -#define CS_TEAMVOTE_YES 16 -#define CS_TEAMVOTE_NO 18 - -#define CS_GAME_VERSION 20 -#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level -#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two -#define CS_WINNER 23 // string indicating round winner -#define CS_SHADERSTATE 24 -#define CS_BOTINFO 25 -#define CS_CLIENTS_READY 26 //TA: following suggestion in STAT_ enum STAT_CLIENTS_READY becomes a configstring - -//TA: extra stuff: -#define CS_BUILDPOINTS 28 -#define CS_STAGES 29 -#define CS_SPAWNS 30 - -#define CS_MODELS 33 -#define CS_SOUNDS (CS_MODELS+MAX_MODELS) -#define CS_SHADERS (CS_SOUNDS+MAX_SOUNDS) -#define CS_PARTICLE_SYSTEMS (CS_SHADERS+MAX_GAME_SHADERS) -#define CS_PLAYERS (CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS) -#define CS_PRECACHES (CS_PLAYERS+MAX_CLIENTS) -#define CS_LOCATIONS (CS_PRECACHES+MAX_CLIENTS) - -#define CS_MAX (CS_LOCATIONS+MAX_LOCATIONS) - -#if (CS_MAX) > MAX_CONFIGSTRINGS -#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +enum +{ + CS_MUSIC = 2, + CS_MESSAGE, // from the map worldspawn's message field + CS_MOTD, // g_motd string for server message of the day + CS_WARMUP = 5, // server time when the match will be restarted !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + + CS_VOTE_TIME, // Vote stuff each needs NUM_TEAMS slots + CS_VOTE_STRING = CS_VOTE_TIME + NUM_TEAMS, + CS_VOTE_YES = CS_VOTE_STRING + NUM_TEAMS, + CS_VOTE_NO = CS_VOTE_YES + NUM_TEAMS, + CS_VOTE_CALLER = CS_VOTE_NO + NUM_TEAMS, + + CS_GAME_VERSION = CS_VOTE_CALLER + NUM_TEAMS, + CS_LEVEL_START_TIME, // so the timer only shows the current level + CS_INTERMISSION, // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two + CS_WINNER , // string indicating round winner + CS_SHADERSTATE, + CS_BOTINFO, + CS_CLIENTS_READY, + + CS_ALIEN_STAGES, + CS_HUMAN_STAGES, + + CS_MODELS, + CS_SOUNDS = CS_MODELS + MAX_MODELS, + CS_SHADERS = CS_SOUNDS + MAX_SOUNDS, + CS_PARTICLE_SYSTEMS = CS_SHADERS + MAX_GAME_SHADERS, + CS_PLAYERS = CS_PARTICLE_SYSTEMS + MAX_GAME_PARTICLE_SYSTEMS, + CS_LOCATIONS = CS_PLAYERS + MAX_CLIENTS, + + CS_MAX = CS_LOCATIONS + MAX_LOCATIONS +}; + +#if CS_MAX > MAX_CONFIGSTRINGS +#error overflow: CS_MAX > MAX_CONFIGSTRINGS #endif typedef enum @@ -112,39 +122,45 @@ typedef enum PM_NOCLIP, // noclip movement PM_SPECTATOR, // still run into walls PM_JETPACK, // jetpack physics - PM_GRABBED, // like dead, but for when the player is still live + PM_GRABBED, // like dead, but for when the player is still alive PM_DEAD, // no acceleration or turning, but free falling PM_FREEZE, // stuck in place with no control - PM_INTERMISSION, // no movement or status bar - PM_SPINTERMISSION // no movement or status bar + PM_INTERMISSION // no movement or status bar } pmtype_t; +// pmtype_t categories +#define PM_Paralyzed( x ) ( (x) == PM_DEAD || (x) == PM_FREEZE ||\ + (x) == PM_INTERMISSION ) +#define PM_Alive( x ) ( (x) == PM_NORMAL || (x) == PM_JETPACK ||\ + (x) == PM_GRABBED ) + typedef enum { WEAPON_READY, WEAPON_RAISING, WEAPON_DROPPING, WEAPON_FIRING, - WEAPON_RELOADING + WEAPON_RELOADING, } weaponstate_t; // pmove->pm_flags -#define PMF_DUCKED 1 -#define PMF_JUMP_HELD 2 -#define PMF_CROUCH_HELD 4 -#define PMF_BACKWARDS_JUMP 8 // go into backwards land -#define PMF_BACKWARDS_RUN 16 // coast down to backwards run -#define PMF_TIME_LAND 32 // pm_time is time before rejump -#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time -#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump -#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up -#define PMF_USE_ITEM_HELD 1024 -#define PMF_WEAPON_RELOAD 2048 //TA: force a weapon switch -#define PMF_FOLLOW 4096 // spectate following another player -#define PMF_QUEUED 8192 //TA: player is queued -#define PMF_TIME_WALLJUMP 16384 //TA: for limiting wall jumping -#define PMF_CHARGE 32768 //TA: keep track of pouncing -#define PMF_WEAPON_SWITCH 65536 //TA: force a weapon switch +#define PMF_DUCKED 0x000001 +#define PMF_JUMP_HELD 0x000002 +#define PMF_CROUCH_HELD 0x000004 +#define PMF_BACKWARDS_JUMP 0x000008 // go into backwards land +#define PMF_BACKWARDS_RUN 0x000010 // coast down to backwards run +#define PMF_TIME_LAND 0x000020 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 0x000040 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 0x000080 // pm_time is waterjump +#define PMF_RESPAWNED 0x000100 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 0x000200 +#define PMF_WEAPON_RELOAD 0x000400 // force a weapon switch +#define PMF_FOLLOW 0x000800 // spectate following another player +#define PMF_QUEUED 0x001000 // player is queued +#define PMF_TIME_WALLJUMP 0x002000 // for limiting wall jumping +#define PMF_CHARGE 0x004000 // keep track of pouncing +#define PMF_WEAPON_SWITCH 0x008000 // force a weapon switch +#define PMF_SPRINTHELD 0x010000 #define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK|PMF_TIME_WALLJUMP) @@ -152,10 +168,11 @@ typedef enum typedef struct { int pouncePayload; + float fallVelocity; } pmoveExt_t; #define MAXTOUCH 32 -typedef struct +typedef struct pmove_s { // state (in / out) playerState_t *ps; @@ -165,7 +182,7 @@ typedef struct int tracemask; // collide against these types of surfaces int debugLevel; // if set, diagnostic output will be printed qboolean noFootsteps; // if the game is setup for no footsteps by the server - qboolean autoWeaponHit[ 32 ]; //FIXME: TA: remind myself later this might be a problem + qboolean autoWeaponHit[ 32 ]; int framecount; @@ -206,49 +223,43 @@ typedef enum { STAT_HEALTH, STAT_ITEMS, - STAT_SLOTS, //TA: tracks the amount of stuff human players are carrying STAT_ACTIVEITEMS, - STAT_WEAPONS, // 16 bit fields - STAT_WEAPONS2, //TA: another 16 bits to push the max weapon count up - STAT_MAX_HEALTH, // health / armor limit, changable by handicap - STAT_PCLASS, //TA: player class (for aliens AND humans) - STAT_PTEAM, //TA: player team - STAT_STAMINA, //TA: stamina (human only) - STAT_STATE, //TA: client states e.g. wall climbing - STAT_MISC, //TA: for uh...misc stuff - STAT_BUILDABLE, //TA: which ghost model to display for building - STAT_BOOSTTIME, //TA: time left for boost (alien only) - STAT_FALLDIST, //TA: the distance the player fell - STAT_VIEWLOCK //TA: direction to lock the view in + STAT_WEAPON, // current primary weapon + STAT_MAX_HEALTH,// health / armor limit, changable by handicap + STAT_CLASS, // player class (for aliens AND humans) + STAT_TEAM, // player team + STAT_STAMINA, // stamina (human only) + STAT_STATE, // client states e.g. wall climbing + STAT_MISC, // for uh...misc stuff (pounce, trample, lcannon) + STAT_BUILDABLE, // which ghost model to display for building + STAT_FALLDIST, // the distance the player fell + STAT_VIEWLOCK // direction to lock the view in + // netcode has space for 3 more } statIndex_t; #define SCA_WALLCLIMBER 0x00000001 #define SCA_TAKESFALLDAMAGE 0x00000002 #define SCA_CANZOOM 0x00000004 -#define SCA_NOWEAPONDRIFT 0x00000008 -#define SCA_FOVWARPS 0x00000010 -#define SCA_ALIENSENSE 0x00000020 -#define SCA_CANUSELADDERS 0x00000040 -#define SCA_WALLJUMPER 0x00000080 +#define SCA_FOVWARPS 0x00000008 +#define SCA_ALIENSENSE 0x00000010 +#define SCA_CANUSELADDERS 0x00000020 +#define SCA_WALLJUMPER 0x00000040 #define SS_WALLCLIMBING 0x00000001 -#define SS_WALLCLIMBINGCEILING 0x00000002 -#define SS_CREEPSLOWED 0x00000004 -#define SS_SPEEDBOOST 0x00000008 -#define SS_INFESTING 0x00000010 -#define SS_GRABBED 0x00000020 -#define SS_BLOBLOCKED 0x00000040 -#define SS_POISONED 0x00000080 -#define SS_HOVELING 0x00000100 -#define SS_BOOSTED 0x00000200 -#define SS_SLOWLOCKED 0x00000400 -#define SS_POISONCLOUDED 0x00000800 -#define SS_MEDKIT_ACTIVE 0x00001000 -#define SS_CHARGING 0x00002000 - -#define SB_VALID_TOGGLEBIT 0x00004000 - -#define MAX_STAMINA 1000 +#define SS_CREEPSLOWED 0x00000002 +#define SS_SPEEDBOOST 0x00000004 +#define SS_GRABBED 0x00000008 +#define SS_BLOBLOCKED 0x00000010 +#define SS_POISONED 0x00000020 +#define SS_BOOSTED 0x00000040 +#define SS_BOOSTEDWARNING 0x00000080 // booster poison is running out +#define SS_SLOWLOCKED 0x00000100 +#define SS_CHARGING 0x00000200 +#define SS_HEALING_ACTIVE 0x00000400 // medistat for humans, creep for aliens +#define SS_HEALING_2X 0x00000800 // medkit or double healing rate +#define SS_HEALING_3X 0x00001000 // triple healing rate + +#define SB_VALID_TOGGLEBIT 0x00002000 // player_state->persistant[] indexes // these fields are the only part of player_state that isn't @@ -257,63 +268,53 @@ typedef enum { PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! PERS_HITS, // total points damage inflicted so damage beeps can sound on change - PERS_UNUSED, // used to be PERS_RANK, no longer used - PERS_TEAM, + PERS_SPAWNS, // how many spawns your team has + PERS_SPECSTATE, PERS_SPAWN_COUNT, // incremented every respawn PERS_ATTACKER, // clientnum of last damage inflicter PERS_KILLED, // count of the number of times you died - //TA: PERS_STATE, PERS_CREDIT, // human credit - PERS_BANK, // human credit in the bank PERS_QUEUEPOS, // position in the spawn queue - PERS_NEWWEAPON // weapon to switch to + PERS_NEWWEAPON, // weapon to switch to + PERS_BP, + PERS_MARKEDBP + // netcode has space for 3 more } persEnum_t; #define PS_WALLCLIMBINGFOLLOW 0x00000001 #define PS_WALLCLIMBINGTOGGLE 0x00000002 #define PS_NONSEGMODEL 0x00000004 +#define PS_SPRINTTOGGLE 0x00000008 // entityState_t->eFlags -#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD -#define EF_TELEPORT_BIT 0x00000002 // toggled every time the origin abruptly changes -#define EF_PLAYER_EVENT 0x00000004 -#define EF_BOUNCE 0x00000008 // for missiles -#define EF_BOUNCE_HALF 0x00000010 // for missiles -#define EF_NO_BOUNCE_SOUND 0x00000020 // for missiles -#define EF_WALLCLIMB 0x00000040 // TA: wall walking -#define EF_WALLCLIMBCEILING 0x00000080 // TA: wall walking ceiling hack -#define EF_NODRAW 0x00000100 // may have an event, but no model (unspawned items) -#define EF_FIRING 0x00000200 // for lightning gun -#define EF_FIRING2 0x00000400 // alt fire -#define EF_FIRING3 0x00000800 // third fire -#define EF_MOVER_STOP 0x00001000 // will push otherwise -#define EF_TALK 0x00002000 // draw a talk balloon -#define EF_CONNECTION 0x00004000 // draw a connection trouble sprite -#define EF_VOTED 0x00008000 // already cast a vote -#define EF_TEAMVOTED 0x00010000 // already cast a vote -#define EF_BLOBLOCKED 0x00020000 // TA: caught by a trapper -#define EF_REAL_LIGHT 0x00040000 // TA: light sprites according to ambient light -#define EF_DBUILDER 0x00080000 // designated builder protection - -typedef enum -{ - PW_NONE, - - PW_QUAD, - PW_BATTLESUIT, - PW_HASTE, - PW_INVIS, - PW_REGEN, - PW_FLIGHT, - - PW_REDFLAG, - PW_BLUEFLAG, - PW_BALL, - - PW_NUM_POWERUPS -} powerup_t; +// notice that some flags are overlapped, so their meaning depends on context +#define EF_DEAD 0x0001 // don't draw a foe marker over players with EF_DEAD +#define EF_TELEPORT_BIT 0x0002 // toggled every time the origin abruptly changes +#define EF_PLAYER_EVENT 0x0004 // only used for eType > ET_EVENTS + +// for missiles: +#define EF_BOUNCE 0x0008 // for missiles +#define EF_BOUNCE_HALF 0x0010 // for missiles +#define EF_NO_BOUNCE_SOUND 0x0020 // for missiles + +// buildable flags: +#define EF_B_SPAWNED 0x0008 +#define EF_B_POWERED 0x0010 +#define EF_B_MARKED 0x0020 + +#define EF_WARN_CHARGE 0x0020 // Lucifer Cannon is about to overcharge +#define EF_WALLCLIMB 0x0040 // wall walking +#define EF_WALLCLIMBCEILING 0x0080 // wall walking ceiling hack +#define EF_NODRAW 0x0100 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x0200 // for lightning gun +#define EF_FIRING2 0x0400 // alt fire +#define EF_FIRING3 0x0800 // third fire +#define EF_MOVER_STOP 0x1000 // will push otherwise +#define EF_POISONCLOUDED 0x2000 // player hit with basilisk gas +#define EF_CONNECTION 0x4000 // draw a connection trouble sprite +#define EF_BLOBLOCKED 0x8000 // caught by a trapper typedef enum { @@ -358,8 +359,8 @@ typedef enum WP_LAS_GUN, WP_MASS_DRIVER, WP_CHAINGUN, - WP_PULSE_RIFLE, WP_FLAMER, + WP_PULSE_RIFLE, WP_LUCIFER_CANNON, WP_GRENADE, @@ -371,7 +372,6 @@ typedef enum //build weapons must remain in a block WP_ABUILD, WP_ABUILD2, - WP_HBUILD2, WP_HBUILD, //ok? @@ -395,17 +395,7 @@ typedef enum UP_NUM_UPGRADES } upgrade_t; -typedef enum -{ - WUT_NONE, - - WUT_ALIENS, - WUT_HUMANS, - - WUT_NUM_TEAMS -} WUTeam_t; - -//TA: bitmasks for upgrade slots +// bitmasks for upgrade slots #define SLOT_NONE 0x00000000 #define SLOT_HEAD 0x00000001 #define SLOT_TORSO 0x00000002 @@ -428,8 +418,6 @@ typedef enum BA_A_BOOSTER, BA_A_HIVE, - BA_A_HOVEL, - BA_H_SPAWN, BA_H_MGTURRET, @@ -447,30 +435,13 @@ typedef enum typedef enum { - BIT_NONE, - - BIT_ALIENS, - BIT_HUMANS, - - BIT_NUM_TEAMS -} buildableTeam_t; - -#define B_HEALTH_BITS 5 -#define B_HEALTH_MASK ((1<persistant[PERS_PLAYEREVENTS]) -#define PLAYEREVENT_DENIEDREWARD 0x0001 -#define PLAYEREVENT_GAUNTLETREWARD 0x0002 -#define PLAYEREVENT_HOLYSHIT 0x0004 + RMT_SPHERE, + RMT_SPHERICAL_CONE_64, + RMT_SPHERICAL_CONE_240 +} rangeMarkerType_t; // entityState_t->event values -// entity events are for effects that take place reletive +// entity events are for effects that take place relative // to an existing entities origin. Very network efficient. // two bits at the top of the entityState->event field @@ -484,6 +455,8 @@ typedef enum #define EVENT_VALID_MSEC 300 +const char *BG_EventName( int num ); + typedef enum { EV_NONE, @@ -522,7 +495,7 @@ typedef enum EV_FIRE_WEAPON2, EV_FIRE_WEAPON3, - EV_PLAYER_RESPAWN, //TA: for fovwarp effects + EV_PLAYER_RESPAWN, // for fovwarp effects EV_PLAYER_TELEPORT_IN, EV_PLAYER_TELEPORT_OUT, @@ -535,6 +508,7 @@ typedef enum EV_BULLET_HIT_WALL, EV_SHOTGUN, + EV_MASS_DRIVER, EV_MISSILE_HIT, EV_MISSILE_MISS, @@ -543,8 +517,8 @@ typedef enum EV_BULLET, // otherEntity is the shooter EV_LEV1_GRAB, - EV_LEV4_CHARGE_PREPARE, - EV_LEV4_CHARGE_START, + EV_LEV4_TRAMPLE_PREPARE, + EV_LEV4_TRAMPLE_START, EV_PAIN, EV_DEATH1, @@ -552,13 +526,13 @@ typedef enum EV_DEATH3, EV_OBITUARY, - EV_GIB_PLAYER, // gib a previously living player + EV_GIB_PLAYER, - EV_BUILD_CONSTRUCT, //TA - EV_BUILD_DESTROY, //TA - EV_BUILD_DELAY, //TA: can't build yet - EV_BUILD_REPAIR, //TA: repairing buildable - EV_BUILD_REPAIRED, //TA: buildable has full health + EV_BUILD_CONSTRUCT, + EV_BUILD_DESTROY, + EV_BUILD_DELAY, // can't build yet + EV_BUILD_REPAIR, // repairing buildable + EV_BUILD_REPAIRED, // buildable has full health EV_HUMAN_BUILDABLE_EXPLOSION, EV_ALIEN_BUILDABLE_EXPLOSION, EV_ALIEN_ACIDTUBE, @@ -572,73 +546,91 @@ typedef enum EV_STOPLOOPINGSOUND, EV_TAUNT, - EV_OVERMIND_ATTACK, //TA: overmind under attack - EV_OVERMIND_DYING, //TA: overmind close to death - EV_OVERMIND_SPAWNS, //TA: overmind needs spawns + EV_OVERMIND_ATTACK, // overmind under attack + EV_OVERMIND_DYING, // overmind close to death + EV_OVERMIND_SPAWNS, // overmind needs spawns + + EV_DCC_ATTACK, // dcc under attack - EV_DCC_ATTACK, //TA: dcc under attack + EV_MGTURRET_SPINUP, // turret spinup sound should play - EV_RPTUSE_SOUND //TA: trigger a sound + EV_RPTUSE_SOUND, // trigger a sound + EV_LEV2_ZAP } entity_event_t; typedef enum { + MN_NONE, + MN_TEAM, MN_A_TEAMFULL, MN_H_TEAMFULL, + MN_A_TEAMLOCKED, + MN_H_TEAMLOCKED, + MN_PLAYERLIMIT, + + // cmd stuff + MN_CMD_CHEAT, + MN_CMD_CHEAT_TEAM, + MN_CMD_TEAM, + MN_CMD_SPEC, + MN_CMD_ALIEN, + MN_CMD_HUMAN, + MN_CMD_ALIVE, //alien stuff MN_A_CLASS, MN_A_BUILD, MN_A_INFEST, - MN_A_HOVEL_OCCUPIED, - MN_A_HOVEL_BLOCKED, MN_A_NOEROOM, MN_A_TOOCLOSE, MN_A_NOOVMND_EVOLVE, + MN_A_EVOLVEBUILDTIMER, + MN_A_CANTEVOLVE, + MN_A_EVOLVEWALLWALK, + MN_A_UNKNOWNCLASS, + MN_A_CLASSNOTSPAWN, + MN_A_CLASSNOTALLOWED, + MN_A_CLASSNOTATSTAGE, + + //shared build + MN_B_NOROOM, + MN_B_NORMAL, + MN_B_CANNOT, + MN_B_LASTSPAWN, + MN_B_SUDDENDEATH, + MN_B_REVOKED, + MN_B_SURRENDER, //alien build - MN_A_SPWNWARN, - MN_A_OVERMIND, - MN_A_NOASSERT, + MN_A_ONEOVERMIND, + MN_A_NOBP, MN_A_NOCREEP, MN_A_NOOVMND, - MN_A_NOROOM, - MN_A_NORMAL, - MN_A_HOVEL, - MN_A_HOVEL_EXIT, //human stuff MN_H_SPAWN, MN_H_BUILD, MN_H_ARMOURY, + MN_H_UNKNOWNITEM, MN_H_NOSLOTS, MN_H_NOFUNDS, MN_H_ITEMHELD, + MN_H_NOARMOURYHERE, + MN_H_NOENERGYAMMOHERE, + MN_H_NOROOMBSUITON, + MN_H_NOROOMBSUITOFF, + MN_H_ARMOURYBUILDTIMER, + MN_H_DEADTOCLASS, + MN_H_UNKNOWNSPAWNITEM, //human build - MN_H_REPEATER, - MN_H_NOPOWER, + MN_H_NOPOWERHERE, + MN_H_NOBP, MN_H_NOTPOWERED, MN_H_NODCC, - MN_H_REACTOR, - MN_H_NOROOM, - MN_H_NORMAL, - MN_H_TNODEWARN, - MN_H_RPTWARN, - MN_H_RPTWARN2, - - //not used - MN_A_TEAMCHANGEBUILDTIMER, - MN_H_TEAMCHANGEBUILDTIMER, - - MN_A_EVOLVEBUILDTIMER, - - MN_H_NOENERGYAMMOHERE, - MN_H_NOARMOURYHERE, - MN_H_NOROOMBSUITON, - MN_H_NOROOMBSUITOFF, - MN_H_ARMOURYBUILDTIMER + MN_H_ONEREACTOR, + MN_H_RPTPOWERHERE, } dynMenu_t; // animations @@ -744,7 +736,7 @@ typedef enum MAX_NONSEG_PLAYER_TOTALANIMATIONS } nonSegPlayerAnimNumber_t; -//TA: for buildable animations +// for buildable animations typedef enum { BANIM_NONE, @@ -772,6 +764,28 @@ typedef enum MAX_BUILDABLE_ANIMATIONS } buildableAnimNumber_t; +typedef enum +{ + WANIM_NONE, + + WANIM_IDLE, + + WANIM_DROP, + WANIM_RELOAD, + WANIM_RAISE, + + WANIM_ATTACK1, + WANIM_ATTACK2, + WANIM_ATTACK3, + WANIM_ATTACK4, + WANIM_ATTACK5, + WANIM_ATTACK6, + WANIM_ATTACK7, + WANIM_ATTACK8, + + MAX_WEAPON_ANIMATIONS +} weaponAnimNumber_t; + typedef struct animation_s { int firstFrame; @@ -789,22 +803,10 @@ typedef struct animation_s #define ANIM_TOGGLEBIT 0x80 #define ANIM_FORCEBIT 0x40 - -typedef enum -{ - TEAM_FREE, - TEAM_SPECTATOR, - - TEAM_NUM_TEAMS -} team_t; - // Time between location updates -#define TEAM_LOCATION_UPDATE_TIME 1000 +#define TEAM_LOCATION_UPDATE_TIME 500 -// How many players on the overlay -#define TEAM_MAXOVERLAY 32 - -//TA: player classes +// player classes typedef enum { PCL_NONE, @@ -828,19 +830,30 @@ typedef enum PCL_HUMAN_BSUIT, PCL_NUM_CLASSES -} pClass_t; - +} class_t; -//TA: player teams +// spectator state typedef enum { - PTE_NONE, - PTE_ALIENS, - PTE_HUMANS, - - PTE_NUM_TEAMS -} pTeam_t; - + SPECTATOR_NOT, + SPECTATOR_FREE, + SPECTATOR_LOCKED, + SPECTATOR_FOLLOW, + SPECTATOR_SCOREBOARD +} spectatorState_t; + +// modes of text communication +typedef enum +{ + SAY_ALL, + SAY_TEAM, + SAY_PRIVMSG, + SAY_TPRIVMSG, + SAY_AREA, + SAY_ADMINS, + SAY_ADMINS_PUBLIC, + SAY_RAW +} saymode_t; // means of death typedef enum @@ -879,7 +892,8 @@ typedef enum MOD_LEVEL2_CLAW, MOD_LEVEL2_ZAP, MOD_LEVEL4_CLAW, - MOD_LEVEL4_CHARGE, + MOD_LEVEL4_TRAMPLE, + MOD_LEVEL4_CRUSH, MOD_SLOWBLOB, MOD_POISON, @@ -893,42 +907,27 @@ typedef enum MOD_ASPAWN, MOD_ATUBE, MOD_OVERMIND, - MOD_SLAP + MOD_DECONSTRUCT, + MOD_REPLACE, + MOD_NOCREEP } meansOfDeath_t; //--------------------------------------------------------- -//TA: player class record +// player class record typedef struct { - int classNum; + class_t number; - char *className; - char *humanName; - - char *modelName; - float modelScale; - char *skinName; - float shadowScale; - - char *hudName; + char *name; + char *info; int stages; - vec3_t mins; - vec3_t maxs; - vec3_t crouchMaxs; - vec3_t deadMins; - vec3_t deadMaxs; - float zOffset; - - int viewheight; - int crouchViewheight; - int health; float fallDamage; - int regenRate; + float regenRate; int abilities; @@ -939,6 +938,7 @@ typedef struct int fov; float bob; float bobCycle; + float landBob; int steptime; float speed; @@ -971,7 +971,8 @@ typedef struct int viewheight; int crouchViewheight; float zOffset; -} classAttributeOverrides_t; + vec3_t shoulderOffsets; +} classConfig_t; //stages typedef enum @@ -983,59 +984,54 @@ typedef enum #define MAX_BUILDABLE_MODELS 4 -//TA: buildable item record +// buildable item record typedef struct { - int buildNum; + buildable_t number; - char *buildName; - char *humanName; - char *entityName; + char *name; + char *humanName; + char *info; + char *entityName; - char *models[ MAX_BUILDABLE_MODELS ]; - float modelScale; + trType_t traj; + float bounce; - vec3_t mins; - vec3_t maxs; - float zOffset; + int buildPoints; + int stages; - trType_t traj; - float bounce; + int health; + int regenRate; - int buildPoints; - int stages; + int splashDamage; + int splashRadius; - int health; - int regenRate; - - int splashDamage; - int splashRadius; + int meansOfDeath; - int meansOfDeath; + team_t team; + weapon_t buildWeapon; - int team; - weapon_t buildWeapon; + int idleAnim; - int idleAnim; + int nextthink; + int buildTime; + qboolean usable; - int nextthink; - int buildTime; - qboolean usable; + int turretRange; + int turretFireSpeed; + weapon_t turretProjType; - int turretRange; - int turretFireSpeed; - weapon_t turretProjType; + float minNormal; + qboolean invertNormal; - float minNormal; - qboolean invertNormal; + qboolean creepTest; + int creepSize; - qboolean creepTest; - int creepSize; - - qboolean dccTest; - qboolean transparentTest; - qboolean reactorTest; - qboolean replaceable; + qboolean dccTest; + qboolean transparentTest; + qboolean uniqueTest; + + int value; } buildableAttributes_t; typedef struct @@ -1046,20 +1042,21 @@ typedef struct vec3_t mins; vec3_t maxs; float zOffset; -} buildableAttributeOverrides_t; +} buildableConfig_t; -//TA: weapon record +// weapon record typedef struct { - int weaponNum; + weapon_t number; int price; int stages; int slots; - char *weaponName; - char *weaponHumanName; + char *name; + char *humanName; + char *info; int maxAmmo; int maxClips; @@ -1081,38 +1078,34 @@ typedef struct qboolean purchasable; qboolean longRanged; - int buildDelay; - - WUTeam_t team; + team_t team; } weaponAttributes_t; -//TA: upgrade record +// upgrade record typedef struct { - int upgradeNum; + upgrade_t number; int price; int stages; int slots; - char *upgradeName; - char *upgradeHumanName; + char *name; + char *humanName; + char *info; char *icon; qboolean purchasable; qboolean usable; - WUTeam_t team; + team_t team; } upgradeAttributes_t; - -//TA: qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips ); -void BG_AddWeaponToInventory( int weapon, int stats[ ] ); -void BG_RemoveWeaponFromInventory( int weapon, int stats[ ] ); qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ); +int BG_SlotsForInventory( int stats[ ] ); void BG_AddUpgradeToInventory( int item, int stats[ ] ); void BG_RemoveUpgradeFromInventory( int item, int stats[ ] ); qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] ); @@ -1121,122 +1114,60 @@ void BG_DeactivateUpgrade( int item, int stats[ ] ); qboolean BG_UpgradeIsActive( int item, int stats[ ] ); qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ], vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling ); +void BG_GetClientNormal( const playerState_t *ps, vec3_t normal ); +void BG_GetClientViewOrigin( const playerState_t *ps, vec3_t viewOrigin ); void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps, const vec3_t mins, const vec3_t maxs, void (*trace)( trace_t *, const vec3_t, const vec3_t, const vec3_t, const vec3_t, int, int ), vec3_t outOrigin, vec3_t outAngles, trace_t *tr ); -int BG_GetValueOfHuman( playerState_t *ps ); -int BG_GetValueOfEquipment( playerState_t *ps ); - -int BG_FindBuildNumForName( char *name ); -int BG_FindBuildNumForEntityName( char *name ); -char *BG_FindNameForBuildable( int bclass ); -char *BG_FindHumanNameForBuildable( int bclass ); -char *BG_FindEntityNameForBuildable( int bclass ); -char *BG_FindModelsForBuildable( int bclass, int modelNum ); -float BG_FindModelScaleForBuildable( int bclass ); -void BG_FindBBoxForBuildable( int bclass, vec3_t mins, vec3_t maxs ); -float BG_FindZOffsetForBuildable( int pclass ); -int BG_FindHealthForBuildable( int bclass ); -int BG_FindRegenRateForBuildable( int bclass ); -trType_t BG_FindTrajectoryForBuildable( int bclass ); -float BG_FindBounceForBuildable( int bclass ); -int BG_FindBuildPointsForBuildable( int bclass ); -qboolean BG_FindStagesForBuildable( int bclass, stage_t stage ); -int BG_FindSplashDamageForBuildable( int bclass ); -int BG_FindSplashRadiusForBuildable( int bclass ); -int BG_FindMODForBuildable( int bclass ); -int BG_FindTeamForBuildable( int bclass ); -weapon_t BG_FindBuildWeaponForBuildable( int bclass ); -int BG_FindAnimForBuildable( int bclass ); -int BG_FindNextThinkForBuildable( int bclass ); -int BG_FindBuildTimeForBuildable( int bclass ); -qboolean BG_FindUsableForBuildable( int bclass ); -int BG_FindRangeForBuildable( int bclass ); -int BG_FindFireSpeedForBuildable( int bclass ); -weapon_t BG_FindProjTypeForBuildable( int bclass ); -float BG_FindMinNormalForBuildable( int bclass ); -qboolean BG_FindInvertNormalForBuildable( int bclass ); -int BG_FindCreepTestForBuildable( int bclass ); -int BG_FindCreepSizeForBuildable( int bclass ); -int BG_FindDCCTestForBuildable( int bclass ); -int BG_FindUniqueTestForBuildable( int bclass ); -qboolean BG_FindReplaceableTestForBuildable( int bclass ); -qboolean BG_FindTransparentTestForBuildable( int bclass ); -void BG_InitBuildableOverrides( void ); - -int BG_FindClassNumForName( char *name ); -char *BG_FindNameForClassNum( int pclass ); -char *BG_FindHumanNameForClassNum( int pclass ); -char *BG_FindModelNameForClass( int pclass ); -float BG_FindModelScaleForClass( int pclass ); -char *BG_FindSkinNameForClass( int pclass ); -float BG_FindShadowScaleForClass( int pclass ); -char *BG_FindHudNameForClass( int pclass ); -qboolean BG_FindStagesForClass( int pclass, stage_t stage ); -void BG_FindBBoxForClass( int pclass, vec3_t mins, vec3_t maxs, vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs ); -float BG_FindZOffsetForClass( int pclass ); -void BG_FindViewheightForClass( int pclass, int *viewheight, int *cViewheight ); -int BG_FindHealthForClass( int pclass ); -float BG_FindFallDamageForClass( int pclass ); -int BG_FindRegenRateForClass( int pclass ); -int BG_FindFovForClass( int pclass ); -float BG_FindBobForClass( int pclass ); -float BG_FindBobCycleForClass( int pclass ); -float BG_FindSpeedForClass( int pclass ); -float BG_FindAccelerationForClass( int pclass ); -float BG_FindAirAccelerationForClass( int pclass ); -float BG_FindFrictionForClass( int pclass ); -float BG_FindStopSpeedForClass( int pclass ); -float BG_FindJumpMagnitudeForClass( int pclass ); -float BG_FindKnockbackScaleForClass( int pclass ); -int BG_FindSteptimeForClass( int pclass ); -qboolean BG_ClassHasAbility( int pclass, int ability ); -weapon_t BG_FindStartWeaponForClass( int pclass ); -float BG_FindBuildDistForClass( int pclass ); -int BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num ); -int BG_FindCostOfClass( int pclass ); -int BG_FindValueOfClass( int pclass ); -void BG_InitClassOverrides( void ); - -int BG_FindPriceForWeapon( int weapon ); -qboolean BG_FindStagesForWeapon( int weapon, stage_t stage ); -int BG_FindSlotsForWeapon( int weapon ); -char *BG_FindNameForWeapon( int weapon ); -int BG_FindWeaponNumForName( char *name ); -char *BG_FindHumanNameForWeapon( int weapon ); -char *BG_FindModelsForWeapon( int weapon, int modelNum ); -char *BG_FindIconForWeapon( int weapon ); -char *BG_FindCrosshairForWeapon( int weapon ); -int BG_FindCrosshairSizeForWeapon( int weapon ); -void BG_FindAmmoForWeapon( int weapon, int *maxAmmo, int *maxClips ); -qboolean BG_FindInfinteAmmoForWeapon( int weapon ); -qboolean BG_FindUsesEnergyForWeapon( int weapon ); -int BG_FindRepeatRate1ForWeapon( int weapon ); -int BG_FindRepeatRate2ForWeapon( int weapon ); -int BG_FindRepeatRate3ForWeapon( int weapon ); -int BG_FindReloadTimeForWeapon( int weapon ); -float BG_FindKnockbackScaleForWeapon( int weapon ); -qboolean BG_WeaponHasAltMode( int weapon ); -qboolean BG_WeaponHasThirdMode( int weapon ); -qboolean BG_WeaponCanZoom( int weapon ); -float BG_FindZoomFovForWeapon( int weapon ); -qboolean BG_FindPurchasableForWeapon( int weapon ); -qboolean BG_FindLongRangedForWeapon( int weapon ); -int BG_FindBuildDelayForWeapon( int weapon ); -WUTeam_t BG_FindTeamForWeapon( int weapon ); - -int BG_FindPriceForUpgrade( int upgrade ); -qboolean BG_FindStagesForUpgrade( int upgrade, stage_t stage ); -int BG_FindSlotsForUpgrade( int upgrade ); -char *BG_FindNameForUpgrade( int upgrade ); -int BG_FindUpgradeNumForName( char *name ); -char *BG_FindHumanNameForUpgrade( int upgrade ); -char *BG_FindIconForUpgrade( int upgrade ); -qboolean BG_FindPurchasableForUpgrade( int upgrade ); -qboolean BG_FindUsableForUpgrade( int upgrade ); -WUTeam_t BG_FindTeamForUpgrade( int upgrade ); +int BG_GetValueOfPlayer( playerState_t *ps ); +qboolean BG_PlayerCanChangeWeapon( playerState_t *ps ); +int BG_PlayerPoisonCloudTime( playerState_t *ps ); +weapon_t BG_GetPlayerWeapon( playerState_t *ps ); +qboolean BG_HasEnergyWeapon( playerState_t *ps ); + +void BG_PackEntityNumbers( entityState_t *es, const int *entityNums, int count ); +int BG_UnpackEntityNumbers( entityState_t *es, int *entityNums, int count ); + +const buildableAttributes_t *BG_BuildableByName( const char *name ); +const buildableAttributes_t *BG_BuildableByEntityName( const char *name ); +const buildableAttributes_t *BG_Buildable( buildable_t buildable ); +qboolean BG_BuildableAllowedInStage( buildable_t buildable, + stage_t stage ); + +buildableConfig_t *BG_BuildableConfig( buildable_t buildable ); +void BG_BuildableBoundingBox( buildable_t buildable, + vec3_t mins, vec3_t maxs ); +void BG_InitBuildableConfigs( void ); + +const classAttributes_t *BG_ClassByName( const char *name ); +const classAttributes_t *BG_Class( class_t klass ); +qboolean BG_ClassAllowedInStage( class_t klass, + stage_t stage ); + +classConfig_t *BG_ClassConfig( class_t klass ); + +void BG_ClassBoundingBox( class_t klass, vec3_t mins, + vec3_t maxs, vec3_t cmaxs, + vec3_t dmins, vec3_t dmaxs ); +qboolean BG_ClassHasAbility( class_t klass, int ability ); +int BG_ClassCanEvolveFromTo( class_t fclass, + class_t tclass, + int credits, int alienStage, int num ); +qboolean BG_AlienCanEvolve( class_t klass, int credits, int alienStage ); + +void BG_InitClassConfigs( void ); + +const weaponAttributes_t *BG_WeaponByName( const char *name ); +const weaponAttributes_t *BG_Weapon( weapon_t weapon ); +qboolean BG_WeaponAllowedInStage( weapon_t weapon, + stage_t stage ); + +const upgradeAttributes_t *BG_UpgradeByName( const char *name ); +const upgradeAttributes_t *BG_Upgrade( upgrade_t upgrade ); +qboolean BG_UpgradeAllowedInStage( upgrade_t upgrade, + stage_t stage ); // content masks #define MASK_ALL (-1) @@ -1257,7 +1188,10 @@ typedef enum ET_PLAYER, ET_ITEM, - ET_BUILDABLE, //TA: buildable type + ET_BUILDABLE, + ET_RANGE_MARKER, + + ET_LOCATION, ET_MISSILE, ET_MOVER, @@ -1275,12 +1209,19 @@ typedef enum ET_MODELDOOR, ET_LIGHTFLARE, ET_LEV2_ZAP_CHAIN, + ET_WEAPON_DROP, ET_EVENTS // any of the EV_* events can be added freestanding // by setting eType to ET_EVENTS + eventNum // this avoids having to set eFlags and eventNum } entityType_t; +void *BG_Alloc( int size ); +void BG_InitMemory( void ); +void BG_Free( void *ptr ); +void BG_DefragmentMemory( void ); +void BG_MemoryInfo( void ); + void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); @@ -1303,28 +1244,83 @@ int atoi_neg( char *token, qboolean allowNegative ); void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize, upgrade_t *upgrades, int upgradesSize ); -void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize ); +void BG_ParseCSVClassList( const char *string, class_t *classes, int classesSize ); void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize ); void BG_InitAllowedGameElements( void ); qboolean BG_WeaponIsAllowed( weapon_t weapon ); qboolean BG_UpgradeIsAllowed( upgrade_t upgrade ); -qboolean BG_ClassIsAllowed( pClass_t class ); +qboolean BG_ClassIsAllowed( class_t klass ); qboolean BG_BuildableIsAllowed( buildable_t buildable ); -qboolean BG_UpgradeClassAvailable( playerState_t *ps ); - -typedef struct -{ - unsigned int hi; - unsigned int lo; -} clientList_t; -qboolean BG_ClientListTest( clientList_t *list, int clientNum ); -void BG_ClientListAdd( clientList_t *list, int clientNum ); -void BG_ClientListRemove( clientList_t *list, int clientNum ); -char *BG_ClientListString( clientList_t *list ); -void BG_ClientListParse( clientList_t *list, const char *s ); // Friendly Fire Flags #define FFF_HUMANS 1 #define FFF_ALIENS 2 #define FFF_BUILDABLES 4 +// bg_voice.c +#define MAX_VOICES 8 +#define MAX_VOICE_NAME_LEN 16 +#define MAX_VOICE_CMD_LEN 16 +#define VOICE_ENTHUSIASM_DECAY 0.5f // enthusiasm lost per second + +typedef enum +{ + VOICE_CHAN_ALL, + VOICE_CHAN_TEAM , + VOICE_CHAN_LOCAL, + + VOICE_CHAN_NUM_CHANS +} voiceChannel_t; + +typedef struct voiceTrack_s +{ +//#ifdef CGAME + sfxHandle_t track; + int duration; +//#endif + char *text; + int enthusiasm; + int team; + int klass; + int weapon; + struct voiceTrack_s *next; +} voiceTrack_t; + +typedef struct voiceCmd_s +{ + char cmd[ MAX_VOICE_CMD_LEN ]; + voiceTrack_t *tracks; + struct voiceCmd_s *next; +} voiceCmd_t; + +typedef struct voice_s +{ + char name[ MAX_VOICE_NAME_LEN ]; + voiceCmd_t *cmds; + struct voice_s *next; +} voice_t; + +voice_t *BG_VoiceInit( void ); +void BG_PrintVoices( voice_t *voices, int debugLevel ); + +voice_t *BG_VoiceByName( voice_t *head, char *name ); +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ); +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num); +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ); +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, + class_t klass, weapon_t weapon, + int enthusiasm, int *trackNum ); + +int BG_LoadEmoticons( emoticon_t *emoticons, int num ); + +char *BG_TeamName( team_t team ); + +typedef struct +{ + const char *name; +} dummyCmd_t; +int cmdcmp( const void *a, const void *b ); + +char *G_CopyString( const char *str ); + +#endif diff --git a/src/game/bg_shared.h b/src/game/bg_shared.h new file mode 100644 index 0000000..e69de29 diff --git a/src/game/bg_slidemove.c b/src/game/bg_slidemove.c index aa32a6a..3d6a521 100644 --- a/src/game/bg_slidemove.c +++ b/src/game/bg_slidemove.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,14 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // bg_slidemove.c -- part of bg_pmove functionality -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h" #include "bg_public.h" #include "bg_local.h" @@ -74,8 +75,7 @@ qboolean PM_SlideMove( qboolean gravity ) if( pml.groundPlane ) { // slide along the ground plane - PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity ); } } @@ -166,10 +166,10 @@ qboolean PM_SlideMove( qboolean gravity ) pml.impactSpeed = -into; // slide along the plane - PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity ); // slide along the plane - PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity, OVERCLIP ); + PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity ); // see if there is a second plane that the new move enters for( j = 0; j < numplanes; j++ ) @@ -181,8 +181,8 @@ qboolean PM_SlideMove( qboolean gravity ) continue; // move doesn't interact with the plane // try clipping the move to the plane - PM_ClipVelocity( clipVelocity, planes[ j ], clipVelocity, OVERCLIP ); - PM_ClipVelocity( endClipVelocity, planes[ j ], endClipVelocity, OVERCLIP ); + PM_ClipVelocity( clipVelocity, planes[ j ], clipVelocity ); + PM_ClipVelocity( endClipVelocity, planes[ j ], endClipVelocity ); // see if it goes back into the first clip plane if( DotProduct( clipVelocity, planes[ i ] ) >= 0 ) @@ -290,7 +290,6 @@ PM_StepSlideMove qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ) { vec3_t start_o, start_v; - vec3_t down_o, down_v; trace_t trace; vec3_t normal; vec3_t step_v, step_vNormal; @@ -298,15 +297,7 @@ qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ) float stepSize; qboolean stepped = qfalse; - if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( pm->ps->grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); + BG_GetClientNormal( pm->ps, normal ); VectorCopy( pm->ps->origin, start_o ); VectorCopy( pm->ps->velocity, start_v ); @@ -339,9 +330,6 @@ qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ) return stepped; } - VectorCopy( pm->ps->origin, down_o ); - VectorCopy( pm->ps->velocity, down_v ); - VectorCopy( start_o, up ); VectorMA( up, STEPSIZE, normal, up ); @@ -381,7 +369,7 @@ qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ) VectorCopy( trace.endpos, pm->ps->origin ); if( trace.fraction < 1.0f ) - PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity ); } if( !predictive && stepped ) diff --git a/src/game/bg_voice.c b/src/game/bg_voice.c new file mode 100644 index 0000000..06ce939 --- /dev/null +++ b/src/game/bg_voice.c @@ -0,0 +1,653 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2008 Tony J. White +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// bg_voice.c -- both games voice functions +#include "qcommon/q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int trap_Parse_LoadSource( const char *filename ); +int trap_Parse_FreeSource( int handle ); +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ); +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ); + +#ifdef CGAME +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); +int trap_S_SoundDuration( sfxHandle_t handle ); +#endif + + +/* +============ +BG_VoiceParseError +============ +*/ +static void BG_VoiceParseError( fileHandle_t handle, const char *err ) +{ + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + trap_Parse_FreeSource( handle ); + Com_Error( ERR_FATAL, "%s on line %d of %s", err, line, filename ); +} + +/* +============ +BG_VoiceList +============ +*/ +static voice_t *BG_VoiceList( void ) +{ + char fileList[ MAX_VOICES * ( MAX_VOICE_NAME_LEN + 8 ) ] = {""}; + int numFiles, i, fileLen = 0; + int count = 0; + char *filePtr; + voice_t *voices = NULL; + voice_t *top = NULL; + + numFiles = trap_FS_GetFileList( "voice", ".voice", fileList, + sizeof( fileList ) ); + + if( numFiles < 1 ) + return NULL; + + // special case for default.voice. this file is REQUIRED and will + // always be loaded first in the event of overflow of voice definitions + if( !trap_FS_FOpenFile( "voice/default.voice", NULL, FS_READ ) ) + { + Com_Printf( "voice/default.voice missing, voice system disabled." ); + return NULL; + } + + voices = (voice_t*)BG_Alloc( sizeof( voice_t ) ); + Q_strncpyz( voices->name, "default", sizeof( voices->name ) ); + voices->cmds = NULL; + voices->next = NULL; + count = 1; + + top = voices; + + filePtr = fileList; + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + + // accounted for above + if( !Q_stricmp( filePtr, "default.voice" ) ) + continue; + + if( fileLen > MAX_VOICE_NAME_LEN + 8 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: MAX_VOICE_NAME_LEN is %d. " + "skipping \"%s\", filename too long", MAX_VOICE_NAME_LEN, filePtr ); + continue; + } + + // trap_FS_GetFileList() buffer has overflowed + if( !trap_FS_FOpenFile( va( "voice/%s", filePtr ), NULL, FS_READ ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceList(): detected " + "an invalid .voice file \"%s\" in directory listing. You have " + "probably named one or more .voice files with outrageously long " + "names. gjbs", filePtr ); + break; + } + + if( count >= MAX_VOICES ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: .voice file overflow. " + "%d of %d .voice files loaded. MAX_VOICES is %d", + count, numFiles, MAX_VOICES ); + break; + } + + voices->next = (voice_t*)BG_Alloc( sizeof( voice_t ) ); + voices = voices->next; + + Q_strncpyz( voices->name, filePtr, sizeof( voices->name ) ); + // strip extension + voices->name[ fileLen - 6 ] = '\0'; + voices->cmds = NULL; + voices->next = NULL; + count++; + } + return top; +} + +/* +============ +BG_VoiceParseTrack +============ +*/ +static qboolean BG_VoiceParseTrack( int handle, voiceTrack_t *voiceTrack ) +{ + pc_token_t token; + qboolean found = qfalse; + qboolean foundText = qfalse; + qboolean foundToken = qfalse; + + foundToken = trap_Parse_ReadToken( handle, &token ); + while( foundToken ) + { + if( token.string[ 0 ] == '}' ) + { + if( foundText ) + return qtrue; + else + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing text attribute for track" ); + } + } + else if( !Q_stricmp( token.string, "team" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->team < 0 ) + voiceTrack->team = 0; + voiceTrack->team |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"team\" value" ); + } + continue; + } + else if( !Q_stricmp( token.string, "class" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->klass < 0 ) + voiceTrack->klass = 0; + voiceTrack->klass |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"class\" value" ); + } + continue; + } + else if( !Q_stricmp( token.string, "weapon" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->weapon < 0 ) + voiceTrack->weapon = 0; + voiceTrack->weapon |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"weapon\" value"); + } + continue; + } + else if( !Q_stricmp( token.string, "text" ) ) + { + if( foundText ) + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "duplicate \"text\" definition for track" ); + } + foundToken = trap_Parse_ReadToken( handle, &token ); + if( !foundToken ) + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing \"text\" value" ); + } + foundText = qtrue; + if( strlen( token.string ) >= MAX_SAY_TEXT ) + { + BG_VoiceParseError( handle, va( "BG_VoiceParseTrack(): " + "\"text\" value " "\"%s\" exceeds MAX_SAY_TEXT length", + token.string ) ); + } + + voiceTrack->text = (char *)BG_Alloc( strlen( token.string ) + 1 ); + Q_strncpyz( voiceTrack->text, token.string, strlen( token.string ) + 1 ); + foundToken = trap_Parse_ReadToken( handle, &token ); + continue; + } + else if( !Q_stricmp( token.string, "enthusiasm" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + if( token.type == TT_NUMBER ) + { + voiceTrack->enthusiasm = token.intvalue; + } + else + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing \"enthusiasm\" value" ); + } + foundToken = trap_Parse_ReadToken( handle, &token ); + continue; + } + else + { + BG_VoiceParseError( handle, va( "BG_VoiceParseTrack():" + " unknown token \"%s\"", token.string ) ); + } + } + return qfalse; +} + +/* +============ +BG_VoiceParseCommand +============ +*/ +static voiceTrack_t *BG_VoiceParseCommand( int handle ) +{ + pc_token_t token; + qboolean parsingTrack = qfalse; + voiceTrack_t *voiceTracks = NULL; + voiceTrack_t *top = NULL; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( !parsingTrack && token.string[ 0 ] == '}' ) + return top; + + if( parsingTrack ) + { + if( token.string[ 0 ] == '{' ) + { + BG_VoiceParseTrack( handle, voiceTracks ); + parsingTrack = qfalse; + continue; + + } + else + { + BG_VoiceParseError( handle, va( "BG_VoiceParseCommand(): " + "parse error at \"%s\"", token.string ) ); + } + } + + + if( top == NULL ) + { + voiceTracks = BG_Alloc( sizeof( voiceTrack_t ) ); + top = voiceTracks; + } + else + { + voiceTracks->next = BG_Alloc( sizeof( voiceCmd_t ) ); + voiceTracks = voiceTracks->next; + } + + if( !trap_FS_FOpenFile( token.string, NULL, FS_READ ) ) + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceParseCommand(): " + "track \"%s\" referenced on line %d of %s does not exist\n", + token.string, line, filename ); + } + else + { +#ifdef CGAME + voiceTracks->track = trap_S_RegisterSound( token.string, qfalse ); + voiceTracks->duration = trap_S_SoundDuration( voiceTracks->track ); +#endif + } + + voiceTracks->team = -1; + voiceTracks->klass = -1; + voiceTracks->weapon = -1; + voiceTracks->enthusiasm = 0; + voiceTracks->text = NULL; + voiceTracks->next = NULL; + parsingTrack = qtrue; + + } + return NULL; +} + +/* +============ +BG_VoiceParse +============ +*/ +static voiceCmd_t *BG_VoiceParse( char *name ) +{ + voiceCmd_t *voiceCmds = NULL; + voiceCmd_t *top = NULL; + pc_token_t token; + qboolean parsingCmd = qfalse; + int handle; + + handle = trap_Parse_LoadSource( va( "voice/%s.voice", name ) ); + if( !handle ) + return NULL; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( parsingCmd ) + { + if( token.string[ 0 ] == '{' ) + { + voiceCmds->tracks = BG_VoiceParseCommand( handle ); + parsingCmd = qfalse; + continue; + } + else + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Error( ERR_FATAL, "BG_VoiceParse(): " + "parse error on line %d of %s", line, filename ); + } + } + + if( strlen( token.string ) >= MAX_VOICE_CMD_LEN ) + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Error( ERR_FATAL, "BG_VoiceParse(): " + "command \"%s\" exceeds MAX_VOICE_CMD_LEN (%d) on line %d of %s", + token.string, MAX_VOICE_CMD_LEN, line, filename ); + } + + if( top == NULL ) + { + voiceCmds = BG_Alloc( sizeof( voiceCmd_t ) ); + top = voiceCmds; + } + else + { + voiceCmds->next = BG_Alloc( sizeof( voiceCmd_t ) ); + voiceCmds = voiceCmds->next; + } + + Q_strncpyz( voiceCmds->cmd, token.string, sizeof( voiceCmds->cmd ) ); + voiceCmds->next = NULL; + parsingCmd = qtrue; + + } + + trap_Parse_FreeSource( handle ); + + return top; +} + +/* +============ +BG_VoiceInit +============ +*/ +voice_t *BG_VoiceInit( void ) +{ + voice_t *voices; + voice_t *voice; + + voices = BG_VoiceList(); + + voice = voices; + while( voice ) + { + voice->cmds = BG_VoiceParse( voice->name ); + voice = voice->next; + } + + return voices; +} + + +/* +============ +BG_PrintVoices +============ +*/ +void BG_PrintVoices( voice_t *voices, int debugLevel ) +{ + voice_t *voice = voices; + voiceCmd_t *voiceCmd; + voiceTrack_t *voiceTrack; + + int cmdCount; + int trackCount; + + if( voice == NULL ) + { + Com_Printf( "voice list is empty\n" ); + return; + } + + while( voice != NULL ) + { + if( debugLevel > 0 ) + Com_Printf( "voice \"%s\"\n", voice->name ); + voiceCmd = voice->cmds; + cmdCount = 0; + trackCount = 0; + while( voiceCmd != NULL ) + { + if( debugLevel > 0 ) + Com_Printf( " %s\n", voiceCmd->cmd ); + voiceTrack = voiceCmd->tracks; + cmdCount++; + while ( voiceTrack != NULL ) + { + if( debugLevel > 1 ) + Com_Printf( " text -> %s\n", voiceTrack->text ); + if( debugLevel > 2 ) + { + Com_Printf( " team -> %d\n", voiceTrack->team ); + Com_Printf( " class -> %d\n", voiceTrack->klass ); + Com_Printf( " weapon -> %d\n", voiceTrack->weapon ); + Com_Printf( " enthusiasm -> %d\n", voiceTrack->enthusiasm ); +#ifdef CGAME + Com_Printf( " duration -> %d\n", voiceTrack->duration ); +#endif + } + if( debugLevel > 1 ) + Com_Printf( "\n" ); + trackCount++; + voiceTrack = voiceTrack->next; + } + voiceCmd = voiceCmd->next; + } + + if( !debugLevel ) + { + Com_Printf( "voice \"%s\": %d commands, %d tracks\n", + voice->name, cmdCount, trackCount ); + } + voice = voice->next; + } +} + +/* +============ +BG_VoiceByName +============ +*/ +voice_t *BG_VoiceByName( voice_t *head, char *name ) +{ + voice_t *v = head; + + while( v ) + { + if( !Q_stricmp( v->name, name ) ) + return v; + v = v->next; + } + return NULL; +} + +/* +============ +BG_VoiceCmdFind +============ +*/ +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ) +{ + voiceCmd_t *vc = head; + int i = 0; + + while( vc ) + { + i++; + if( !Q_stricmp( vc->cmd, name ) ) + { + *cmdNum = i; + return vc; + } + vc = vc->next; + } + return NULL; +} + +/* +============ +BG_VoiceCmdByNum +============ +*/ +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num ) +{ + voiceCmd_t *vc = head; + int i = 0; + + while( vc ) + { + i++; + if( i == num ) + return vc; + vc = vc->next; + } + return NULL; +} + +/* +============ +BG_VoiceTrackByNum +============ +*/ +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ) +{ + voiceTrack_t *vt = head; + int i = 0; + + while( vt ) + { + i++; + if( i == num ) + return vt; + vt = vt->next; + } + return NULL; +} + +/* +============ +BG_VoiceTrackFind +============ +*/ +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, + class_t class, weapon_t weapon, + int enthusiasm, int *trackNum ) +{ + voiceTrack_t *vt = head; + int highestMatch = 0; + int matchCount = 0; + int selectedMatch = 0; + int i = 0; + int j = 0; + + // find highest enthusiasm without going over + while( vt ) + { + if( ( vt->team >= 0 && !( vt->team & ( 1 << team ) ) ) || + ( vt->klass >= 0 && !( vt->klass & ( 1 << class ) ) ) || + ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || + vt->enthusiasm > enthusiasm ) + { + vt = vt->next; + continue; + } + + if( vt->enthusiasm > highestMatch ) + { + matchCount = 0; + highestMatch = vt->enthusiasm; + } + if( vt->enthusiasm == highestMatch ) + matchCount++; + vt = vt->next; + } + + if( !matchCount ) + return NULL; + + // return randomly selected match + selectedMatch = rand() / ( RAND_MAX / matchCount + 1 ); + vt = head; + i = 0; + j = 0; + while( vt ) + { + j++; + if( ( vt->team >= 0 && !( vt->team & ( 1 << team ) ) ) || + ( vt->klass >= 0 && !( vt->klass & ( 1 << class ) ) ) || + ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || + vt->enthusiasm != highestMatch ) + { + vt = vt->next; + continue; + } + if( i == selectedMatch ) + { + *trackNum = j; + return vt; + } + i++; + vt = vt->next; + } + return NULL; +} diff --git a/src/game/g_active.c b/src/game/g_active.c index b5df273..d978a4a 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -40,7 +41,7 @@ void P_DamageFeedback( gentity_t *player ) vec3_t angles; client = player->client; - if( client->ps.pm_type == PM_DEAD ) + if( !PM_Alive( client->ps.pm_type ) ) return; // total points of damage shot at the player this frame @@ -129,7 +130,7 @@ void P_WorldEffects( gentity_t *ent ) // play a gurp sound instead of a normal pain sound if( ent->health <= ent->damage ) G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) ); - else if( rand( ) & 1 ) + else if( rand( ) < RAND_MAX / 2 + 1 ) G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) ); else G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) ); @@ -191,59 +192,74 @@ void G_SetClientSound( gentity_t *ent ) //============================================================== -static void G_ClientShove( gentity_t *ent, gentity_t *victim ) +/* +============== +ClientShove +============== +*/ +static int GetClientMass( gentity_t *ent ) { - vec3_t dir, push; - int entMass = 200, vicMass = 200; - - // shoving enemies changes gameplay too much - if( !OnSameTeam( ent, victim ) ) - return; + int entMass = 100; - if ( ( victim->client->ps.weapon >= WP_ABUILD ) && - ( victim->client->ps.weapon <= WP_HBUILD ) && - ( victim->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) ) - return; - - // alien mass is directly related to their health points - // human mass is 200, double for bsuit - if( ent->client->pers.teamSelection == PTE_ALIENS ) - { - entMass = BG_FindHealthForClass( ent->client->pers.classSelection ); - } - else if( ent->client->pers.teamSelection == PTE_HUMANS ) + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + entMass = BG_Class( ent->client->pers.classSelection )->health; + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) { if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) ) entMass *= 2; } else + return 0; + return entMass; +} + +static void ClientShove( gentity_t *ent, gentity_t *victim ) +{ + vec3_t dir, push; + float force; + int entMass, vicMass; + + // Don't push if the entity is not trying to move + if( !ent->client->pers.cmd.rightmove && !ent->client->pers.cmd.forwardmove && + !ent->client->pers.cmd.upmove ) return; - if( victim->client->pers.teamSelection == PTE_ALIENS ) - { - vicMass = BG_FindHealthForClass( victim->client->pers.classSelection ); - } - else if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, - victim->client->ps.stats ) ) - { - vicMass *= 2; - } + // Cannot push enemy players unless they are walking on the player + if( !OnSameTeam( ent, victim ) && + victim->client->ps.groundEntityNum != ent - g_entities ) + return; + // Shove force is scaled by relative mass + entMass = GetClientMass( ent ); + vicMass = GetClientMass( victim ); if( vicMass <= 0 || entMass <= 0 ) return; + force = g_shove.value * entMass / vicMass; + if( force < 0 ) + force = 0; + if( force > 150 ) + force = 150; + // Give the victim some shove velocity VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir ); VectorNormalizeFast( dir ); - - // don't break the dretch elevator - if( fabs( dir[ 2 ] ) > fabs( dir[ 0 ] ) && fabs( dir[ 2 ] ) > fabs( dir[ 1 ] ) ) - return; - - VectorScale( dir, - ( g_shove.value * ( ( float )entMass / ( float )vicMass ) ), push ); - VectorAdd( victim->client->ps.velocity, push, - victim->client->ps.velocity ); - + VectorScale( dir, force, push ); + VectorAdd( victim->client->ps.velocity, push, victim->client->ps.velocity ); + + // Set the pmove timer so that the other client can't cancel + // out the movement immediately + if( !victim->client->ps.pm_time ) + { + int time; + + time = force * 2 + 0.5f; + if( time < 50 ) + time = 50; + if( time > 200 ) + time = 200; + victim->client->ps.pm_time = time; + victim->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } } /* @@ -253,45 +269,38 @@ ClientImpacts */ void ClientImpacts( gentity_t *ent, pmove_t *pm ) { - int i, j; + int i; trace_t trace; gentity_t *other; + // clear a fake trace struct for touch function memset( &trace, 0, sizeof( trace ) ); for( i = 0; i < pm->numtouch; i++ ) { - for( j = 0; j < i; j++ ) - { - if( pm->touchents[ j ] == pm->touchents[ i ] ) - break; - } - - if( j != i ) - continue; // duplicated - other = &g_entities[ pm->touchents[ i ] ]; // see G_UnlaggedDetectCollisions(), this is the inverse of that. // if our movement is blocked by another player's real position, - // don't use the unlagged position for them because they are + // don't use the unlagged position for them because they are // blocking or server-side Pmove() from reaching it if( other->client && other->client->unlaggedCalc.used ) other->client->unlaggedCalc.used = qfalse; - //charge attack - if( ent->client->ps.weapon == WP_ALEVEL4 && - ent->client->ps.stats[ STAT_MISC ] > 0 && - ent->client->charging ) - ChargeAttack( ent, other ); + // tyrant impact attacks + if( ent->client->ps.weapon == WP_ALEVEL4 ) + { + G_ChargeAttack( ent, other ); + G_CrushAttack( ent, other ); + } + // shove players if( ent->client && other->client ) - G_ClientShove( ent, other ); - - if( !other->touch ) - continue; + ClientShove( ent, other ); - other->touch( other, ent, &trace ); + // touch triggers + if( other->touch ) + other->touch( other, ent, &trace ); } } @@ -316,11 +325,15 @@ void G_TouchTriggers( gentity_t *ent ) if( !ent->client ) return; + // noclipping clients don't activate triggers! + if( ent->client->noclip ) + return; + // dead clients don't activate triggers! if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) return; - BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], pmins, pmaxs, NULL, NULL, NULL ); VectorAdd( ent->client->ps.origin, pmins, mins ); @@ -346,9 +359,7 @@ void G_TouchTriggers( gentity_t *ent ) continue; // ignore most entities if a spectator - if( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) || - ( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) || - ( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) { if( hit->s.eType != ET_TELEPORT_TRIGGER && // this is ugly but adding a new ET_? type will @@ -380,136 +391,123 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { pmove_t pm; gclient_t *client; - qboolean attack1, attack3; - qboolean doPmove = qtrue; + int clientNum; + qboolean attack1, following, queued; client = ent->client; client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; - attack1 = ( ( client->buttons & BUTTON_ATTACK ) && - !( client->oldbuttons & BUTTON_ATTACK ) ); - attack3 = ( ( client->buttons & BUTTON_USE_HOLDABLE ) && - !( client->oldbuttons & BUTTON_USE_HOLDABLE ) ); - - if( level.mapRotationVoteTime ) + attack1 = ( client->buttons & BUTTON_ATTACK ) && + !( client->oldbuttons & BUTTON_ATTACK ); + + // We are in following mode only if we are following a non-spectating client + following = client->sess.spectatorState == SPECTATOR_FOLLOW; + if( following ) { - if( attack1 ) - { - G_IntermissionMapVoteCommand( ent, qtrue, qfalse ); - attack1 = qfalse; - } - if( ( client->buttons & BUTTON_ATTACK2 ) && !( client->oldbuttons & BUTTON_ATTACK2 ) ) - G_IntermissionMapVoteCommand( ent, qfalse, qfalse ); + clientNum = client->sess.spectatorClient; + if( clientNum < 0 || clientNum > level.maxclients || + !g_entities[ clientNum ].client || + g_entities[ clientNum ].client->sess.spectatorState != SPECTATOR_NOT ) + following = qfalse; } - - if( client->sess.spectatorState == SPECTATOR_LOCKED || client->sess.spectatorState == SPECTATOR_FOLLOW ) - client->ps.pm_type = PM_FREEZE; + + // Check to see if we are in the spawn queue + if( client->pers.teamSelection == TEAM_ALIENS ) + queued = G_SearchSpawnQueue( &level.alienSpawnQueue, ent - g_entities ); + else if( client->pers.teamSelection == TEAM_HUMANS ) + queued = G_SearchSpawnQueue( &level.humanSpawnQueue, ent - g_entities ); else - client->ps.pm_type = PM_SPECTATOR; + queued = qfalse; - if ( client->sess.spectatorState == SPECTATOR_FOLLOW ) + // Wants to get out of spawn queue + if( attack1 && queued ) { - gclient_t *cl; - if ( client->sess.spectatorClient >= 0 ) - { - cl = &level.clients[ client->sess.spectatorClient ]; - if ( cl->sess.sessionTeam != TEAM_SPECTATOR ) - doPmove = qfalse; - } + if( client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + client->pers.classSelection = PCL_NONE; + client->pers.humanItemSelection = WP_NONE; + client->ps.stats[ STAT_CLASS ] = PCL_NONE; + client->ps.pm_flags &= ~PMF_QUEUED; + queued = qfalse; + } + else if( attack1 ) + { + // Wants to get into spawn queue + if( client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( client->pers.teamSelection == TEAM_NONE ) + G_TriggerMenu( client->ps.clientNum, MN_TEAM ); + else if( client->pers.teamSelection == TEAM_ALIENS ) + G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); + else if( client->pers.teamSelection == TEAM_HUMANS ) + G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); } - if (doPmove) + // We are either not following anyone or following a spectator + if( !following ) { - client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); - - // in case the client entered the queue while following a teammate - if( ( client->pers.teamSelection == PTE_ALIENS && - G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) || - ( client->pers.teamSelection == PTE_HUMANS && - G_SearchSpawnQueue( &level.humanSpawnQueue, ent-g_entities ) ) ) - { - client->ps.pm_flags |= PMF_QUEUED; - } - + if( client->sess.spectatorState == SPECTATOR_LOCKED || + client->sess.spectatorState == SPECTATOR_FOLLOW ) + client->ps.pm_type = PM_FREEZE; + else if( client->noclip ) + client->ps.pm_type = PM_NOCLIP; + else + client->ps.pm_type = PM_SPECTATOR; + + if( queued ) + client->ps.pm_flags |= PMF_QUEUED; + client->ps.speed = client->pers.flySpeed; client->ps.stats[ STAT_STAMINA ] = 0; client->ps.stats[ STAT_MISC ] = 0; - client->ps.stats[ STAT_BUILDABLE ] = 0; - client->ps.stats[ STAT_PCLASS ] = PCL_NONE; + client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + client->ps.stats[ STAT_CLASS ] = PCL_NONE; client->ps.weapon = WP_NONE; - // set up for pmove + // Set up for pmove memset( &pm, 0, sizeof( pm ) ); pm.ps = &client->ps; + pm.pmext = &client->pmext; pm.cmd = *ucmd; - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.tracemask = ent->clipmask; pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; - // perform a pmove + // Perform a pmove Pmove( &pm ); - // save results of pmove - VectorCopy( client->ps.origin, ent->s.origin ); + // Save results of pmove + VectorCopy( client->ps.origin, ent->s.pos.trBase ); + VectorCopy( client->ps.origin, ent->r.currentOrigin ); + VectorCopy( client->ps.viewangles, ent->r.currentAngles ); + VectorCopy( client->ps.viewangles, ent->s.pos.trBase ); G_TouchTriggers( ent ); trap_UnlinkEntity( ent ); - if( ( attack1 ) && ( client->ps.pm_flags & PMF_QUEUED ) ) - { - if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); - else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); - - client->pers.classSelection = PCL_NONE; - client->ps.stats[ STAT_PCLASS ] = PCL_NONE; - } - - if( attack1 && client->pers.classSelection == PCL_NONE ) - { - if( client->pers.teamSelection == PTE_NONE ) - G_TriggerMenu( client->ps.clientNum, MN_TEAM ); - else if( client->pers.teamSelection == PTE_ALIENS ) - G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); - else if( client->pers.teamSelection == PTE_HUMANS ) - G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); - } - - //set the queue position for the client side + // Set the queue position and spawn count for the client side if( client->ps.pm_flags & PMF_QUEUED ) { - if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { client->ps.persistant[ PERS_QUEUEPOS ] = G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + client->ps.persistant[ PERS_SPAWNS ] = level.numAlienSpawns; } - else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { client->ps.persistant[ PERS_QUEUEPOS ] = G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + client->ps.persistant[ PERS_SPAWNS ] = level.numHumanSpawns; } } } - - else if( attack1 && ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) - { - G_StopFollowing( ent ); - client->pers.classSelection = PCL_NONE; - if( client->pers.teamSelection == PTE_NONE ) - G_TriggerMenu( ent-g_entities, MN_TEAM ); - else if( client->pers.teamSelection == PTE_ALIENS ) - G_TriggerMenu( ent-g_entities, MN_A_CLASS ); - else if( client->pers.teamSelection == PTE_HUMANS ) - G_TriggerMenu( ent-g_entities, MN_H_SPAWN ); - } - - if( attack3 ) - { - G_ToggleFollow( ent ); - } } @@ -521,8 +519,10 @@ ClientInactivityTimer Returns qfalse if the client is dropped ================= */ -qboolean ClientInactivityTimer( gclient_t *client ) +qboolean ClientInactivityTimer( gentity_t *ent ) { + gclient_t *client = ent->client; + if( ! g_inactivity.integer ) { // give everyone some time, so if the operator sets g_inactivity during @@ -540,13 +540,16 @@ qboolean ClientInactivityTimer( gclient_t *client ) } else if( !client->pers.localClient ) { - if( level.time > client->inactivityTime ) + if( level.time > client->inactivityTime && + !G_admin_permission( ent, ADMF_ACTIVITY ) ) { trap_DropClient( client - level.clients, "Dropped due to inactivity" ); return qfalse; } - if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) + if( level.time > client->inactivityTime - 10000 && + !client->inactivityWarning && + !G_admin_permission( ent, ADMF_ACTIVITY ) ) { client->inactivityWarning = qtrue; trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); @@ -569,203 +572,100 @@ void ClientTimerActions( gentity_t *ent, int msec ) usercmd_t *ucmd; int aForward, aRight; qboolean walking = qfalse, stopped = qfalse, - crouched = qfalse, jumping = qfalse, - strafing = qfalse; + crouched = qfalse; + int i; ucmd = &ent->client->pers.cmd; aForward = abs( ucmd->forwardmove ); aRight = abs( ucmd->rightmove ); - client = ent->client; - client->time100 += msec; - client->time1000 += msec; - client->time10000 += msec; - if( aForward == 0 && aRight == 0 ) stopped = qtrue; else if( aForward <= 64 && aRight <= 64 ) walking = qtrue; - if( aRight > 0 ) - strafing = qtrue; - - if( ucmd->upmove > 0 ) - jumping = qtrue; - else if( ent->client->ps.pm_flags & PMF_DUCKED ) + if( ucmd->upmove <= 0 && ent->client->ps.pm_flags & PMF_DUCKED ) crouched = qtrue; + client = ent->client; + client->time100 += msec; + client->time1000 += msec; + client->time10000 += msec; + while ( client->time100 >= 100 ) { + weapon_t weapon = BG_GetPlayerWeapon( &client->ps ); + client->time100 -= 100; - //if not trying to run then not trying to sprint - if( walking || stopped ) - client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; - - if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) - client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; - - if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched ) - { - //subtract stamina - if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) - client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE; - else - client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; - - if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA ) - client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA; - } - - if( walking || crouched ) - { - //restore stamina - client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; - - if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) - client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; - } - else if( stopped ) - { - //restore stamina faster + // Restore or subtract stamina + if( stopped || client->ps.pm_type == PM_JETPACK ) client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; - - if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) - client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; - } - - //client is charging up for a pounce - if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG ) - { - int pounceSpeed = 0; - - if( client->ps.weapon == WP_ALEVEL3 ) - pounceSpeed = LEVEL3_POUNCE_SPEED; - else if( client->ps.weapon == WP_ALEVEL3_UPG ) - pounceSpeed = LEVEL3_POUNCE_UPG_SPEED; - - if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 ) - client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed; - - if( !( ucmd->buttons & BUTTON_ATTACK2 ) ) - { - if( client->pmext.pouncePayload > 0 ) - client->allowedToPounce = qtrue; - } - - if( client->ps.stats[ STAT_MISC ] > pounceSpeed ) - client->ps.stats[ STAT_MISC ] = pounceSpeed; - } - - //client is charging up for a... charge - if( client->ps.weapon == WP_ALEVEL4 ) + else if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && + !( client->buttons & BUTTON_WALKING ) ) // walk overrides sprint + client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; + else if( walking || crouched ) + client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; + + // Check stamina limits + if( client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + else if( client->ps.stats[ STAT_STAMINA ] < -STAMINA_MAX ) + client->ps.stats[ STAT_STAMINA ] = -STAMINA_MAX; + + if( weapon == WP_ABUILD || weapon == WP_ABUILD2 || + client->ps.stats[ STAT_WEAPON ] == WP_HBUILD ) { - if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 && - !client->charging ) - { - client->charging = qfalse; //should already be off, just making sure - client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING; - - if( ucmd->forwardmove > 0 ) - { - //trigger charge sound...is quite annoying - //if( client->ps.stats[ STAT_MISC ] <= 0 ) - // G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 ); - - client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO ); - - if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME ) - client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME; - } - else - client->ps.stats[ STAT_MISC ] = 0; - } - - if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging || - client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME ) - { - if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME ) - { + // Update build timer + if( client->ps.stats[ STAT_MISC ] > 0 ) client->ps.stats[ STAT_MISC ] -= 100; - if( client->charging == qfalse ) - G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 ); - - client->charging = qtrue; - client->ps.stats[ STAT_STATE ] |= SS_CHARGING; - - //if the charger has stopped moving take a chunk of charge away - if( VectorLength( client->ps.velocity ) < 64.0f || aRight ) - client->ps.stats[ STAT_MISC ] = client->ps.stats[ STAT_MISC ] / 2; - - //can't charge backwards - if( ucmd->forwardmove < 0 ) - client->ps.stats[ STAT_MISC ] = 0; - } - else - client->ps.stats[ STAT_MISC ] = 0; - - - if( client->ps.stats[ STAT_MISC ] <= 0 ) - { + if( client->ps.stats[ STAT_MISC ] < 0 ) client->ps.stats[ STAT_MISC ] = 0; - client->charging = qfalse; - client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING; - } - } - } - - //client is charging up an lcannon - if( client->ps.weapon == WP_LUCIFER_CANNON ) - { - int ammo; - - ammo = client->ps.ammo; - - if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK ) - client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE; - - if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE ) - client->ps.stats[ STAT_MISC ] = LCANNON_TOTAL_CHARGE; - - if( client->ps.stats[ STAT_MISC ] > ( ammo * LCANNON_TOTAL_CHARGE ) / 10 ) - client->ps.stats[ STAT_MISC ] = ammo * LCANNON_TOTAL_CHARGE / 10; } - switch( client->ps.weapon ) + switch( weapon ) { case WP_ABUILD: case WP_ABUILD2: case WP_HBUILD: - case WP_HBUILD2: - //set validity bit on buildable + + // Set validity bit on buildable if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) { - int dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); - vec3_t dummy; + int dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; + vec3_t dummy, dummy2; + int dummy3; if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, - dist, dummy ) == IBE_NONE ) + dist, dummy, dummy2, &dummy3 ) == IBE_NONE ) client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; else client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; - } - case WP_BLASTER: - //update build timer - if( client->ps.stats[ STAT_MISC ] > 0 ) - client->ps.stats[ STAT_MISC ] -= 100; - - if( client->ps.stats[ STAT_MISC ] < 0 ) - client->ps.stats[ STAT_MISC ] = 0; + // Let the client know which buildables will be removed by building + for( i = 0; i < MAX_MISC; i++ ) + { + if( i < level.numBuildablesForRemoval ) + client->ps.misc[ i ] = level.markedBuildables[ i ]->s.number; + else + client->ps.misc[ i ] = 0; + } + } + else + { + for( i = 0; i < MAX_MISC; i++ ) + client->ps.misc[ i ] = 0; + } break; default: break; } - if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) + if( ent->client->pers.teamSelection == TEAM_HUMANS && + ( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) ) { int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); @@ -777,9 +677,10 @@ void ClientTimerActions( gentity_t *ent, int msec ) { ent->client->medKitHealthToRestore--; ent->health++; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health; } else - ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; + ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; } else { @@ -792,13 +693,14 @@ void ClientTimerActions( gentity_t *ent, int msec ) { ent->client->medKitHealthToRestore--; ent->health++; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health; client->medKitIncrementTime = level.time + ( remainingStartupTime / MEDKIT_STARTUP_SPEED ); } } else - ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; + ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; } } } @@ -807,20 +709,17 @@ void ClientTimerActions( gentity_t *ent, int msec ) { client->time1000 -= 1000; - //client is poison clouded - if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) - G_Damage( ent, client->lastPoisonCloudedClient, client->lastPoisonCloudedClient, NULL, NULL, - LEVEL1_PCLOUD_DMG, 0, MOD_LEVEL1_PCLOUD ); - //client is poisoned if( client->ps.stats[ STAT_STATE ] & SS_POISONED ) { - int damage = ALIEN_POISON_DMG; - + int damage = ALIEN_POISON_DMG; + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) damage -= BSUIT_POISON_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) ) damage -= HELMET_POISON_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) damage -= LIGHTARMOUR_POISON_PROTECTION; @@ -828,135 +727,37 @@ void ClientTimerActions( gentity_t *ent, int msec ) 0, damage, 0, MOD_POISON ); } - //replenish alien health - if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && - level.surrenderTeam != PTE_ALIENS ) - { - int entityList[ MAX_GENTITIES ]; - vec3_t range = { LEVEL1_REGEN_RANGE, LEVEL1_REGEN_RANGE, LEVEL1_REGEN_RANGE }; - vec3_t mins, maxs; - int i, num; - gentity_t *boostEntity; - float modifier = 1.0f; - - VectorAdd( client->ps.origin, range, maxs ); - VectorSubtract( client->ps.origin, range, mins ); - - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - boostEntity = &g_entities[ entityList[ i ] ]; - - if( boostEntity->client && boostEntity->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && - boostEntity->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG ) - { - modifier = LEVEL1_REGEN_MOD; - break; - } - else if( boostEntity->s.eType == ET_BUILDABLE && - boostEntity->s.modelindex == BA_A_BOOSTER && - boostEntity->spawned && boostEntity->health > 0 ) - { - modifier = BOOSTER_REGEN_MOD; - break; - } - } - - if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] && - !level.paused && - ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) - { - ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier; - - // if completely healed, cancel retribution - if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) - { - for( i = 0; i < MAX_CLIENTS; i++ ) - ent->client->tkcredits[ i ] = 0; - } - } - - if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] ) - ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; - } - - - if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - ent->client->pers.statscounters.timealive++; - level.alienStatsCounters.timealive++; - if( G_BuildableRange( ent->client->ps.origin, 900, BA_A_OVERMIND ) ) - { - ent->client->pers.statscounters.timeinbase++; - level.alienStatsCounters.timeinbase++; - } - if( BG_ClassHasAbility( ent->client->ps.stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) - { - ent->client->pers.statscounters.dretchbasytime++; - level.alienStatsCounters.dretchbasytime++; - if( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING || ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING) - { - ent->client->pers.statscounters.jetpackusewallwalkusetime++; - level.alienStatsCounters.jetpackusewallwalkusetime++; - } - } - } - else if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - ent->client->pers.statscounters.timealive++; - level.humanStatsCounters.timealive++; - if( G_BuildableRange( ent->client->ps.origin, 900, BA_H_REACTOR ) ) - { - ent->client->pers.statscounters.timeinbase++; - level.humanStatsCounters.timeinbase++; - } - if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) ) - { - if( client->ps.pm_type == PM_JETPACK ) - { - ent->client->pers.statscounters.jetpackusewallwalkusetime++; - level.humanStatsCounters.jetpackusewallwalkusetime++; - } - } - } - - // turn off life support when a team admits defeat - if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && - level.surrenderTeam == PTE_ALIENS ) + // turn off life support when a team admits defeat + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && + level.surrenderTeam == TEAM_ALIENS ) { G_Damage( ent, NULL, NULL, NULL, NULL, - BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ), + BG_Class( client->ps.stats[ STAT_CLASS ] )->regenRate, DAMAGE_NO_ARMOR, MOD_SUICIDE ); } - else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && - level.surrenderTeam == PTE_HUMANS ) + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + level.surrenderTeam == TEAM_HUMANS ) { G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE ); } - //my new jetpack code - if( mod_jetpackFuel.value >= 10.0f ) { - //if we have jetpack and its on - if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { - //check if fuels 0 if so deactivate it if not give a 10 second fuel low warning and take JETPACK_USE_RATE from fuel - if( client->jetpackfuel <= 0.0f ) { - BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); - } else if( client->jetpackfuel < 10.0f && client->jetpackfuel > 0.0f) { - client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value; - trap_SendServerCommand( client - level.clients, "cp \"^3Fuel ^1Low!!!!!^7\nLand now.\"" ); - } else { - client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value; - } + // lose some voice enthusiasm + if( client->voiceEnthusiasm > 0.0f ) + client->voiceEnthusiasm -= VOICE_ENTHUSIASM_DECAY; + else + client->voiceEnthusiasm = 0.0f; - //if jetpack isnt active regenerate fuel and give a message when its full - } else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && !BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { - if( client->jetpackfuel > ( mod_jetpackFuel.value - 10.0f ) && client->jetpackfuel <= mod_jetpackFuel.value ) { - client->jetpackfuel = client->jetpackfuel + mod_jetpackRegen.value; - trap_SendServerCommand( client - level.clients, "cp \"^3Fuel Status: ^2Full!^7\n\"" ); - } else if( client->jetpackfuel < mod_jetpackFuel.value ) { - //regenerate some fuel - client->jetpackfuel = client->jetpackfuel + mod_jetpackRegen.value; - } + client->pers.secondsAlive++; + if( g_freeFundPeriod.integer > 0 && + client->pers.secondsAlive % g_freeFundPeriod.integer == 0 ) + { + // Give clients some credit periodically + if( G_TimeTilSuddenDeath( ) > 0 ) + { + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + G_AddCreditToClient( client, FREEKILL_ALIEN, qtrue ); + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + G_AddCreditToClient( client, FREEKILL_HUMAN, qtrue ); } } } @@ -965,21 +766,42 @@ void ClientTimerActions( gentity_t *ent, int msec ) { client->time10000 -= 10000; - if( client->ps.weapon == WP_ALEVEL3_UPG ) + if( ent->client->ps.weapon == WP_ABUILD || + ent->client->ps.weapon == WP_ABUILD2 ) { - int ammo, maxAmmo; + AddScore( ent, ALIEN_BUILDER_SCOREINC ); + } + else if( ent->client->ps.weapon == WP_HBUILD ) + { + AddScore( ent, HUMAN_BUILDER_SCOREINC ); + } - BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL ); - ammo = client->ps.ammo; + // Give score to basis that healed other aliens + if( ent->client->pers.hasHealed ) + { + if( client->ps.weapon == WP_ALEVEL1 ) + AddScore( ent, LEVEL1_REGEN_SCOREINC ); + else if( client->ps.weapon == WP_ALEVEL1_UPG ) + AddScore( ent, LEVEL1_UPG_REGEN_SCOREINC ); + + ent->client->pers.hasHealed = qfalse; + } + } - if( ammo < maxAmmo ) + // Regenerate Adv. Dragoon barbs + if( client->ps.weapon == WP_ALEVEL3_UPG ) + { + if( client->ps.ammo < BG_Weapon( WP_ALEVEL3_UPG )->maxAmmo ) + { + if( ent->timestamp + LEVEL3_BOUNCEBALL_REGEN < level.time ) { - ammo++; - client->ps.ammo = ammo; - client->ps.clips = 0; + client->ps.ammo++; + ent->timestamp = level.time; } } - } + else + ent->timestamp = level.time; + } } /* @@ -989,7 +811,6 @@ ClientIntermissionThink */ void ClientIntermissionThink( gclient_t *client ) { - client->ps.eFlags &= ~EF_TALK; client->ps.eFlags &= ~EF_FIRING; client->ps.eFlags &= ~EF_FIRING2; @@ -1020,10 +841,10 @@ void ClientEvents( gentity_t *ent, int oldEventSequence ) vec3_t dir; vec3_t point, mins; float fallDistance; - pClass_t class; + class_t class; client = ent->client; - class = client->ps.stats[ STAT_PCLASS ]; + class = client->ps.stats[ STAT_CLASS ]; if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; @@ -1047,11 +868,11 @@ void ClientEvents( gentity_t *ent, int oldEventSequence ) else if( fallDistance > 1.0f ) fallDistance = 1.0f; - damage = (int)( (float)BG_FindHealthForClass( class ) * - BG_FindFallDamageForClass( class ) * fallDistance ); + damage = (int)( (float)BG_Class( class )->health * + BG_Class( class )->fallDamage * fallDistance ); VectorSet( dir, 0, 0, 1 ); - BG_FindBBoxForClass( class, mins, NULL, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, mins, NULL, NULL, NULL, NULL ); mins[ 0 ] = mins[ 1 ] = 0.0f; VectorAdd( client->ps.origin, mins, point ); @@ -1122,8 +943,8 @@ void SendPendingPredictableEvents( playerState_t *ps ) ============== G_UnlaggedStore - Called on every server frame. Stores position data for the client at that - into client->unlaggedHist[] and the time into level.unlaggedTimes[]. + Called on every server frame. Stores position data for the client at that + into client->unlaggedHist[] and the time into level.unlaggedTimes[]. This data is used by G_UnlaggedCalc() ============== */ @@ -1132,24 +953,24 @@ void G_UnlaggedStore( void ) int i = 0; gentity_t *ent; unlagged_t *save; - + if( !g_unlagged.integer ) return; - level.unlaggedIndex++; + level.unlaggedIndex++; if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS ) level.unlaggedIndex = 0; level.unlaggedTimes[ level.unlaggedIndex ] = level.time; - + for( i = 0; i < level.maxclients; i++ ) { ent = &g_entities[ i ]; save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; - save->used = qfalse; + save->used = qfalse; if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) continue; if( ent->client->pers.connected != CON_CONNECTED ) - continue; + continue; VectorCopy( ent->r.mins, save->mins ); VectorCopy( ent->r.maxs, save->maxs ); VectorCopy( ent->s.pos.trBase, save->origin ); @@ -1160,7 +981,7 @@ void G_UnlaggedStore( void ) /* ============== G_UnlaggedClear - + Mark all unlaggedHist[] markers for this client invalid. Useful for preventing teleporting and death. ============== @@ -1185,14 +1006,14 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) { int i = 0; gentity_t *ent; - int startIndex = level.unlaggedIndex; - int stopIndex = -1; - int frameMsec = 0; - float lerp = 0.5f; + int startIndex; + int stopIndex; + int frameMsec; + float lerp; if( !g_unlagged.integer ) return; - + // clear any calculated values from a previous run for( i = 0; i < level.maxclients; i++ ) { @@ -1200,13 +1021,18 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) ent->client->unlaggedCalc.used = qfalse; } - for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + // client is on the current frame, no need for unlagged + if( level.unlaggedTimes[ level.unlaggedIndex ] <= time ) + return; + + startIndex = level.unlaggedIndex; + for( i = 1; i < MAX_UNLAGGED_MARKERS; i++ ) { - if( level.unlaggedTimes[ startIndex ] <= time ) - break; stopIndex = startIndex; if( --startIndex < 0 ) startIndex = MAX_UNLAGGED_MARKERS - 1; + if( level.unlaggedTimes[ startIndex ] <= time ) + break; } if( i == MAX_UNLAGGED_MARKERS ) { @@ -1214,20 +1040,13 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) // just use the oldest marker with no lerping lerp = 0.0f; } - - // client is on the current frame, no need for unlagged - if( stopIndex == -1 ) - return; - - // lerp between two markers - frameMsec = level.unlaggedTimes[ stopIndex ] - - level.unlaggedTimes[ startIndex ]; - if( frameMsec > 0 ) + else { - lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) - / ( float )frameMsec; + // lerp between two markers + frameMsec = level.unlaggedTimes[ stopIndex ] - level.unlaggedTimes[ startIndex ]; + lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) / ( float )frameMsec; } - + for( i = 0; i < level.maxclients; i++ ) { ent = &g_entities[ i ]; @@ -1243,13 +1062,13 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) continue; // between two unlagged markers - VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, + VectorLerp2( lerp, ent->client->unlaggedHist[ startIndex ].mins, ent->client->unlaggedHist[ stopIndex ].mins, ent->client->unlaggedCalc.mins ); - VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, + VectorLerp2( lerp, ent->client->unlaggedHist[ startIndex ].maxs, ent->client->unlaggedHist[ stopIndex ].maxs, ent->client->unlaggedCalc.maxs ); - VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, + VectorLerp2( lerp, ent->client->unlaggedHist[ startIndex ].origin, ent->client->unlaggedHist[ stopIndex ].origin, ent->client->unlaggedCalc.origin ); @@ -1268,10 +1087,10 @@ void G_UnlaggedOff( void ) { int i = 0; gentity_t *ent; - + if( !g_unlagged.integer ) return; - + for( i = 0; i < level.maxclients; i++ ) { ent = &g_entities[ i ]; @@ -1304,13 +1123,13 @@ void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ) int i = 0; gentity_t *ent; unlagged_t *calc; - + if( !g_unlagged.integer ) return; if( !attacker->client->pers.useUnlagged ) return; - + for( i = 0; i < level.maxclients; i++ ) { ent = &g_entities[ i ]; @@ -1331,7 +1150,7 @@ void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ) float maxRadius = ( r1 > r2 ) ? r1 : r2; if( Distance( muzzle, calc->origin ) > range + maxRadius ) - continue; + continue; } // create a backup of the real positions @@ -1355,7 +1174,7 @@ void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ) the current time, but only updates other player's positions up to the postition sent in the most recent snapshot. - This allows player X to essentially "move through" the position of player Y + This allows player X to essentially "move through" the position of player Y when player X's cmd is processed with Pmove() on the server. This is because player Y was clipping player X's Pmove() on his client, but when the same cmd is processed with Pmove on the server it is not clipped. @@ -1378,6 +1197,7 @@ static void G_UnlaggedDetectCollisions( gentity_t *ent ) if( !g_unlagged.integer ) return; + if( !ent->client->pers.useUnlagged ) return; @@ -1400,47 +1220,11 @@ static void G_UnlaggedDetectCollisions( gentity_t *ent ) trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, MASK_PLAYERSOLID ); if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS ) - g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; + g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; G_UnlaggedOff( ); } -/* -============== -ClientGradualFunds - -g_gradualFreeFunds values: -0 - disabled -1 - vanilla behavior -2 - 1 and counters don't reset on death or evolution -3 - 2 and funds are given even during SD -============== -*/ -static void ClientGradualFunds( gentity_t *ent ) -{ - if( !g_gradualFreeFunds.integer ) - return; - - if( ent->client->pers.lastFreekillTime + FREEKILL_PERIOD >= level.time ) - return; - - if( g_suddenDeath.integer && g_gradualFreeFunds.integer < 3 ) - return; - - switch ( ent->client->ps.stats[ STAT_PTEAM ] ) - { - case PTE_ALIENS: - G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue ); - break; - - case PTE_HUMANS: - G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue ); - break; - } - - ent->client->pers.lastFreekillTime += FREEKILL_PERIOD; -} - /* ============== ClientThink @@ -1459,7 +1243,6 @@ void ClientThink_real( gentity_t *ent ) int oldEventSequence; int msec; usercmd_t *ucmd; - int real_pm_type; client = ent->client; @@ -1470,34 +1253,12 @@ void ClientThink_real( gentity_t *ent ) // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; - if( client->pers.paused ) - ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = ucmd->buttons = 0; - // sanity check the command time to prevent speedup cheating if( ucmd->serverTime > level.time + 200 ) - { ucmd->serverTime = level.time + 200; -// G_Printf("serverTime <<<<<\n" ); - } if( ucmd->serverTime < level.time - 1000 ) - { ucmd->serverTime = level.time - 1000; -// G_Printf("serverTime >>>>>\n" ); - } - - // ucmd->serverTime is a client predicted value, but it works for making a - // replacement for client->ps.ping when in SPECTATOR_FOLLOW - client->pers.ping = level.time - ucmd->serverTime; - - // account for the one frame of delay on client side - client->pers.ping -= level.time - level.previousTime; - - // account for the time that's elapsed since the last ClientEndFrame() - client->pers.ping += trap_Milliseconds( ) - level.frameMsec; - - if( client->pers.ping < 0 ) - client->pers.ping = 0; msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want @@ -1511,9 +1272,15 @@ void ClientThink_real( gentity_t *ent ) client->unlaggedTime = ucmd->serverTime; if( pmove_msec.integer < 8 ) + { trap_Cvar_Set( "pmove_msec", "8" ); + trap_Cvar_Update(&pmove_msec); + } else if( pmove_msec.integer > 33 ) + { trap_Cvar_Set( "pmove_msec", "33" ); + trap_Cvar_Update(&pmove_msec); + } if( pmove_fixed.integer || client->pers.pmoveFixed ) { @@ -1527,21 +1294,12 @@ void ClientThink_real( gentity_t *ent ) // if( level.intermissiontime ) { - if( level.mapRotationVoteTime ) - { - SpectatorThink( ent, ucmd ); - return; - } - ClientIntermissionThink( client ); return; } - if( client->pers.teamSelection != PTE_NONE && client->pers.joinedATeam ) - G_UpdatePTRConnection( client ); - // spectators don't do much - if( client->sess.sessionTeam == TEAM_SPECTATOR ) + if( client->sess.spectatorState != SPECTATOR_NOT ) { if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) return; @@ -1550,20 +1308,19 @@ void ClientThink_real( gentity_t *ent ) return; } + G_namelog_update_score( client ); + // check for inactivity timer, but never drop the local client of a non-dedicated server - if( !ClientInactivityTimer( client ) ) + if( !ClientInactivityTimer( ent ) ) return; - // calculate where ent is currently seeing all the other active clients + // calculate where ent is currently seeing all the other active clients G_UnlaggedCalc( ent->client->unlaggedTime, ent ); if( client->noclip ) client->ps.pm_type = PM_NOCLIP; else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) client->ps.pm_type = PM_DEAD; - else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING || - client->ps.stats[ STAT_STATE ] & SS_HOVELING ) - client->ps.pm_type = PM_FREEZE; else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || client->ps.stats[ STAT_STATE ] & SS_GRABBED ) client->ps.pm_type = PM_GRABBED; @@ -1572,31 +1329,33 @@ void ClientThink_real( gentity_t *ent ) else client->ps.pm_type = PM_NORMAL; - // paused - real_pm_type = client->ps.pm_type; - if ( level.paused ) client->ps.pm_type = PM_SPECTATOR; - - if( client->ps.stats[ STAT_STATE ] & SS_GRABBED && + if( ( client->ps.stats[ STAT_STATE ] & SS_GRABBED ) && client->grabExpiryTime < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; - if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED && + if( ( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) && client->lastLockTime + LOCKBLOB_LOCKTIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; - if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED && + if( ( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED ) && client->lastSlowTime + ABUILDER_BLOB_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; - client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime; - - if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED && - client->lastBoostedTime + BOOST_TIME < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; + // Update boosted state flags + client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTEDWARNING; + if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + { + if( level.time - client->boostedTime >= BOOST_TIME ) + client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; + else if( level.time - client->boostedTime >= BOOST_WARN_TIME ) + client->ps.stats[ STAT_STATE ] |= SS_BOOSTEDWARNING; + } - if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && - client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; + // Check if poison cloud has worn off + if( ( client->ps.eFlags & EF_POISONCLOUDED ) && + BG_PlayerPoisonCloudTime( &client->ps ) - level.time + + client->lastPoisonCloudedTime <= 0 ) + client->ps.eFlags &= ~EF_POISONCLOUDED; if( client->ps.stats[ STAT_STATE ] & SS_POISONED && client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) @@ -1608,13 +1367,13 @@ void ClientThink_real( gentity_t *ent ) BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) { //if currently using a medkit or have no need for a medkit now - if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE || + if( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X || ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) { BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); } - else if( client->ps.stats[ STAT_HEALTH ] > 0 && !level.paused ) + else if( client->ps.stats[ STAT_HEALTH ] > 0 ) { //remove anti toxin BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); @@ -1623,7 +1382,7 @@ void ClientThink_real( gentity_t *ent ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; - client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE; + client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; client->lastMedKitTime = level.time; client->medKitHealthToRestore = client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; @@ -1634,6 +1393,102 @@ void ClientThink_real( gentity_t *ent ) } } + // Replenish alien health + if( level.surrenderTeam != client->pers.teamSelection && + ent->nextRegenTime >= 0 && ent->nextRegenTime < level.time ) + { + float regenRate = + BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->regenRate; + + if( ent->health <= 0 || ent->nextRegenTime < 0 || regenRate == 0 ) + ent->nextRegenTime = -1; // no regen + else + { + int entityList[ MAX_GENTITIES ]; + int i, num; + int count, interval; + vec3_t range, mins, maxs; + float modifier = 1.0f; + + VectorSet( range, REGEN_BOOST_RANGE, REGEN_BOOST_RANGE, + REGEN_BOOST_RANGE ); + VectorAdd( client->ps.origin, range, maxs ); + VectorSubtract( client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + gentity_t *boost = &g_entities[ entityList[ i ] ]; + + if( Distance( client->ps.origin, boost->r.currentOrigin ) > REGEN_BOOST_RANGE ) + continue; + + if( modifier < BOOSTER_REGEN_MOD && boost->s.eType == ET_BUILDABLE && + boost->s.modelindex == BA_A_BOOSTER && boost->spawned && + boost->health > 0 && boost->powered ) + { + modifier = BOOSTER_REGEN_MOD; + continue; + } + + if( boost->s.eType == ET_PLAYER && boost->client && + boost->client->pers.teamSelection == + ent->client->pers.teamSelection && boost->health > 0 ) + { + class_t class = boost->client->ps.stats[ STAT_CLASS ]; + qboolean didBoost = qfalse; + + if( class == PCL_ALIEN_LEVEL1 && modifier < LEVEL1_REGEN_MOD ) + { + modifier = LEVEL1_REGEN_MOD; + didBoost = qtrue; + } + else if( class == PCL_ALIEN_LEVEL1_UPG && + modifier < LEVEL1_UPG_REGEN_MOD ) + { + modifier = LEVEL1_UPG_REGEN_MOD; + didBoost = qtrue; + } + + if( didBoost && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) + boost->client->pers.hasHealed = qtrue; + } + } + + // Transmit heal rate to the client so it can be displayed on the HUD + client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; + client->ps.stats[ STAT_STATE ] &= ~( SS_HEALING_2X | SS_HEALING_3X ); + if( modifier == 1.0f && !G_FindCreep( ent ) ) + { + client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + modifier *= ALIEN_REGEN_NOCREEP_MOD; + } + else if( modifier >= 3.0f ) + client->ps.stats[ STAT_STATE ] |= SS_HEALING_3X; + else if( modifier >= 2.0f ) + client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; + + interval = 1000 / ( regenRate * modifier ); + // if recovery interval is less than frametime, compensate + count = 1 + ( level.time - ent->nextRegenTime ) / interval; + ent->nextRegenTime += count * interval; + + if( ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) + { + ent->health += count; + client->ps.stats[ STAT_HEALTH ] = ent->health; + + // if at max health, clear damage counters + if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) + { + ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + } + } + } + } + if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) { @@ -1650,10 +1505,11 @@ void ClientThink_real( gentity_t *ent ) } // set speed - client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); - - if( client->pers.paused ) - client->ps.speed = 0; + if( client->ps.pm_type == PM_NOCLIP ) + client->ps.speed = client->pers.flySpeed; + else + client->ps.speed = g_speed.value * + BG_Class( client->ps.stats[ STAT_CLASS ] )->speed; if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; @@ -1669,7 +1525,7 @@ void ClientThink_real( gentity_t *ent ) } //switch jetpack off if no reactor - if( !level.reactorPresent ) + if( !G_Reactor( ) ) BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); } @@ -1678,50 +1534,19 @@ void ClientThink_real( gentity_t *ent ) memset( &pm, 0, sizeof( pm ) ); - if( !( ucmd->buttons & BUTTON_TALK ) && !( client->ps.pm_flags & PMF_RESPAWNED ) ) - { - switch( client->ps.weapon ) - { - case WP_ALEVEL0: - if( client->ps.weaponTime <= 0 ) - pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent ); - break; - - case WP_ALEVEL1: - case WP_ALEVEL1_UPG: - CheckGrabAttack( ent ); - break; - - case WP_ALEVEL3: - case WP_ALEVEL3_UPG: - if( client->ps.weaponTime <= 0 ) - pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent ); - break; - - default: - break; - } - } - if( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } + + // clear fall velocity before every pmove + client->pmext.fallVelocity = 0.0f; pm.ps = &client->ps; pm.pmext = &client->pmext; pm.cmd = *ucmd; - - if( pm.ps->pm_type == PM_DEAD ) - pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY; - - if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING || - pm.ps->stats[ STAT_STATE ] & SS_HOVELING ) - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; - else - pm.tracemask = MASK_PLAYERSOLID; - + pm.tracemask = ent->clipmask; pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; @@ -1734,8 +1559,7 @@ void ClientThink_real( gentity_t *ent ) // moved from after Pmove -- potentially the cause of // future triggering bugs - if( !ent->client->noclip ) - G_TouchTriggers( ent ); + G_TouchTriggers( ent ); Pmove( &pm ); @@ -1745,13 +1569,61 @@ void ClientThink_real( gentity_t *ent ) if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; - if ( level.paused ) client->ps.pm_type = real_pm_type; - + VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles ); if( g_smoothClients.integer ) BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); else BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + switch( client->ps.weapon ) + { + case WP_ALEVEL0: + if( !CheckVenomAttack( ent ) ) + { + client->ps.weaponstate = WEAPON_READY; + } + else + { + client->ps.generic1 = WPM_PRIMARY; + G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); + } + break; + + case WP_ALEVEL1: + case WP_ALEVEL1_UPG: + CheckGrabAttack( ent ); + break; + + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + if( !CheckPounceAttack( ent ) ) + { + client->ps.weaponstate = WEAPON_READY; + } + else + { + client->ps.generic1 = WPM_SECONDARY; + G_AddEvent( ent, EV_FIRE_WEAPON2, 0 ); + } + break; + + case WP_ALEVEL4: + // If not currently in a trample, reset the trample bookkeeping data + if( !( client->ps.pm_flags & PMF_CHARGE ) && client->trampleBuildablesHitPos ) + { + ent->client->trampleBuildablesHitPos = 0; + memset( ent->client->trampleBuildablesHit, 0, sizeof( ent->client->trampleBuildablesHit ) ); + } + break; + + case WP_HBUILD: + CheckCkitRepair( ent ); + break; + + default: + break; + } + SendPendingPredictableEvents( &ent->client->ps ); if( !( ent->client->ps.eFlags & EF_FIRING ) ) @@ -1770,7 +1642,7 @@ void ClientThink_real( gentity_t *ent ) // touch other objects ClientImpacts( ent, &pm ); - + // execute client events ClientEvents( ent, oldEventSequence ); @@ -1779,12 +1651,12 @@ void ClientThink_real( gentity_t *ent ) // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - VectorCopy( ent->client->ps.origin, ent->s.origin ); + VectorCopy( ent->client->ps.origin, ent->s.pos.trBase ); // save results of triggers and client events if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; - + // Don't think anymore if dead if( client->ps.stats[ STAT_HEALTH ] <= 0 ) return; @@ -1794,121 +1666,84 @@ void ClientThink_real( gentity_t *ent ) client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; - if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) && + if( ( client->buttons & BUTTON_USE_EVOLVE ) && !( client->oldbuttons & BUTTON_USE_EVOLVE ) && client->ps.stats[ STAT_HEALTH ] > 0 ) { trace_t trace; vec3_t view, point; gentity_t *traceEnt; - if( client->ps.stats[ STAT_STATE ] & SS_HOVELING ) - { - gentity_t *hovel = client->hovel; +#define USE_OBJECT_RANGE 64 - //only let the player out if there is room - if( !AHovel_Blocked( hovel, ent, qtrue ) ) - { - //prevent lerping - client->ps.eFlags ^= EF_TELEPORT_BIT; - client->ps.eFlags &= ~EF_NODRAW; - G_UnlaggedClear( ent ); + int entityList[ MAX_GENTITIES ]; + vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; + vec3_t mins, maxs; + int i, num; - //client leaves hovel - client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; + // look for object infront of player + AngleVectors( client->ps.viewangles, view, NULL, NULL ); + VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); + trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); - //hovel is empty - G_SetBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); - hovel->active = qfalse; - } - else - { - //exit is blocked - G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); - } - } + traceEnt = &g_entities[ trace.entityNum ]; + + if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) + traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context else { -#define USE_OBJECT_RANGE 64 - - int entityList[ MAX_GENTITIES ]; - vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; - vec3_t mins, maxs; - int i, num; + //no entity in front of player - do a small area search - //TA: look for object infront of player - AngleVectors( client->ps.viewangles, view, NULL, NULL ); - VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); - trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); - - traceEnt = &g_entities[ trace.entityNum ]; + VectorAdd( client->ps.origin, range, maxs ); + VectorSubtract( client->ps.origin, range, mins ); - if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) - traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context - else + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) { - //no entity in front of player - do a small area search - - VectorAdd( client->ps.origin, range, maxs ); - VectorSubtract( client->ps.origin, range, mins ); + traceEnt = &g_entities[ entityList[ i ] ]; - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) + if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) { - traceEnt = &g_entities[ entityList[ i ] ]; - - if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) - { - traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context - break; - } + traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context + break; } + } - if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( i == num && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], + client->pers.credit, + g_alienStage.integer ) ) { - if( BG_UpgradeClassAvailable( &client->ps ) ) - { - //no nearby objects and alien - show class menu - G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); - } - else - { - //flash frags - G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); - } + //no nearby objects and alien - show class menu + G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); + } + else + { + //flash frags + G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); } } } } - if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu ) - { - G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY ); - - client->retriggerArmouryMenu = 0; - } + client->ps.persistant[ PERS_BP ] = G_GetBuildPoints( client->ps.origin, + client->ps.stats[ STAT_TEAM ] ); + client->ps.persistant[ PERS_MARKEDBP ] = G_GetMarkedBuildPoints( client->ps.origin, + client->ps.stats[ STAT_TEAM ] ); - // Give clients some credit periodically - ClientGradualFunds( ent ); + if( client->ps.persistant[ PERS_BP ] < 0 ) + client->ps.persistant[ PERS_BP ] = 0; // perform once-a-second actions ClientTimerActions( ent, msec ); - + if( ent->suicideTime > 0 && ent->suicideTime < level.time ) { - ent->flags &= ~FL_GODMODE; ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, ent, ent, 100000, MOD_SUICIDE ); ent->suicideTime = 0; } - if( client->pers.bubbleTime && client->pers.bubbleTime < level.time ) - { - gentity_t *bubble; - - client->pers.bubbleTime = level.time + 500; - bubble = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT ); - bubble->s.clientNum = ent->s.clientNum; - } } /* @@ -1953,52 +1788,30 @@ SpectatorClientEndFrame void SpectatorClientEndFrame( gentity_t *ent ) { gclient_t *cl; - int clientNum, flags; + int clientNum; int score, ping; - vec3_t spawn_origin, spawn_angles; // if we are doing a chase cam or a remote view, grab the latest info if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { clientNum = ent->client->sess.spectatorClient; - - if( clientNum >= 0 ) + if( clientNum >= 0 && clientNum < level.maxclients ) { cl = &level.clients[ clientNum ]; - if( cl->pers.connected == CON_CONNECTED ) { - - if( cl -> sess.spectatorState != SPECTATOR_FOLLOW ) - { - flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) | - ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) ); - score = ent->client->ps.persistant[ PERS_SCORE ]; - ping = ent->client->ps.ping; - ent->client->ps = cl->ps; - ent->client->ps.persistant[ PERS_SCORE ] = score; - ent->client->ps.ping = ping; - ent->client->ps.eFlags = flags; - ent->client->ps.pm_flags |= PMF_FOLLOW; - ent->client->ps.pm_flags &= ~PMF_QUEUED; - } - else //we are stickyspec-spectating someone who is spectating someone else - { - ent->client->ps.clientNum = (g_entities + clientNum)->s.number; - ent->client->ps.commandTime = cl->ps.commandTime; - ent->client->ps.weapon = 0; - ent->client->ps.pm_flags |= PMF_FOLLOW; - ent->client->ps.stats[ STAT_PCLASS ] = PCL_NONE; - - if( cl->pers.teamSelection == PTE_ALIENS ) - G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( cl->pers.teamSelection == PTE_HUMANS ) - G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); - - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, ent->client->ps.origin ); - G_SetClientViewAngle( ent, spawn_angles ); - } + score = ent->client->ps.persistant[ PERS_SCORE ]; + ping = ent->client->ps.ping; + + // Copy + ent->client->ps = cl->ps; + + // Restore + ent->client->ps.persistant[ PERS_SCORE ] = score; + ent->client->ps.ping = ping; + + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.pm_flags &= ~PMF_QUEUED; } } } @@ -2015,20 +1828,12 @@ while a slow client may have multiple ClientEndFrame between ClientThink. */ void ClientEndFrame( gentity_t *ent ) { - clientPersistant_t *pers; - - if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) { SpectatorClientEndFrame( ent ); return; } - pers = &ent->client->pers; - - // save a copy of certain playerState values in case of SPECTATOR_FOLLOW - pers->score = ent->client->ps.persistant[ PERS_SCORE ]; - pers->credit = ent->client->ps.persistant[ PERS_CREDIT ]; - // // If the end of unit layout is displayed, don't give // the player any normal movement attributes @@ -2048,8 +1853,6 @@ void ClientEndFrame( gentity_t *ent ) else ent->s.eFlags &= ~EF_CONNECTION; - ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health... - // respawn if dead if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime ) respawn( ent ); @@ -2064,5 +1867,3 @@ void ClientEndFrame( gentity_t *ent ) SendPendingPredictableEvents( &ent->client->ps ); } - - diff --git a/src/game/g_admin.c b/src/game/g_admin.c index dc19530..afc40cb 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -1,6 +1,7 @@ /* =========================================================================== -Copyright (C) 2004-2006 Tony J. White +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. @@ -11,12 +12,12 @@ and Travis Maurer. The functionality of this code mimics the behaviour of the currently inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2) -by Ryan Mannion. However, shrubet was a closed-source project and -none of it's code has been copied, only it's functionality. +by Ryan Mannion. However, shrubet was a closed-source project and +none of its code has been copied, only its functionality. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -25,8 +26,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -36,420 +37,270 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA static char g_bfb[ 32000 ]; // note: list ordered alphabetically -g_admin_cmd_t g_admin_cmds[ ] = - { - {"adjustban", G_admin_adjustban, "ban", - "change the duration or reason of a ban. time is specified as numbers " - "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes)," - " or seconds if no units are specified. if the duration is" - " preceded by a + or -, the ban duration will be extended or shortened by" - " the specified amount", - "[^3ban#^7] (^5duration^7) (^5reason^7)" +g_admin_cmd_t g_admin_cmds[ ] = + { + {"addlayout", G_admin_addlayout, qfalse, "addlayout", + "place layout elements into the game. the elements are specified by a " + "union of filtered layouts. the syntax is demonstrated by an example: " + "^5reactor,telenode|westside+alien|sewers^7 will place only the " + "reactor and telenodes from the westside layout, and also all alien " + "layout elements from the sewers layout", + "[^3layoutelements^7]" + }, + + {"adjustban", G_admin_adjustban, qfalse, "ban", + "change the IP address mask, duration or reason of a ban. mask is " + "prefixed with '/'. duration is specified as numbers followed by units " + " 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes), or seconds if " + " no unit is specified. if the duration is preceded by a + or -, the " + "ban duration will be extended or shortened by the specified amount", + "[^3ban#^7] (^5/mask^7) (^5duration^7) (^5reason^7)" }, - - {"adminlog", G_admin_adminlog, "adminlog", - "list recent admin activity", - "(^5start id#|name|!command|-skip#^7) (^5search skip#^7)" + + {"adminhelp", G_admin_adminhelp, qtrue, "adminhelp", + "display admin commands available to you or help on a specific command", + "(^5command^7)" }, - {"admintest", G_admin_admintest, "admintest", + {"admintest", G_admin_admintest, qfalse, "admintest", "display your current admin level", "" }, - {"allowbuild", G_admin_denybuild, "denybuild", + {"allowbuild", G_admin_denybuild, qfalse, "denybuild", "restore a player's ability to build", "[^3name|slot#^7]" }, - {"allowweapon", G_admin_denyweapon, "denyweapon", - "restore a player's ability to use a weapon or class", - "[^3name|slot#^7] [^3class|weapon|all^7]" - }, - - {"allready", G_admin_allready, "allready", + {"allready", G_admin_allready, qfalse, "allready", "makes everyone ready in intermission", "" }, - {"ban", G_admin_ban, "ban", + {"ban", G_admin_ban, qfalse, "ban", "ban a player by IP and GUID with an optional expiration time and reason." - " time is specified as numbers followed by units 'w' (weeks), 'd' " + " duration is specified as numbers followed by units 'w' (weeks), 'd' " "(days), 'h' (hours) or 'm' (minutes), or seconds if no units are " "specified", - "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)" - }, - - {"buildlog", G_admin_buildlog, "buildlog", - "display a list of recent builds and deconstructs, optionally specifying" - " a team", - "(^5xnum^7) (^5#skip^7) (^5-name|num^7) (^5a|h^7)" - "\n ^3Example:^7 '!buildlog #10 h' skips 10 events, then shows the previous 10 events affecting human buildables" - }, - - {"cancelvote", G_admin_cancelvote, "cancelvote", - "cancel a vote taking place", - "" - }, - - {"cp", G_admin_cp, "cp", - "display a CP message to users, optionally specifying team(s) to send to", - "(-AHS) [^3message^7]" - }, - - {"decon", G_admin_decon, "decon", - "Reverts a decon previously made and ban the user for the time specified in g_deconBanTime", - "[^3name|slot#^7]" + "[^3name|slot#|IP(/mask)^7] (^5duration^7) (^5reason^7)" }, - {"demo", G_admin_demo, "demo", - "turn admin chat off for the caller so it does not appear in demos. " - "this is a toggle use !demo again to turn warnings back on", + {"builder", G_admin_builder, qtrue, "builder", + "show who built a structure", "" }, - {"denybuild", G_admin_denybuild, "denybuild", - "take away a player's ability to build", - "[^3name|slot#^7]" + {"buildlog", G_admin_buildlog, qfalse, "buildlog", + "show buildable log", + "(^5name|slot#^7) (^5id^7)" }, - {"designate", G_admin_designate, "designate", - "give the player designated builder privileges", - "[^3name|slot#^7]" + {"cancelvote", G_admin_endvote, qfalse, "cancelvote", + "cancel a vote taking place", + "(^5a|h^7)" }, - {"devmap", G_admin_devmap, "devmap", - "load a map with cheats (and optionally force layout)", + {"changemap", G_admin_changemap, qfalse, "changemap", + "load a map (and optionally force layout)", "[^3mapname^7] (^5layout^7)" }, - {"denyweapon", G_admin_denyweapon, "denyweapon", - "take away a player's ability to use a weapon or class", - "[^3name|slot#^7] [^3class|weapon^7]" - }, - - {"drop", G_admin_drop, "drop", - "kick a client from the server without log", - "[^3name|slot#^7] [^3message^7]" - }, - - {"flag", G_admin_flag, "flag", - "add an admin flag to a player, prefix flag with '-' to disallow the flag. " - "console can use this command on admin levels by prefacing a '*' to the admin level value.", - "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]" - }, - - {"flaglist", G_admin_flaglist, "flag", - "list all flags understood by this server", - "" - }, - - {"help", G_admin_help, "help", - "display commands available to you or help on a specific command", - "(^5command^7)" + {"denybuild", G_admin_denybuild, qfalse, "denybuild", + "take away a player's ability to build", + "[^3name|slot#^7]" }, - {"info", G_admin_info, "info", - "display the contents of server info files", - "(^5subject^7)" - }, - - {"invisible", G_admin_invisible, "invisible", - "hides a player so they cannot be seen in playerlists", - "" - }, - - {"kick", G_admin_kick, "kick", + {"kick", G_admin_kick, qfalse, "kick", "kick a player with an optional reason", "[^3name|slot#^7] (^5reason^7)" }, - - {"L0", G_admin_L0, "l0", - "Sets a level 1 to level 0", - "[^3name|slot#^7]" - }, - - {"L1", G_admin_L1, "l1", - "Sets a level 0 to level 1", - "[^3name|slot#^7]" - }, - - {"layoutsave", G_admin_layoutsave, "layoutsave", - "save a map layout", - "[^3mapname^7]" - }, - - {"listadmins", G_admin_listadmins, "listadmins", + + {"listadmins", G_admin_listadmins, qtrue, "listadmins", "display a list of all server admins and their levels", - "(^5name|start admin#^7) (^5minimum level to display^7)" + "(^5name^7) (^5start admin#^7)" }, - - {"listlayouts", G_admin_listlayouts, "listlayouts", + + {"listlayouts", G_admin_listlayouts, qtrue, "listlayouts", "display a list of all available layouts for a map", "(^5mapname^7)" }, - {"listplayers", G_admin_listplayers, "listplayers", + {"listplayers", G_admin_listplayers, qtrue, "listplayers", "display a list of players, their client numbers and their levels", "" }, - - {"listmaps", G_admin_listmaps, "listmaps", - "display a list of available maps on the server", - "(^5map name^7)" - }, - - {"lock", G_admin_lock, "lock", + + {"lock", G_admin_lock, qfalse, "lock", "lock a team to prevent anyone from joining it", "[^3a|h^7]" }, - - {"map", G_admin_map, "map", - "load a map (and optionally force layout)", - "[^3mapname^7] (^5layout^7)" - }, - - {"maplog", G_admin_maplog, "maplog", - "show recently played maps", - "" - }, - {"mute", G_admin_mute, "mute", + {"mute", G_admin_mute, qfalse, "mute", "mute a player", - "[^3name|slot#^7] (Duration)" + "[^3name|slot#^7]" }, - - {"namelog", G_admin_namelog, "namelog", + + {"namelog", G_admin_namelog, qtrue, "namelog", "display a list of names used by recently connected players", - "(^5name^7)" + "(^5name|IP(/mask)^7) (start namelog#)" }, - {"nextmap", G_admin_nextmap, "nextmap", + {"nextmap", G_admin_nextmap, qfalse, "nextmap", "go to the next map in the cycle", "" }, - {"nobuild", G_admin_nobuild, "nobuild", - "set nobuild markers to prevent players from building in an area", - "(^5area^7) (^5height^7)" + {"passvote", G_admin_endvote, qfalse, "passvote", + "pass a vote currently taking place", + "(^5a|h^7)" }, - {"passvote", G_admin_passvote, "passvote", - "pass a vote currently taking place", + {"pause", G_admin_pause, qfalse, "pause", + "Pause (or unpause) the game.", "" }, - - {"pause", G_admin_pause, "pause", - "prevent a player from interacting with the game." - " * will pause all players, using no argument will pause game clock", - "(^5name|slot|*^7)" - }, - - {"putteam", G_admin_putteam, "putteam", + {"putteam", G_admin_putteam, qfalse, "putteam", "move a player to a specified team", - "[^3name|slot#^7] [^3h|a|s^7] (^3duration^7)" + "[^3name|slot#^7] [^3h|a|s^7]" }, - {"readconfig", G_admin_readconfig, "readconfig", + {"readconfig", G_admin_readconfig, qfalse, "readconfig", "reloads the admin config file and refreshes permission flags", "" }, - - {"register", G_admin_register, "register", - "Registers your name to protect it from being used by others or updates your admin name to your current name.", - "" - }, - {"rename", G_admin_rename, "rename", + {"rename", G_admin_rename, qfalse, "rename", "rename a player", "[^3name|slot#^7] [^3new name^7]" }, - {"restart", G_admin_restart, "restart", + {"restart", G_admin_restart, qfalse, "restart", "restart the current map (optionally using named layout or keeping/switching teams)", "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)" }, - {"revert", G_admin_revert, "revert", - "revert one or more buildlog events, optionally of only one team", - "(^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)" - "\n ^3Example:^7 '!revert x5 h' reverts the last 5 events affecting human buildables" + {"revert", G_admin_revert, qfalse, "revert", + "revert buildables to a given time", + "[^3id^7]" }, - {"rotation", G_admin_listrotation, "rotation", - "display a list of maps that are in the active map rotation", - "" + {"setdevmode", G_admin_setdevmode, qfalse, "setdevmode", + "switch developer mode on or off", + "[^3on|off^7]" }, - {"seen", G_admin_seen, "seen", - "find the last time a player was on the server", - "[^3name|admin#^7]" + {"setivo", G_admin_setivo, qfalse, "setivo", + "set an intermission view override", + "[^3s|a|h^7]" }, - {"setlevel", G_admin_setlevel, "setlevel", + {"setlevel", G_admin_setlevel, qfalse, "setlevel", "sets the admin level of a player", "[^3name|slot#|admin#^7] [^3level^7]" }, - {"showbans", G_admin_showbans, "showbans", - "display a (partial) list of active bans", - "(^5start at ban#^7) (^5name|IP|'-subnet'^7)" + {"setnextmap", G_admin_setnextmap, qfalse, "setnextmap", + "set the next map (and, optionally, a forced layout)", + "[^3mapname^7] (^5layout^7)" }, - {"slap", G_admin_slap, "slap", - "Do damage to a player, and send them flying", - "[^3name|slot^7] (damage)" + {"showbans", G_admin_showbans, qtrue, "showbans", + "display a (partial) list of active bans", + "(^5name|IP(/mask)^7) (^5start at ban#^7)" }, - {"spec999", G_admin_spec999, "spec999", + {"spec999", G_admin_spec999, qfalse, "spec999", "move 999 pingers to the spectator team", - "" - }, - - {"specme", G_admin_putmespec, "specme", - "moves you to the spectators", - "" - }, - - {"subnetban", G_admin_subnetban, "subnetban", - "Add or change a subnet mask on a ban", - "[^3ban#^7] [^5CIDR mask^7]" - "\n ^3Example:^7 '!subnetban 10 16' changes ban #10 to be a ban on XXX.XXX.*.*" - "\n ^3Example:^7 '!subnetban 10 24' changes ban #10 to be a ban on XXX.XXX.XXX.*" - "\n ^3Example:^7 '!subnetban 10 32' changes ban #10 to be a regular (non-subnet) ban" - "\n ^1WARNING:^7 Use of this command may make your admin.dat incompatible with other game.qvms" - }, + ""}, - {"suspendban", G_admin_suspendban, "ban", - "suspend a ban for a length of time. time is specified as numbers " - "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes)," - " or seconds if no units are specified", - "[^5ban #^7] [^5length^7]" - }, - - {"time", G_admin_time, "time", + {"time", G_admin_time, qtrue, "time", "show the current local server time", - "" + ""}, + {"transform", G_admin_transform, qfalse, "magic", + "change a human player to a different player model", + "[^3name|slot#^7] [^3player model^7]" }, - {"unban", G_admin_unban, "ban", + {"unban", G_admin_unban, qfalse, "ban", "unbans a player specified by the slot as seen in showbans", "[^3ban#^7]" }, - - {"undesignate", G_admin_designate, "designate", - "revoke designated builder privileges", - "[^3name|slot#^7]" - }, - - {"unflag", G_admin_flag, "flag", - "clears an admin flag from a player. " - "console can use this command on admin levels by prefacing a '*' to the admin level value.", - "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]" - }, - {"unlock", G_admin_unlock, "lock", + {"unlock", G_admin_lock, qfalse, "lock", "unlock a locked team", "[^3a|h^7]" }, - - {"unmute", G_admin_mute, "mute", + + {"unmute", G_admin_mute, qfalse, "mute", "unmute a muted player", "[^3name|slot#^7]" }, - {"unpause", G_admin_pause, "pause", - "allow a player to interact with the game." - " * will unpause all players, using no argument will unpause game clock", - "(^5name|slot|*^7)" - }, - - { - "warn", G_admin_warn, "warn", - "Warn a player to cease or face admin intervention", - "[^3name|slot#^7] [reason]" - }, - - {"setdevmode", G_admin_setdevmode, "setdevmode", - "switch developer mode on or off", - "[^3on|off^7]" - }, + {"sm", G_admin_sm, qfalse, "schachtmeister", + "Schachtmeister", + "..." + } + }; - {"hstage", G_admin_hstage, "stage", - "change the stage for humans", - "[^3#^7]" - }, +static size_t adminNumCmds = ARRAY_LEN( g_admin_cmds ); - {"astage", G_admin_astage, "stage", - "change the stage for aliens", - "[^3#^7]" - }, +static int admin_level_maxname = 0; +g_admin_level_t *g_admin_levels = NULL; +g_admin_admin_t *g_admin_admins = NULL; +g_admin_ban_t *g_admin_bans = NULL; +g_admin_command_t *g_admin_commands = NULL; - {"bubble", G_admin_bubble, "bubble", - "continuously spawn bubbles around a player", - "[^3name|slot#^7]" - }, +void G_admin_register_cmds( void ) +{ + int i; - {"scrim", G_admin_scrim, "scrim", - "toggles scrim mode", - "[on|off]", - }, + for( i = 0; i < adminNumCmds; i++ ) + trap_AddCommand( g_admin_cmds[ i ].keyword ); +} - {"give", G_admin_give, "give", - "give funds to a player", - "[^3name|slot#^7] [^3amount^7]" - }, +void G_admin_unregister_cmds( void ) +{ + int i; - {"setrotation", G_admin_setrotation, "setrotation", - "sets the map rotation", - "[^3rotation^7]" - }, + for( i = 0; i < adminNumCmds; i++ ) + trap_RemoveCommand( g_admin_cmds[ i ].keyword ); +} - {"versions", G_admin_versions, "namelog", - "Check what versions of Tremulous players are running.", - "" - }, +void G_admin_cmdlist( gentity_t *ent ) +{ + int i; + char out[ MAX_STRING_CHARS ] = ""; + int len, outlen; - {"showff", G_admin_showff, "showff", - "shows how much friendly damage a player has done this game" - "\nno arguments will list all connected players", - "(^3name|slot^7)" - "\n ^3Example:^7 ^120% ^7means 1/5th of the damage dealt this game was dealt to the team" - }, + outlen = 0; - {"tklog", G_admin_tklog, "tklog", - "list recent teamkill activity", - "(^5start id#|name|-skip#^7) (^5search skip#^7)" - }, + for( i = 0; i < adminNumCmds; i++ ) + { + if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) + continue; - {"sm", G_admin_sm, "schachtmeister", - "Schachtmeister", - "..." + len = strlen( g_admin_cmds[ i ].keyword ) + 1; + if( len + outlen >= sizeof( out ) - 1 ) + { + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + outlen = 0; } - }; - -static int adminNumCmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] ); - -static int admin_level_maxname = 0; -g_admin_level_t *g_admin_levels[ MAX_ADMIN_LEVELS ]; -g_admin_admin_t *g_admin_admins[ MAX_ADMIN_ADMINS ]; -g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ]; -g_admin_command_t *g_admin_commands[ MAX_ADMIN_COMMANDS ]; -g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ]; + strcpy( out + outlen, va( " %s", g_admin_cmds[ i ].keyword ) ); + outlen += len; + } -int G_admin_parse_time( const char *time ); + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); +} // match a certain flag within these flags -// return state of whether flag was found or not, -// set *perm to indicate whether found flag was + or - static qboolean admin_permission( char *flags, const char *flag, qboolean *perm ) { char *token, *token_p = flags; - qboolean all_found = qfalse; - qboolean base_perm = qfalse; - + qboolean allflags = qfalse; + qboolean p = qfalse; + *perm = qfalse; while( *( token = COM_Parse( &token_p ) ) ) { *perm = qtrue; @@ -459,63 +310,84 @@ static qboolean admin_permission( char *flags, const char *flag, qboolean *perm return qtrue; if( !strcmp( token, ADMF_ALLFLAGS ) ) { - all_found = qtrue; - base_perm = *perm; + allflags = qtrue; + p = *perm; } } + if( allflags ) + *perm = p; + return allflags; +} + +g_admin_cmd_t *G_admin_cmd( const char *cmd ) +{ + return bsearch( cmd, g_admin_cmds, adminNumCmds, sizeof( g_admin_cmd_t ), + cmdcmp ); +} - if( all_found && flag[ 0 ] != '.' ) +g_admin_level_t *G_admin_level( const int l ) +{ + g_admin_level_t *level; + + for( level = g_admin_levels; level; level = level->next ) { - *perm = base_perm; - return qtrue; + if( level->level == l ) + return level; } - return qfalse; + return NULL; } -static int admin_adminlog_index = 0; -g_admin_adminlog_t *g_admin_adminlog[ MAX_ADMIN_ADMINLOGS ]; - -static int admin_tklog_index = 0; -g_admin_tklog_t *g_admin_tklog[ MAX_ADMIN_TKLOGS ]; - -// This function should only be used directly when the client is connecting and thus has no GUID. -// Else, use G_admin_permission() -qboolean G_admin_permission_guid( char *guid, const char* flag ) +g_admin_admin_t *G_admin_admin( const char *guid ) { - int i; - int l = 0; - qboolean perm = qfalse; + g_admin_admin_t *admin; - // Does the admin specifically have this flag granted/denied to them, - // irrespective of their admin level? - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + for( admin = g_admin_admins; admin; admin = admin->next ) { - if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) ) - { - if( admin_permission( g_admin_admins[ i ]->flags, flag, &perm ) ) - return perm; - l = g_admin_admins[ i ]->level; - break; - } + if( !Q_stricmp( admin->guid, guid ) ) + return admin; } - // If not, is this flag granted/denied for their admin level? - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) + return NULL; +} + +g_admin_command_t *G_admin_command( const char *cmd ) +{ + g_admin_command_t *c; + + for( c = g_admin_commands; c; c = c->next ) { - if( g_admin_levels[ i ]->level == l ) - return admin_permission( g_admin_levels[ i ]->flags, flag, &perm ) && - perm; + if( !Q_stricmp( c->command, cmd ) ) + return c; } - return qfalse; -} + return NULL; +} qboolean G_admin_permission( gentity_t *ent, const char *flag ) { - if(!ent) return qtrue; //console always wins + qboolean perm; + g_admin_admin_t *a; + g_admin_level_t *l; + + // console always wins + if( !ent ) + return qtrue; + + if( ( a = ent->client->pers.admin ) ) + { + if( admin_permission( a->flags, flag, &perm ) ) + return perm; + + l = G_admin_level( a->level ); + } + else + l = G_admin_level( 0 ); + + if( l ) + return admin_permission( l->flags, flag, &perm ) && perm; - return G_admin_permission_guid(ent->client->pers.guid, flag); + return qfalse; } qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ) @@ -524,125 +396,94 @@ qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ) gclient_t *client; char testName[ MAX_NAME_LENGTH ] = {""}; char name2[ MAX_NAME_LENGTH ] = {""}; + g_admin_admin_t *admin; int alphaCount = 0; - G_SanitiseString( name, name2, sizeof( name2) ); + G_SanitiseString( name, name2, sizeof( name2 ) ); - if( !Q_stricmp( name2, "UnnamedPlayer" ) ) + if( !strcmp( name2, "unnamedplayer" ) ) return qtrue; - if( !Q_stricmp( name2, "console" ) ) + if( !strcmp( name2, "console" ) ) + { + if( err && len > 0 ) + Q_strncpyz( err, "The name 'console' is not allowed.", len ); + return qfalse; + } + + G_DecolorString( name, testName, sizeof( testName ) ); + if( isdigit( testName[ 0 ] ) ) + { + if( err && len > 0 ) + Q_strncpyz( err, "Names cannot begin with numbers", len ); + return qfalse; + } + + for( i = 0; testName[ i ]; i++) + { + if( isalpha( testName[ i ] ) ) + alphaCount++; + } + + if( alphaCount == 0 ) { - Q_strncpyz( err, va( "The name '%s^7' is invalid here", name2 ), - len ); + if( err && len > 0 ) + Q_strncpyz( err, "Names must contain letters", len ); return qfalse; } for( i = 0; i < level.maxclients; i++ ) { client = &level.clients[ i ]; - if( client->pers.connected != CON_CONNECTING - && client->pers.connected != CON_CONNECTED ) - { + if( client->pers.connected == CON_DISCONNECTED ) continue; - } // can rename ones self to the same name using different colors if( i == ( ent - g_entities ) ) continue; - G_SanitiseString( client->pers.netname, testName, sizeof( testName) ); - if( !Q_stricmp( name2, testName ) ) + G_SanitiseString( client->pers.netname, testName, sizeof( testName ) ); + if( !strcmp( name2, testName ) ) { - Q_strncpyz( err, va( "The name '%s^7' is already in use", name ), - len ); + if( err && len > 0 ) + Com_sprintf( err, len, "The name '%s^7' is already in use", name ); return qfalse; } } - - if( Q_isdigit( name2[ 0 ] ) || name2[ 0 ] == '-' ) - { - Q_strncpyz( err, "Names cannot begin with a number or with a dash. Please choose another.", len ); - return qfalse; - } - - for( i = 0; name2[ i ] !='\0'; i++) - { - if( Q_isalpha( name2[ i ] ) ) - alphaCount++; - - if( name2[ i ] == ' ' ) - { - if( name2[ i + 1 ] == '-' ) - { - Q_strncpyz( err, "Names cannot contain a - preceded by a space. Please choose another.", len ); - return qfalse; - } - } - } - - if( alphaCount == 0 ) - { - Q_strncpyz( err, va( "The name '%s^7' does not include at least one letter. Please choose another.", name ), len ); - return qfalse; - } - - if( !g_adminNameProtect.string[ 0 ] ) - return qtrue; - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + for( admin = g_admin_admins; admin; admin = admin->next ) { - if( g_admin_admins[ i ]->level < 1 ) + if( admin->level < 1 ) continue; - G_SanitiseString( g_admin_admins[ i ]->name, testName, sizeof( testName) ); - if( !Q_stricmp( name2, testName ) && - Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) ) + G_SanitiseString( admin->name, testName, sizeof( testName ) ); + if( !strcmp( name2, testName ) && ent->client->pers.admin != admin ) { - Q_strncpyz( err, va( "The name '%s^7' belongs to an admin. " - "Please choose another.", name ), len ); + if( err && len > 0 ) + Com_sprintf( err, len, "The name '%s^7' belongs to an admin, " + "please use another name", name ); return qfalse; } } return qtrue; } -static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) +static qboolean admin_higher_admin( g_admin_admin_t *a, g_admin_admin_t *b ) { - int i; - int alevel = 0; - int alevel2 = 0; - - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( admin_guid, g_admin_admins[ i ]->guid ) ) - { - alevel = g_admin_admins[ i ]->level; - - // Novelty Levels should be equivelant to level 1 - if( alevel > 9 ) - alevel = 1; + qboolean perm; - break; - } - } - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( victim_guid, g_admin_admins[ i ]->guid ) ) - { - alevel2 = g_admin_admins[ i ]->level; + if( !b ) + return qtrue; - // Novelty Levels should be equivelant to level 1 - if( alevel2 > 9 ) - alevel2 = 1; + if( admin_permission( b->flags, ADMF_IMMUTABLE, &perm ) ) + return !perm; - if( alevel < alevel2 ) - return qfalse; + return b->level <= ( a ? a->level : 0 ); +} - if( strstr( g_admin_admins[ i ]->flags, va( "%s", ADMF_IMMUTABLE ) ) ) - return qfalse; - } - } - return qtrue; +static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) +{ + return admin_higher_admin( G_admin_admin( admin_guid ), + G_admin_admin( victim_guid ) ); } static qboolean admin_higher( gentity_t *admin, gentity_t *victim ) @@ -651,25 +492,15 @@ static qboolean admin_higher( gentity_t *admin, gentity_t *victim ) // console always wins if( !admin ) return qtrue; - // just in case - if( !victim ) - return qtrue; - return admin_higher_guid( admin->client->pers.guid, - victim->client->pers.guid ); + return admin_higher_admin( admin->client->pers.admin, + victim->client->pers.admin ); } static void admin_writeconfig_string( char *s, fileHandle_t f ) { - char buf[ MAX_STRING_CHARS ]; - - buf[ 0 ] = '\0'; if( s[ 0 ] ) - { - //Q_strcat(buf, sizeof(buf), s); - Q_strncpyz( buf, s, sizeof( buf ) ); - trap_FS_Write( buf, strlen( buf ), f ); - } + trap_FS_Write( s, strlen( s ), f ); trap_FS_Write( "\n", 1, f ); } @@ -677,18 +508,18 @@ static void admin_writeconfig_int( int v, fileHandle_t f ) { char buf[ 32 ]; - Com_sprintf( buf, sizeof(buf), "%d", v ); - if( buf[ 0 ] ) - trap_FS_Write( buf, strlen( buf ), f ); - trap_FS_Write( "\n", 1, f ); + Com_sprintf( buf, sizeof( buf ), "%d\n", v ); + trap_FS_Write( buf, strlen( buf ), f ); } -void admin_writeconfig( void ) +static void admin_writeconfig( void ) { fileHandle_t f; - int len, i; - int t, expiretime; - char levels[ MAX_STRING_CHARS ] = {""}; + int t; + g_admin_admin_t *a; + g_admin_level_t *l; + g_admin_ban_t *b; + g_admin_command_t *c; if( !g_admin.string[ 0 ] ) { @@ -697,96 +528,75 @@ void admin_writeconfig( void ) return; } t = trap_RealTime( NULL ); - len = trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE ); - if( len < 0 ) + if( trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE ) < 0 ) { G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n", g_admin.string ); return; } - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) + for( l = g_admin_levels; l; l = l->next ) { trap_FS_Write( "[level]\n", 8, f ); trap_FS_Write( "level = ", 10, f ); - admin_writeconfig_int( g_admin_levels[ i ]->level, f ); + admin_writeconfig_int( l->level, f ); trap_FS_Write( "name = ", 10, f ); - admin_writeconfig_string( g_admin_levels[ i ]->name, f ); + admin_writeconfig_string( l->name, f ); trap_FS_Write( "flags = ", 10, f ); - admin_writeconfig_string( g_admin_levels[ i ]->flags, f ); + admin_writeconfig_string( l->flags, f ); trap_FS_Write( "\n", 1, f ); } - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + for( a = g_admin_admins; a; a = a->next ) { // don't write level 0 users - if( g_admin_admins[ i ]->level < 1 ) + if( a->level == 0 ) continue; - //if set dont write admins that havent been seen in a while - expiretime = G_admin_parse_time( g_adminExpireTime.string ); - if( expiretime > 0 ) { - //only expire level 1 people - if( t - expiretime > g_admin_admins[ i ]->seen && g_admin_admins[ i ]->level == 1 ) { - G_Printf("Admin %s has been expired.\n", g_admin_admins[ i ]->name ); - continue; - } - } - trap_FS_Write( "[admin]\n", 8, f ); trap_FS_Write( "name = ", 10, f ); - admin_writeconfig_string( g_admin_admins[ i ]->name, f ); + admin_writeconfig_string( a->name, f ); trap_FS_Write( "guid = ", 10, f ); - admin_writeconfig_string( g_admin_admins[ i ]->guid, f ); + admin_writeconfig_string( a->guid, f ); trap_FS_Write( "level = ", 10, f ); - admin_writeconfig_int( g_admin_admins[ i ]->level, f ); + admin_writeconfig_int( a->level, f ); trap_FS_Write( "flags = ", 10, f ); - admin_writeconfig_string( g_admin_admins[ i ]->flags, f ); - trap_FS_Write( "seen = ", 10, f ); - admin_writeconfig_int( g_admin_admins[ i ]->seen, f ); + admin_writeconfig_string( a->flags, f ); trap_FS_Write( "\n", 1, f ); } - for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) + for( b = g_admin_bans; b; b = b->next ) { // don't write expired bans // if expires is 0, then it's a perm ban - if( g_admin_bans[ i ]->expires != 0 && - ( g_admin_bans[ i ]->expires - t ) < 1 ) + if( b->expires != 0 && b->expires <= t ) continue; trap_FS_Write( "[ban]\n", 6, f ); trap_FS_Write( "name = ", 10, f ); - admin_writeconfig_string( g_admin_bans[ i ]->name, f ); + admin_writeconfig_string( b->name, f ); trap_FS_Write( "guid = ", 10, f ); - admin_writeconfig_string( g_admin_bans[ i ]->guid, f ); + admin_writeconfig_string( b->guid, f ); trap_FS_Write( "ip = ", 10, f ); - admin_writeconfig_string( g_admin_bans[ i ]->ip, f ); + admin_writeconfig_string( b->ip.str, f ); trap_FS_Write( "reason = ", 10, f ); - admin_writeconfig_string( g_admin_bans[ i ]->reason, f ); + admin_writeconfig_string( b->reason, f ); trap_FS_Write( "made = ", 10, f ); - admin_writeconfig_string( g_admin_bans[ i ]->made, f ); + admin_writeconfig_string( b->made, f ); trap_FS_Write( "expires = ", 10, f ); - admin_writeconfig_int( g_admin_bans[ i ]->expires, f ); - if( g_admin_bans[ i ]->suspend > t ) { - trap_FS_Write( "suspend = ", 10, f ); - admin_writeconfig_int( g_admin_bans[ i ]->suspend, f ); - } + admin_writeconfig_int( b->expires, f ); trap_FS_Write( "banner = ", 10, f ); - admin_writeconfig_string( g_admin_bans[ i ]->banner, f ); - trap_FS_Write( "blevel = ", 10, f ); - admin_writeconfig_int( g_admin_bans[ i ]->bannerlevel, f ); + admin_writeconfig_string( b->banner, f ); trap_FS_Write( "\n", 1, f ); } - for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) + for( c = g_admin_commands; c; c = c->next ) { - levels[ 0 ] = '\0'; trap_FS_Write( "[command]\n", 10, f ); trap_FS_Write( "command = ", 10, f ); - admin_writeconfig_string( g_admin_commands[ i ]->command, f ); + admin_writeconfig_string( c->command, f ); trap_FS_Write( "exec = ", 10, f ); - admin_writeconfig_string( g_admin_commands[ i ]->exec, f ); + admin_writeconfig_string( c->exec, f ); trap_FS_Write( "desc = ", 10, f ); - admin_writeconfig_string( g_admin_commands[ i ]->desc, f ); + admin_writeconfig_string( c->desc, f ); trap_FS_Write( "flag = ", 10, f ); - admin_writeconfig_string( g_admin_commands[ i ]->flag, f ); + admin_writeconfig_string( c->flag, f ); trap_FS_Write( "\n", 1, f ); } trap_FS_FCloseFile( f ); @@ -794,41 +604,32 @@ void admin_writeconfig( void ) static void admin_readconfig_string( char **cnf, char *s, int size ) { - char * t; + char *t; //COM_MatchToken(cnf, "="); + s[ 0 ] = '\0'; t = COM_ParseExt( cnf, qfalse ); - if( !strcmp( t, "=" ) ) - { - t = COM_ParseExt( cnf, qfalse ); - } - else + if( strcmp( t, "=" ) ) { - G_Printf( "readconfig: warning missing = before " - "\"%s\" on line %d\n", - t, - COM_GetCurrentParseLine() ); + COM_ParseWarning( "expected '=' before \"%s\"", t ); + Q_strncpyz( s, t, size ); } - s[ 0 ] = '\0'; - while( t[ 0 ] ) + while( 1 ) { - if( ( s[ 0 ] == '\0' && strlen( t ) <= size ) - || ( strlen( t ) + strlen( s ) < size ) ) - { - - Q_strcat( s, size, t ); - Q_strcat( s, size, " " ); - } t = COM_ParseExt( cnf, qfalse ); + if( !*t ) + break; + if( strlen( t ) + strlen( s ) >= size ) + break; + if( *s ) + Q_strcat( s, size, " " ); + Q_strcat( s, size, t ); } - // trim the trailing space - if( strlen( s ) > 0 && s[ strlen( s ) - 1 ] == ' ' ) - s[ strlen( s ) - 1 ] = '\0'; } static void admin_readconfig_int( char **cnf, int *v ) { - char * t; + char *t; //COM_MatchToken(cnf, "="); t = COM_ParseExt( cnf, qfalse ); @@ -838,10 +639,7 @@ static void admin_readconfig_int( char **cnf, int *v ) } else { - G_Printf( "readconfig: warning missing = before " - "\"%s\" on line %d\n", - t, - COM_GetCurrentParseLine() ); + COM_ParseWarning( "expected '=' before \"%s\"", t ); } *v = atoi( t ); } @@ -850,413 +648,217 @@ static void admin_readconfig_int( char **cnf, int *v ) // ones to make new installs easier for admins static void admin_default_levels( void ) { - g_admin_level_t * l; - int i; - - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - G_Free( g_admin_levels[ i ] ); - g_admin_levels[ i ] = NULL; - } - for( i = 0; i <= 5; i++ ) - { - l = G_Alloc( sizeof( g_admin_level_t ) ); - l->level = i; - *l->name = '\0'; - *l->flags = '\0'; - g_admin_levels[ i ] = l; - } + g_admin_level_t *l; + int level = 0; - Q_strncpyz( g_admin_levels[ 0 ]->name, "^4Unknown Player", - sizeof( l->name ) ); - Q_strncpyz( g_admin_levels[ 0 ]->flags, - "listplayers admintest help specme time", + l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^4Unknown Player", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time", sizeof( l->flags ) ); - Q_strncpyz( g_admin_levels[ 1 ]->name, "^5Server Regular", - sizeof( l->name ) ); - Q_strncpyz( g_admin_levels[ 1 ]->flags, - "listplayers admintest help specme time", + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^5Server Regular", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time", sizeof( l->flags ) ); - Q_strncpyz( g_admin_levels[ 2 ]->name, "^6Team Manager", - sizeof( l->name ) ); - Q_strncpyz( g_admin_levels[ 2 ]->flags, - "listplayers admintest help specme time putteam spec999 warn denybuild", + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^6Team Manager", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time putteam spec999", sizeof( l->flags ) ); - Q_strncpyz( g_admin_levels[ 3 ]->name, "^2Junior Admin", - sizeof( l->name ) ); - Q_strncpyz( g_admin_levels[ 3 ]->flags, - "listplayers admintest help specme time putteam spec999 kick mute warn " - "denybuild ADMINCHAT SEESFULLLISTPLAYERS", + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^2Junior Admin", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time putteam spec999 kick mute ADMINCHAT", sizeof( l->flags ) ); - Q_strncpyz( g_admin_levels[ 4 ]->name, "^3Senior Admin", - sizeof( l->name ) ); - Q_strncpyz( g_admin_levels[ 4 ]->flags, - "listplayers admintest help specme time putteam spec999 kick mute showbans " - "ban namelog warn denybuild decon ADMINCHAT SEESFULLLISTPLAYERS", + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^3Senior Admin", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time putteam spec999 kick mute showbans ban " + "namelog ADMINCHAT", sizeof( l->flags ) ); - Q_strncpyz( g_admin_levels[ 5 ]->name, "^1Server Operator", - sizeof( l->name ) ); - Q_strncpyz( g_admin_levels[ 5 ]->flags, - "ALLFLAGS -INCOGNITO -IMMUTABLE -DBUILDER -BANIMMUNITY", + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^1Server Operator", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "ALLFLAGS -IMMUTABLE -INCOGNITO", sizeof( l->flags ) ); + admin_level_maxname = 15; } -// return a level for a player entity. -int G_admin_level( gentity_t *ent ) +void G_admin_authlog( gentity_t *ent ) { - int i; - qboolean found = qfalse; + char aflags[ MAX_ADMIN_FLAGS * 2 ]; + g_admin_level_t *level; + int levelNum = 0; if( !ent ) - { - return MAX_ADMIN_LEVELS; - } + return; - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) - { + if( ent->client->pers.admin ) + levelNum = ent->client->pers.admin->level; - found = qtrue; - break; - } - } + level = G_admin_level( levelNum ); - if( found ) - { - return g_admin_admins[ i ]->level; - } + Com_sprintf( aflags, sizeof( aflags ), "%s %s", + ent->client->pers.admin->flags, + ( level ) ? level->flags : "" ); - return 0; + G_LogPrintf( "AdminAuth: %i \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE + "\" [%d] (%s): %s\n", + (int)( ent - g_entities ), ent->client->pers.netname, + ent->client->pers.admin->name, ent->client->pers.admin->level, + ent->client->pers.guid, aflags ); } -// set a player's adminname -void G_admin_set_adminname( gentity_t *ent ) +static char adminLog[ MAX_STRING_CHARS ]; +static int adminLogLen; +static void admin_log_start( gentity_t *admin, const char *cmd ) { - int i; - qboolean found = qfalse; - - if( !ent ) - { - return; - } + const char *name = admin ? admin->client->pers.netname : "console"; - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) - { - found = qtrue; - break; - } - } - - if( found ) - { - Q_strncpyz( ent->client->pers.adminName, g_admin_admins[ i ]->name, sizeof( ent->client->pers.adminName ) ); - } - else - { - Q_strncpyz( ent->client->pers.adminName, "", sizeof( ent->client->pers.adminName ) ); - } + adminLogLen = Q_snprintf( adminLog, sizeof( adminLog ), + "%d \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE "\" [%d] (%s): %s", + admin ? admin->s.clientNum : -1, + name, + admin && admin->client->pers.admin ? admin->client->pers.admin->name : name, + admin && admin->client->pers.admin ? admin->client->pers.admin->level : 0, + admin ? admin->client->pers.guid : "", + cmd ); } -// Get an admin's registered name -const char *G_admin_get_adminname( gentity_t *ent ) +static void admin_log( const char *str ) { - int i; - - if( !ent ) - return "console"; - - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) - return g_admin_admins[ i ]->name; - } - - return ent->client->pers.netname; + if( adminLog[ 0 ] ) + adminLogLen += Q_snprintf( adminLog + adminLogLen, + sizeof( adminLog ) - adminLogLen, ": %s", str ); } -// Get an admin's name to print as the user of various commands, -// obeying admin stealth settings when necessary -char* G_admin_adminPrintName( gentity_t *ent ) +static void admin_log_abort( void ) { - char *out; - - if( !ent->client->pers.adminLevel ) - { - out = ""; - return out; - } - - if( G_admin_permission( ent, ADMF_ADMINSTEALTH ) ) - { - out = ent->client->pers.adminName; - } - else - { - out = ent->client->pers.netname; - } - - - return out; + adminLog[ 0 ] = '\0'; + adminLogLen = 0; } -static void admin_log( gentity_t *admin, char *cmd, int skiparg ) +static void admin_log_end( const qboolean ok ) { - fileHandle_t f; - int len, i, j; - char string[ MAX_STRING_CHARS ], decoloured[ MAX_STRING_CHARS ]; - int min, tens, sec; - g_admin_admin_t *a; - g_admin_level_t *l; - char flags[ MAX_ADMIN_FLAGS * 2 ]; - gentity_t *victim = NULL; - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; - - if( !g_adminLog.string[ 0 ] ) - return ; - - - len = trap_FS_FOpenFile( g_adminLog.string, &f, FS_APPEND ); - if( len < 0 ) - { - G_Printf( "admin_log: error could not open %s\n", g_adminLog.string ); - return ; - } + if( adminLog[ 0 ] ) + G_LogPrintf( "AdminExec: %s: %s\n", ok ? "ok" : "fail", adminLog ); + admin_log_abort( ); +} - sec = (level.time - level.startTime) / 1000; - min = sec / 60; - sec -= min * 60; - tens = sec / 10; - sec -= tens * 10; +struct llist +{ + struct llist *next; +}; +static int admin_search( gentity_t *ent, + const char *cmd, + const char *noun, + qboolean ( *match )( void *, const void * ), + void ( *out )( void *, char * ), + const void *list, + const void *arg, /* this will be used as char* later */ + int start, + const int offset, + const int limit ) +{ + int i; + int count = 0; + int found = 0; + int total; + int next = 0, end = 0; + char str[ MAX_STRING_CHARS ]; + struct llist *l = (struct llist *)list; + + for( total = 0; l; total++, l = l->next ) ; + if( start < 0 ) + start += total; + else + start -= offset; + if( start < 0 || start > total ) + start = 0; - *flags = '\0'; - if( admin ) + ADMBP_begin(); + for( i = 0, l = (struct llist *)list; l; i++, l = l->next ) { - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + if( match( l, arg ) ) { - if( !Q_stricmp( g_admin_admins[ i ]->guid , admin->client->pers.guid ) ) + if( i >= start && ( limit < 1 || count < limit ) ) { - - a = g_admin_admins[ i ]; - Q_strncpyz( flags, a->flags, sizeof( flags ) ); - for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) - { - if( g_admin_levels[ j ]->level == a->level ) - { - l = g_admin_levels[ j ]; - Q_strcat( flags, sizeof( flags ), l->flags ); - break; - } - } - break; + out( l, str ); + ADMBP( va( "%-3d %s\n", i + offset, str ) ); + count++; + end = i; + } + else if( count == limit ) + { + if( next == 0 ) + next = i; } - } - } - if( G_SayArgc() > 1 + skiparg ) - { - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) == 1 ) - { - victim = &g_entities[ pids[ 0 ] ]; + found++; } } - if( victim && Q_stricmp( cmd, "attempted" ) ) - { - Com_sprintf( string, sizeof( string ), - "%3i:%i%i: %i: %s: %s (%s): %s: %s: %s: %s: \"%s\"\n", - min, - tens, - sec, - ( admin ) ? admin->s.clientNum : -1, - ( admin ) ? admin->client->pers.guid - : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - ( admin ) ? admin->client->pers.netname : "console", - ( admin ) ? admin->client->pers.adminName : "console", - flags, - cmd, - victim->client->pers.guid, - victim->client->pers.netname, - G_SayConcatArgs( 2 + skiparg ) ); - } - else + if( limit > 0 ) { - Com_sprintf( string, sizeof( string ), - "%3i:%i%i: %i: %s: %s (%s): %s: %s: \"%s\"\n", - min, - tens, - sec, - ( admin ) ? admin->s.clientNum : -1, - ( admin ) ? admin->client->pers.guid - : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - ( admin ) ? admin->client->pers.netname : "console", - ( admin ) ? admin->client->pers.adminName : "console", - flags, - cmd, - G_SayConcatArgs( 1 + skiparg ) ); + ADMBP( va( "^3%s: ^7showing %d of %d %s %d-%d%s%s.", + cmd, count, found, noun, start + offset, end + offset, + *(char *)arg ? " matching " : "", (char *)arg ) ); + if( next ) + ADMBP( va( " use '%s%s%s %d' to see more", cmd, + *(char *)arg ? " " : "", + (char *)arg, + next + offset ) ); } + ADMBP( "\n" ); + ADMBP_end(); + return next + offset; +} - if( g_decolourLogfiles.integer ) - { - G_DecolorString( string, decoloured ); - trap_FS_Write( decoloured, strlen( decoloured ), f ); - } - else +static qboolean admin_match( void *admin, const void *match ) +{ + char n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ]; + G_SanitiseString( (char *)match, n2, sizeof( n2 ) ); + if( !n2[ 0 ] ) + return qtrue; + G_SanitiseString( ( (g_admin_admin_t *)admin )->name, n1, sizeof( n1 ) ); + return strstr( n1, n2 ) ? qtrue : qfalse; +} +static void admin_out( void *admin, char *str ) +{ + g_admin_admin_t *a = (g_admin_admin_t *)admin; + g_admin_level_t *l = G_admin_level( a->level ); + int lncol = 0, i; + for( i = 0; l && l->name[ i ]; i++ ) { - trap_FS_Write( string, strlen( string ), f ); + if( Q_IsColorString( l->name + i ) ) + lncol += 2; } - trap_FS_FCloseFile( f ); - - if ( !Q_stricmp( cmd, "attempted" ) ) - { - Com_sprintf( string, sizeof( string ), - "%s^7 (%i) %s: %s", - ( admin ) ? admin->client->pers.netname : "console", - ( admin ) ? admin->s.clientNum : -1, - cmd, - G_SayConcatArgs( 1 + skiparg ) ); - G_AdminsPrintf("%s\n",string); - } - - G_LogPrintf("Admin Command: %s^7 (%s): %s %s\n",( admin ) ? admin->client->pers.netname : "console", ( admin ) ? admin->client->pers.adminName : "console", cmd, G_SayConcatArgs( 1 + skiparg )); + Com_sprintf( str, MAX_STRING_CHARS, "%-6d %*s^7 %s", + a->level, admin_level_maxname + lncol - 1, l ? l->name : "(null)", + a->name ); } - -static int admin_listadmins( gentity_t *ent, int start, char *search, int minlevel ) +static int admin_listadmins( gentity_t *ent, int start, char *search ) { - int drawn = 0; - char guid_stub[9]; - char name[ MAX_NAME_LENGTH ] = {""}; - char name2[ MAX_NAME_LENGTH ] = {""}; - char lname[ MAX_NAME_LENGTH ] = {""}; - char lname_fmt[ 5 ]; - int i,j; - gentity_t *vic; - int l = 0; - qboolean dup = qfalse; - - ADMBP_begin(); - - // print out all connected players regardless of level if name searching - for( i = 0; i < level.maxclients && search[ 0 ]; i++ ) - { - vic = &g_entities[ i ]; - - if( vic->client && vic->client->pers.connected != CON_CONNECTED ) - continue; - - l = vic->client->pers.adminLevel; - - G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) ); - if( !strstr( name, search ) ) - continue; - - for( j = 0; j < 8; j++ ) - guid_stub[ j ] = vic->client->pers.guid[ j + 24 ]; - guid_stub[ j ] = '\0'; - - lname[ 0 ] = '\0'; - Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); - for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) - { - if( g_admin_levels[ j ]->level == l ) - { - G_DecolorString( g_admin_levels[ j ]->name, lname ); - Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is", - ( admin_level_maxname + strlen( g_admin_levels[ j ]->name ) - - strlen( lname ) ) ); - Com_sprintf( lname, sizeof( lname ), lname_fmt, - g_admin_levels[ j ]->name ); - break; - } - } - ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n", - i, - l, - lname, - guid_stub, - vic->client->pers.netname ) ); - drawn++; - } - - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] - && drawn < MAX_ADMIN_LISTITEMS; i++ ) - if( g_admin_admins[ i ]->level >= minlevel ) - { - - if( start ) - { - start--; - continue; - } - - if( search[ 0 ] ) - { - G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) ); - if( !strstr( name, search ) ) - continue; - - // verify we don't have the same guid/name pair in connected players - // since we don't want to draw the same player twice - dup = qfalse; - for( j = 0; j < level.maxclients; j++ ) - { - vic = &g_entities[ j ]; - if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) - continue; - G_SanitiseString( vic->client->pers.netname, name2, sizeof( name2) ); - if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) - && strstr( name2, search ) ) - { - dup = qtrue; - break; - } - } - if( dup ) - continue; - } - for( j = 0; j < 8; j++ ) - guid_stub[ j ] = g_admin_admins[ i ]->guid[ j + 24 ]; - guid_stub[ j ] = '\0'; - - lname[ 0 ] = '\0'; - Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); - for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) - { - if( g_admin_levels[ j ]->level == g_admin_admins[ i ]->level ) - { - G_DecolorString( g_admin_levels[ j ]->name, lname ); - Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is", - ( admin_level_maxname + strlen( g_admin_levels[ j ]->name ) - - strlen( lname ) ) ); - Com_sprintf( lname, sizeof( lname ), lname_fmt, - g_admin_levels[ j ]->name ); - break; - } - } - ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n", - ( i + MAX_CLIENTS ), - g_admin_admins[ i ]->level, - lname, - guid_stub, - g_admin_admins[ i ]->name ) ); - drawn++; - } - ADMBP_end(); - return drawn; + return admin_search( ent, "listadmins", "admins", admin_match, admin_out, + g_admin_admins, search, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS ); } +#define MAX_DURATION_LENGTH 13 void G_admin_duration( int secs, char *duration, int dursize ) { - + // sizeof("12.5 minutes") == 13 if( secs > ( 60 * 60 * 24 * 365 * 50 ) || secs < 0 ) Q_strncpyz( duration, "PERMANENT", dursize ); else if( secs >= ( 60 * 60 * 24 * 365 ) ) @@ -1278,440 +880,200 @@ void G_admin_duration( int secs, char *duration, int dursize ) Com_sprintf( duration, dursize, "%i seconds", secs ); } -qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ) +static void G_admin_ban_message( + gentity_t *ent, + g_admin_ban_t *ban, + char *creason, + int clen, + char *areason, + int alen ) +{ + if( creason ) + { + char duration[ MAX_DURATION_LENGTH ]; + G_admin_duration( ban->expires - trap_RealTime( NULL ), duration, + sizeof( duration ) ); + // part of this might get cut off on the connect screen + Com_sprintf( creason, clen, + "You have been banned by %s" S_COLOR_WHITE " duration: %s" + " reason: %s", + ban->banner, + duration, + ban->reason ); + } + + if( areason && ent ) + { + // we just want the ban number + int n = 1; + g_admin_ban_t *b = g_admin_bans; + for( ; b && b != ban; b = b->next, n++ ) + ; + Com_sprintf( areason, alen, + S_COLOR_YELLOW "Banned player %s" S_COLOR_YELLOW + " tried to connect from %s (ban #%d)", + ent->client->pers.netname[ 0 ] ? ent->client->pers.netname : ban->name, + ent->client->pers.ip.str, + n ); + } +} + +static qboolean G_admin_ban_matches( g_admin_ban_t *ban, gentity_t *ent ) +{ + return !Q_stricmp( ban->guid, ent->client->pers.guid ) || + ( !G_admin_permission( ent, ADMF_IMMUNITY ) && + G_AddressCompare( &ban->ip, &ent->client->pers.ip ) ); +} + +static g_admin_ban_t *G_admin_match_ban( gentity_t *ent ) { - static char lastConnectIP[ 16 ] = {""}; - static int lastConnectTime = 0; - char guid[ 33 ]; - char ip[ 16 ]; - char *value; - int i; - unsigned int userIP = 0, intIP = 0, tempIP; - int IP[5], k, mask, ipscanfcount; int t; - char notice[51]; - qboolean ignoreIP = qfalse; - - trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); - - *reason = '\0'; - - if( !*userinfo ) - return qfalse; - - value = Info_ValueForKey( userinfo, "ip" ); - Q_strncpyz( ip, value, sizeof( ip ) ); - // strip port - value = strchr( ip, ':' ); - if ( value ) - *value = '\0'; - - if( !*ip ) - return qfalse; - - value = Info_ValueForKey( userinfo, "cl_guid" ); - Q_strncpyz( guid, value, sizeof( guid ) ); - + g_admin_ban_t *ban; + t = trap_RealTime( NULL ); - memset( IP, 0, sizeof( IP )); - sscanf(ip, "%i.%i.%i.%i", &IP[4], &IP[3], &IP[2], &IP[1]); - for(k = 4; k >= 1; k--) - { - if(!IP[k]) continue; - userIP |= IP[k] << 8*(k-1); - } - ignoreIP = G_admin_permission_guid( guid , ADMF_BAN_IMMUNITY ); - for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) + if( ent->client->pers.localClient ) + return NULL; + + for( ban = g_admin_bans; ban; ban = ban->next ) { // 0 is for perm ban - if( g_admin_bans[ i ]->expires != 0 && - ( g_admin_bans[ i ]->expires - t ) < 1 ) - continue; - //if suspend time is over continue - if( g_admin_bans[ i ]->suspend >= t ) + if( ban->expires != 0 && ban->expires <= t ) continue; - if( !ignoreIP ) - { - tempIP = userIP; - intIP = 0; - mask = -1; - - memset( IP, 0, sizeof( IP )); - ipscanfcount = sscanf(g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]); - - if( ipscanfcount == 4 ) - mask = -1; - else if( ipscanfcount == 5 ) - mask = IP[0]; - else if( ipscanfcount > 0 && ipscanfcount < 4 ) - mask = 8 * ipscanfcount; - else - continue; + if( G_admin_ban_matches( ban, ent ) ) + return ban; + } - for(k = 4; k >= 1; k--) - { - if(!IP[k]) continue; - intIP |= IP[k] << 8*(k-1); - } + return NULL; +} - if(mask > 0 && mask <= 32) - { - tempIP &= ~((1 << (32-mask)) - 1); - intIP &= ~((1 << (32-mask)) - 1); - } +qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen ) +{ + g_admin_ban_t *ban; + char warningMessage[ MAX_STRING_CHARS ]; - if( intIP == tempIP || mask == 0 ) - { - char duration[ 32 ]; - G_admin_duration( ( g_admin_bans[ i ]->expires - t ), - duration, sizeof( duration ) ); - - // flood protected - if( t - lastConnectTime >= 300 || - Q_stricmp( lastConnectIP, ip ) ) - { - lastConnectTime = t; - Q_strncpyz( lastConnectIP, ip, sizeof( lastConnectIP ) ); - - G_WarningsPrintf( - "ban", - "Banned player %s^7 (%s^7) tried to connect (ban #%i on %s by %s^7 expires %s reason: %s^7 )\n", - Info_ValueForKey( userinfo, "name" ), - g_admin_bans[ i ]->name, - i+1, - ip, - g_admin_bans[ i ]->banner, - duration, - g_admin_bans[ i ]->reason ); - } - - Com_sprintf( - reason, - rlen, - "You have been banned by %s^7 reason: %s^7 expires: %s %s", - g_admin_bans[ i ]->banner, - g_admin_bans[ i ]->reason, - duration, - notice - ); - G_LogPrintf("Banned player tried to connect from IP %s\n", ip); - return qtrue; - } - } - if( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) ) - { - char duration[ 32 ]; - G_admin_duration( ( g_admin_bans[ i ]->expires - t ), - duration, sizeof( duration ) ); - Com_sprintf( - reason, - rlen, - "You have been banned by %s^7 reason: %s^7 expires: %s", - g_admin_bans[ i ]->banner, - g_admin_bans[ i ]->reason, - duration - ); - G_Printf("Banned player tried to connect with GUID %s\n", guid); - return qtrue; - } - } - if ( *guid ) + if( ent->client->pers.localClient ) + return qfalse; + + if( ( ban = G_admin_match_ban( ent ) ) ) { - int count = 0; - qboolean valid = qtrue; + G_admin_ban_message( ent, ban, reason, rlen, + warningMessage, sizeof( warningMessage ) ); - while( guid[ count ] != '\0' && valid ) - { - if( (guid[ count ] < '0' || guid[ count ] > '9') && - (guid[ count ] < 'A' || guid[ count ] > 'F') ) - { - valid = qfalse; - } - count++; - } - if( !valid || count != 32 ) - { - Com_sprintf( reason, rlen, "Invalid client data" ); - G_Printf("Player with invalid GUID [%s] connect from IP %s\n", guid, ip); - return qtrue; - } + // don't spam admins + if( ban->warnCount++ < 5 ) + G_AdminMessage( NULL, warningMessage ); + // and don't fill the console + else if( ban->warnCount < 10 ) + trap_Print( va( "%s%s\n", warningMessage, + ban->warnCount + 1 == 10 ? + S_COLOR_WHITE " - future messages for this ban will be suppressed" : + "" ) ); + return qtrue; } + return qfalse; } -qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ) +qboolean G_admin_cmd_check( gentity_t *ent ) { - int i; char command[ MAX_ADMIN_CMD_LEN ]; - char *cmd; - int skip = 0; + g_admin_cmd_t *admincmd; + g_admin_command_t *c; + qboolean success; command[ 0 ] = '\0'; - G_SayArgv( 0, command, sizeof( command ) ); - if( !Q_stricmp( command, "say" ) || - ( G_admin_permission( ent, ADMF_TEAMCHAT_CMD ) && - ( !Q_stricmp( command, "say_team" ) ) ) ) - { - skip = 1; - G_SayArgv( 1, command, sizeof( command ) ); - } + trap_Argv( 0, command, sizeof( command ) ); if( !command[ 0 ] ) return qfalse; - if( command[ 0 ] == '!' ) - { - cmd = &command[ 1 ]; - } - else - { - return qfalse; - } - - // Flood limit. If they're talking too fast, determine that and return. - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); - return qtrue; - } - - if( G_admin_is_restricted( ent, qtrue ) ) - return qtrue; + Q_strlwr( command ); + admin_log_start( ent, command ); - for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) + if( ( c = G_admin_command( command ) ) ) { - if( Q_stricmp( cmd, g_admin_commands[ i ]->command ) ) - continue; - - if( G_admin_permission( ent, g_admin_commands[ i ]->flag ) ) + admin_log( ConcatArgsPrintable( 1 ) ); + if( ( success = G_admin_permission( ent, c->flag ) ) ) { - trap_SendConsoleCommand( EXEC_APPEND, g_admin_commands[ i ]->exec ); - admin_log( ent, cmd, skip ); - G_admin_adminlog_log( ent, cmd, NULL, skip, qtrue ); + if( G_FloodLimited( ent ) ) + return qtrue; + trap_SendConsoleCommand( EXEC_APPEND, c->exec ); } else { - ADMP( va( "^3!%s: ^7permission denied\n", g_admin_commands[ i ]->command ) ); - admin_log( ent, "attempted", skip - 1 ); - G_admin_adminlog_log( ent, cmd, NULL, skip, qfalse ); + ADMP( va( "^3%s: ^7permission denied\n", c->command ) ); } + admin_log_end( success ); return qtrue; } - for( i = 0; i < adminNumCmds; i++ ) + if( ( admincmd = G_admin_cmd( command ) ) ) { - if( Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) ) - continue; - - if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) + if( ( success = G_admin_permission( ent, admincmd->flag ) ) ) { - g_admin_cmds[ i ].handler( ent, skip ); - admin_log( ent, cmd, skip ); - G_admin_adminlog_log( ent, cmd, NULL, skip, qtrue ); + if( G_FloodLimited( ent ) ) + return qtrue; + if( admincmd->silent ) + admin_log_abort( ); + if( !( success = admincmd->handler( ent ) ) ) + admin_log( ConcatArgsPrintable( 1 ) ); } else { - ADMP( va( "^3!%s: ^7permission denied\n", g_admin_cmds[ i ].keyword ) ); - admin_log( ent, "attempted", skip - 1 ); - G_admin_adminlog_log( ent, cmd, NULL, skip, qfalse ); + ADMP( va( "^3%s: ^7permission denied\n", admincmd->keyword ) ); + admin_log( ConcatArgsPrintable( 1 ) ); } + admin_log_end( success ); return qtrue; } return qfalse; } -void G_admin_namelog_cleanup( ) +static void llsort( struct llist **head, int compar( const void *, const void * ) ) { - int i; - - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - if( g_admin_namelog[ i ]->smj.comment ) - G_Free( g_admin_namelog[ i ]->smj.comment ); - G_Free( g_admin_namelog[ i ] ); - g_admin_namelog[ i ] = NULL; - } -} - -static void dispatchSchachtmeisterIPAQuery( const char *ipa ) -{ - trap_SendConsoleCommand( EXEC_APPEND, va( "smq ipa \"%s\"\n", ipa ) ); -} - -static void schachtmeisterProcess( g_admin_namelog_t *namelog ) -{ - schachtmeisterJudgement_t *j = &namelog->smj; - - if( !j->ratingTime || level.time - j->ratingTime >= 600000 ) - { - if( j->queryTime ) - return; - - j->queryTime = level.time; - goto dispatch; - } - - if( !j->queryTime || level.time - j->queryTime >= 60000 ) - return; + struct llist *a, *b, *t, *l; + int i, c = 1, ns, as, bs; - if( j->dispatchTime && level.time - j->dispatchTime < 5000 ) + if( !*head ) return; - dispatch: - j->dispatchTime = level.time; - dispatchSchachtmeisterIPAQuery( namelog->ip ); -} - -void G_admin_schachtmeisterFrame( void ) -{ - int i; - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - schachtmeisterProcess( g_admin_namelog[ i ] ); -} - -void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) -{ - int i, j; - g_admin_namelog_t *namelog; - char n1[ MAX_NAME_LENGTH ]; - char n2[ MAX_NAME_LENGTH ]; - int clientNum = ( client - level.clients ); - - if ( client->sess.invisible == qfalse ) + do { - G_admin_seen_update( client->pers.guid ); - } - - G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) ); - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - if( disconnect && g_admin_namelog[ i ]->slot != clientNum ) - continue; - - if( !disconnect && !( g_admin_namelog[ i ]->slot == clientNum || - g_admin_namelog[ i ]->slot == -1 ) ) - { - continue; - } - - if( !Q_stricmp( client->pers.ip, g_admin_namelog[ i ]->ip ) - && !Q_stricmp( client->pers.guid, g_admin_namelog[ i ]->guid ) ) + a = *head, l = *head = NULL; + for( ns = 0; a; ns++, a = b ) { - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES - && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) + b = a; + for( i = as = 0; i < c; i++ ) { - G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); - if( !Q_stricmp( n1, n2 ) ) + as++; + if( !( b = b->next ) ) break; } - if( j == MAX_ADMIN_NAMELOG_NAMES ) - j = MAX_ADMIN_NAMELOG_NAMES - 1; - Q_strncpyz( g_admin_namelog[ i ]->name[ j ], client->pers.netname, - sizeof( g_admin_namelog[ i ]->name[ j ] ) ); - g_admin_namelog[ i ]->slot = ( disconnect ) ? -1 : clientNum; - - // if this player is connecting, they are no longer banned - if( !disconnect ) - g_admin_namelog[ i ]->banned = qfalse; - - //check other things like if user was denybuild or muted or denyweapon and restore them - if( !disconnect ) - { - if( g_admin_namelog[ i ]->muted ) - { - client->pers.muted = qtrue; - client->pers.muteExpires = g_admin_namelog[ i ]->muteExpires; - G_AdminsPrintf( "^7%s^7's mute has been restored\n", client->pers.netname ); - g_admin_namelog[ i ]->muted = qfalse; - } - if( g_admin_namelog[ i ]->denyBuild ) - { - client->pers.denyBuild = qtrue; - G_AdminsPrintf( "^7%s^7's Denybuild has been restored\n", client->pers.netname ); - g_admin_namelog[ i ]->denyBuild = qfalse; - } - if( g_admin_namelog[ i ]->denyHumanWeapons > 0 || g_admin_namelog[ i ]->denyAlienClasses > 0 ) - { - if( g_admin_namelog[ i ]->denyHumanWeapons > 0 ) - client->pers.denyHumanWeapons = g_admin_namelog[ i ]->denyHumanWeapons; - if( g_admin_namelog[ i ]->denyAlienClasses > 0 ) - client->pers.denyAlienClasses = g_admin_namelog[ i ]->denyAlienClasses; - - G_AdminsPrintf( "^7%s^7's Denyweapon has been restored\n", client->pers.netname ); - g_admin_namelog[ i ]->denyHumanWeapons = 0; - g_admin_namelog[ i ]->denyAlienClasses = 0; - } - if( g_admin_namelog[ i ]->specExpires > 0 ) - { - client->pers.specExpires = g_admin_namelog[ i ]->specExpires; - G_AdminsPrintf( "^7%s^7's Putteam spectator has been restored\n", client->pers.netname ); - g_admin_namelog[ i ]->specExpires = 0; - } - if( g_admin_namelog[ i ]->voteCount > 0 ) - { - client->pers.voteCount = g_admin_namelog[ i ]->voteCount; - g_admin_namelog[ i ]->voteCount = 0; - } - } - else + for( bs = c; ( b && bs ) || as; l = t ) { - //for mute - if( G_IsMuted( client ) ) - { - g_admin_namelog[ i ]->muted = qtrue; - g_admin_namelog[ i ]->muteExpires = client->pers.muteExpires; - } - //denybuild - if( client->pers.denyBuild ) - { - g_admin_namelog[ i ]->denyBuild = qtrue; - } - //denyweapon humans - if( client->pers.denyHumanWeapons > 0 ) - { - g_admin_namelog[ i ]->denyHumanWeapons = client->pers.denyHumanWeapons; - } - //denyweapon aliens - if( client->pers.denyAlienClasses > 0 ) - { - g_admin_namelog[ i ]->denyAlienClasses = client->pers.denyAlienClasses; - } - //putteam spec - if( client->pers.specExpires > 0 ) - { - g_admin_namelog[ i ]->specExpires = client->pers.specExpires; - } - if( client->pers.voteCount > 0 ) - { - g_admin_namelog[ i ]->voteCount = client->pers.voteCount; - } + if( as && ( !bs || !b || compar( a, b ) <= 0 ) ) + t = a, a = a->next, as--; + else + t = b, b = b->next, bs--; + if( l ) + l->next = t; + else + *head = t; } - - return; } - } - if( i >= MAX_ADMIN_NAMELOGS ) - { - G_Printf( "G_admin_namelog_update: warning, g_admin_namelogs overflow\n" ); - return; - } - namelog = G_Alloc( sizeof( g_admin_namelog_t ) ); - memset( namelog, 0, sizeof( *namelog ) ); - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES ; j++ ) - namelog->name[ j ][ 0 ] = '\0'; - Q_strncpyz( namelog->ip, client->pers.ip, sizeof( namelog->ip ) ); - Q_strncpyz( namelog->guid, client->pers.guid, sizeof( namelog->guid ) ); - Q_strncpyz( namelog->name[ 0 ], client->pers.netname, - sizeof( namelog->name[ 0 ] ) ); - namelog->slot = ( disconnect ) ? -1 : clientNum; - schachtmeisterProcess( namelog ); - g_admin_namelog[ i ] = namelog; + l->next = NULL; + c *= 2; + } while( ns > 1 ); +} + +static int cmplevel( const void *a, const void *b ) +{ + return ((g_admin_level_t *)b)->level - ((g_admin_level_t *)a)->level; } -qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) +qboolean G_admin_readconfig( gentity_t *ent ) { - g_admin_level_t * l = NULL; + g_admin_level_t *l = NULL; g_admin_admin_t *a = NULL; g_admin_ban_t *b = NULL; g_admin_command_t *c = NULL; @@ -1722,54 +1084,82 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) char *t; qboolean level_open, admin_open, ban_open, command_open; int i; + char ip[ 44 ]; G_admin_cleanup(); if( !g_admin.string[ 0 ] ) { - ADMP( "^3!readconfig: g_admin is not set, not loading configuration " + ADMP( "^3readconfig: g_admin is not set, not loading configuration " "from a file\n" ); - admin_default_levels(); return qfalse; } - len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ) ; + len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ); if( len < 0 ) { - ADMP( va( "^3!readconfig: ^7could not open admin config file %s\n", - g_admin.string ) ); + G_Printf( "^3readconfig: ^7could not open admin config file %s\n", + g_admin.string ); admin_default_levels(); return qfalse; } - cnf = G_Alloc( len + 1 ); + cnf = BG_Alloc( len + 1 ); cnf2 = cnf; trap_FS_Read( cnf, len, f ); *( cnf + len ) = '\0'; trap_FS_FCloseFile( f ); - t = COM_Parse( &cnf ); + admin_level_maxname = 0; + level_open = admin_open = ban_open = command_open = qfalse; - while( *t ) + COM_BeginParseSession( g_admin.string ); + while( 1 ) { - if( !Q_stricmp( t, "[level]" ) || - !Q_stricmp( t, "[admin]" ) || - !Q_stricmp( t, "[ban]" ) || - !Q_stricmp( t, "[command]" ) ) - { + t = COM_Parse( &cnf ); + if( !*t ) + break; - if( level_open ) - g_admin_levels[ lc++ ] = l; - else if( admin_open ) - g_admin_admins[ ac++ ] = a; - else if( ban_open ) - g_admin_bans[ bc++ ] = b; - else if( command_open ) - g_admin_commands[ cc++ ] = c; - level_open = admin_open = - ban_open = command_open = qfalse; + if( !Q_stricmp( t, "[level]" ) ) + { + if( l ) + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + else + l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) ); + level_open = qtrue; + admin_open = ban_open = command_open = qfalse; + lc++; } - - if( level_open ) + else if( !Q_stricmp( t, "[admin]" ) ) + { + if( a ) + a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) ); + else + a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) ); + admin_open = qtrue; + level_open = ban_open = command_open = qfalse; + ac++; + } + else if( !Q_stricmp( t, "[ban]" ) ) + { + if( b ) + b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) ); + else + b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); + ban_open = qtrue; + level_open = admin_open = command_open = qfalse; + bc++; + } + else if( !Q_stricmp( t, "[command]" ) ) + { + if( c ) + c = c->next = BG_Alloc( sizeof( g_admin_command_t ) ); + else + c = g_admin_commands = BG_Alloc( sizeof( g_admin_command_t ) ); + command_open = qtrue; + level_open = admin_open = ban_open = qfalse; + cc++; + } + else if( level_open ) { if( !Q_stricmp( t, "level" ) ) { @@ -1778,6 +1168,10 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) else if( !Q_stricmp( t, "name" ) ) { admin_readconfig_string( &cnf, l->name, sizeof( l->name ) ); + // max printable name length for formatting + len = Q_PrintStrlen( l->name ); + if( len > admin_level_maxname ) + admin_level_maxname = len; } else if( !Q_stricmp( t, "flags" ) ) { @@ -1785,9 +1179,7 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) } else { - ADMP( va( "^3!readconfig: ^7[level] parse error near %s on line %d\n", - t, - COM_GetCurrentParseLine() ) ); + COM_ParseError( "[level] unrecognized token \"%s\"", t ); } } else if( admin_open ) @@ -1808,15 +1200,9 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) { admin_readconfig_string( &cnf, a->flags, sizeof( a->flags ) ); } - else if( !Q_stricmp( t, "seen" ) ) - { - admin_readconfig_int( &cnf, &a->seen ); - } else { - ADMP( va( "^3!readconfig: ^7[admin] parse error near %s on line %d\n", - t, - COM_GetCurrentParseLine() ) ); + COM_ParseError( "[admin] unrecognized token \"%s\"", t ); } } @@ -1832,7 +1218,8 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) } else if( !Q_stricmp( t, "ip" ) ) { - admin_readconfig_string( &cnf, b->ip, sizeof( b->ip ) ); + admin_readconfig_string( &cnf, ip, sizeof( ip ) ); + G_AddressParse( ip, &b->ip ); } else if( !Q_stricmp( t, "reason" ) ) { @@ -1846,23 +1233,13 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) { admin_readconfig_int( &cnf, &b->expires ); } - else if( !Q_stricmp( t, "suspend" ) ) - { - admin_readconfig_int( &cnf, &b->suspend ); - } else if( !Q_stricmp( t, "banner" ) ) { admin_readconfig_string( &cnf, b->banner, sizeof( b->banner ) ); } - else if( !Q_stricmp( t, "blevel" ) ) - { - admin_readconfig_int( &cnf, &b->bannerlevel ); - } else { - ADMP( va( "^3!readconfig: ^7[ban] parse error near %s on line %d\n", - t, - COM_GetCurrentParseLine() ) ); + COM_ParseError( "[ban] unrecognized token \"%s\"", t ); } } else if( command_open ) @@ -1885,6540 +1262,2367 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) } else { - ADMP( va( "^3!readconfig: ^7[command] parse error near %s on line %d\n", - t, - COM_GetCurrentParseLine() ) ); + COM_ParseError( "[command] unrecognized token \"%s\"", t ); } } - - if( !Q_stricmp( t, "[level]" ) ) - { - if( lc >= MAX_ADMIN_LEVELS ) - return qfalse; - l = G_Alloc( sizeof( g_admin_level_t ) ); - l->level = 0; - *l->name = '\0'; - *l->flags = '\0'; - level_open = qtrue; - } - else if( !Q_stricmp( t, "[admin]" ) ) - { - if( ac >= MAX_ADMIN_ADMINS ) - return qfalse; - a = G_Alloc( sizeof( g_admin_admin_t ) ); - *a->name = '\0'; - *a->guid = '\0'; - a->level = 0; - *a->flags = '\0'; - a->seen = 0; - admin_open = qtrue; - } - else if( !Q_stricmp( t, "[ban]" ) ) - { - if( bc >= MAX_ADMIN_BANS ) - return qfalse; - b = G_Alloc( sizeof( g_admin_ban_t ) ); - *b->name = '\0'; - *b->guid = '\0'; - *b->ip = '\0'; - *b->made = '\0'; - b->expires = 0; - b->suspend = 0; - *b->reason = '\0'; - b->bannerlevel = 0; - ban_open = qtrue; - } - else if( !Q_stricmp( t, "[command]" ) ) + else { - if( cc >= MAX_ADMIN_COMMANDS ) - return qfalse; - c = G_Alloc( sizeof( g_admin_command_t ) ); - *c->command = '\0'; - *c->exec = '\0'; - *c->desc = '\0'; - *c->flag = '\0'; - command_open = qtrue; + COM_ParseError( "unexpected token \"%s\"", t ); } - t = COM_Parse( &cnf ); } - if( level_open ) - { - - g_admin_levels[ lc++ ] = l; - } - if( admin_open ) - g_admin_admins[ ac++ ] = a; - if( ban_open ) - g_admin_bans[ bc++ ] = b; - if( command_open ) - g_admin_commands[ cc++ ] = c; - G_Free( cnf2 ); - ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n", + BG_Free( cnf2 ); + ADMP( va( "^3readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n", lc, ac, bc, cc ) ); if( lc == 0 ) admin_default_levels(); else { - char n[ MAX_NAME_LENGTH ] = {""}; - - // max printable name length for formatting - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - G_DecolorString( l->name, n ); - if( strlen( n ) > admin_level_maxname ) - admin_level_maxname = strlen( n ); - } + llsort( (struct llist **)&g_admin_levels, cmplevel ); + llsort( (struct llist **)&g_admin_admins, cmplevel ); } - // reset adminLevel + + // restore admin mapping for( i = 0; i < level.maxclients; i++ ) + { if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) - level.clients[ i ].pers.adminLevel = G_admin_level( &g_entities[ i ] ); + { + level.clients[ i ].pers.admin = + G_admin_admin( level.clients[ i ].pers.guid ); + if( level.clients[ i ].pers.admin ) + G_admin_authlog( &g_entities[ i ] ); + G_admin_cmdlist( &g_entities[ i ] ); + } + } + return qtrue; } -qboolean G_admin_time( gentity_t *ent, int skiparg ) +qboolean G_admin_time( gentity_t *ent ) { qtime_t qt; - int t; - t = trap_RealTime( &qt ); - ADMP( va( "^3!time: ^7local time is %02i:%02i:%02i\n", + trap_RealTime( &qt ); + ADMP( va( "^3time: ^7local time is %02i:%02i:%02i\n", qt.tm_hour, qt.tm_min, qt.tm_sec ) ); - return qtrue; } -static int G_admin_find_slot( gentity_t *ent, char *namearg, const char *command ) +// this should be in one of the headers, but it is only used here for now +namelog_t *G_NamelogFromString( gentity_t *ent, char *s ); + +/* +for consistency, we should be able to target a disconnected player with setlevel +but we can't use namelog and remain consistent, so the solution would be to make +everyone a real level 0 admin so they can be targeted until the next level +but that seems kind of stupid +*/ +qboolean G_admin_setlevel( gentity_t *ent ) { - char name[ MAX_NAME_LENGTH ]; - char testname[ MAX_NAME_LENGTH ]; - char *guid = NULL; - int matches = 0; - int id = -1; + char name[ MAX_NAME_LENGTH ] = {""}; + char lstr[ 12 ]; // 11 is max strlen() for 32-bit (signed) int + char testname[ MAX_NAME_LENGTH ] = {""}; int i; - qboolean numeric = qtrue; + gentity_t *vic = NULL; + g_admin_admin_t *a = NULL; + g_admin_level_t *l = NULL; + int na; - G_SanitiseString( namearg, name, sizeof( name ) ); - for( i = 0; i < sizeof( name ) && name[ i ] ; i++ ) + if( trap_Argc() < 3 ) { - if( !isdigit( name[ i ] ) ) - { - numeric = qfalse; - break; - } + ADMP( "^3setlevel: ^7usage: setlevel [name|slot#] [level]\n" ); + return qfalse; } - if( numeric ) - { - id = atoi( name ); - - if( id >= 0 && id < level.maxclients ) - { - gentity_t *vic; - - vic = &g_entities[ id ]; - if( vic && vic->client && vic->client->pers.connected != CON_DISCONNECTED ) - return id; - - ADMP( va( "^3!%s:^7 no one connected by that slot number\n", command ) ); - return -1; - } - if( id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS && - g_admin_admins[ id - MAX_CLIENTS ] ) - { - return id; - } - ADMP( va( "^3!%s:^7 no match for slot or admin number %d\n", command, id ) ); - return -1; - } + trap_Argv( 1, testname, sizeof( testname ) ); + trap_Argv( 2, lstr, sizeof( lstr ) ); - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && matches < 2; i++ ) - { - G_SanitiseString( g_admin_admins[ i ]->name, testname, sizeof( testname ) ); - if( strstr( testname, name ) ) - { - id = i + MAX_CLIENTS; - guid = g_admin_admins[ i ]->guid; - matches++; - } - } - for( i = 0; i < level.maxclients && matches < 2; i++ ) + if( !( l = G_admin_level( atoi( lstr ) ) ) ) { - if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) - continue; - - if( matches && guid && !Q_stricmp( level.clients[ i ].pers.guid, guid ) ) - continue; - - G_SanitiseString( level.clients[ i ].pers.netname, testname, sizeof( testname ) ); - if( strstr( testname, name ) ) - { - id = i; - matches++; - } + ADMP( "^3setlevel: ^7level is not defined\n" ); + return qfalse; } - if( matches == 0 ) + if( ent && l->level > + ( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ) ) { - ADMP( va( "^3!%s:^7 no match, use !listplayers or !listadmins to " - "find an appropriate number to use instead of name.\n", command ) ); - return -1; + ADMP( "^3setlevel: ^7you may not use setlevel to set a level higher " + "than your current level\n" ); + return qfalse; } - if( matches == 1 ) - return id; - - ADMP( va( "^3!%s:^7 multiple matches, use the admin number instead:\n", command ) ); - admin_listadmins( ent, 0, name, 0 ); - - return -1; -} - -static int G_admin_find_admin_slot( gentity_t *ent, char *namearg, char *command, char *nick, int nick_len ) -{ - gentity_t *vic; - char *guid; - int id; - int i; - - if ( nick ) - nick[ 0 ] = '\0'; + for( na = 0, a = g_admin_admins; a; na++, a = a->next ); - id = G_admin_find_slot( ent, namearg, command ); - if( id < 0 ) - return -1; - - if( id < MAX_CLIENTS ) + for( i = 0; testname[ i ] && isdigit( testname[ i ] ); i++ ); + if( !testname[ i ] ) { - vic = &g_entities[ id ]; - guid = vic->client->pers.guid; - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + int id = atoi( testname ); + if( id < MAX_CLIENTS ) { - if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) ) + vic = &g_entities[ id ]; + if( !vic || !vic->client || vic->client->pers.connected == CON_DISCONNECTED ) { - id = i + MAX_CLIENTS; - if( nick ) - Q_strncpyz( nick, vic->client->pers.netname, nick_len ); - break; + ADMP( va( "^3setlevel: ^7no player connected in slot %d\n", id ) ); + return qfalse; } } - if( id < MAX_CLIENTS ) + else if( id < na + MAX_CLIENTS ) + for( i = 0, a = g_admin_admins; i < id - MAX_CLIENTS; i++, a = a->next ); + else { - ADMP( va( "^3!%s:^7 player is not !registered\n", command ) ); - return -1; + ADMP( va( "^3setlevel: ^7%s not in range 1-%d\n", + testname, na + MAX_CLIENTS - 1 ) ); + return qfalse; } } + else + G_SanitiseString( testname, name, sizeof( name ) ); - id -= MAX_CLIENTS; - if( nick && !nick[ 0 ] ) - Q_strncpyz( nick, g_admin_admins[ id ]->name, nick_len ); - - return id; -} - -qboolean G_admin_setlevel( gentity_t *ent, int skiparg ) -{ - char lstr[ 11 ]; // 10 is max strlen() for 32-bit int - char adminname[ MAX_NAME_LENGTH ] = {""}; - char testname[ MAX_NAME_LENGTH ] = {""}; - char guid[ 33 ]; - int l, i; - gentity_t *vic = NULL; - qboolean updated = qfalse; - g_admin_admin_t *a; - qboolean found = qfalse; - int id = -1; - - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!setlevel: ^7usage: !setlevel [name|slot#] [level]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, testname, sizeof( testname ) ); - G_SayArgv( 2 + skiparg, lstr, sizeof( lstr ) ); - l = atoi( lstr ); - - if( ent && l > ent->client->pers.adminLevel ) + if( vic ) + a = vic->client->pers.admin; + else if( !a ) { - ADMP( "^3!setlevel: ^7you may not use !setlevel to set a level higher " - "than your current level\n" ); - return qfalse; - } + g_admin_admin_t *wa; + int matches = 0; - // if admin is activated for the first time on a running server, we need - // to ensure at least the default levels get created - if( !ent && !g_admin_levels[ 0 ] ) - G_admin_readconfig(NULL, 0); - - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - if( g_admin_levels[ i ]->level == l ) + for( wa = g_admin_admins; wa && matches < 2; wa = wa->next ) { - found = qtrue; - break; + G_SanitiseString( wa->name, testname, sizeof( testname ) ); + if( strstr( testname, name ) ) + { + a = wa; + matches++; + } } - } - if( !found ) - { - ADMP( "^3!setlevel: ^7level is not defined\n" ); - return qfalse; - } - id = G_admin_find_slot( ent, testname, "setlevel" ); - if( id >=0 && id < MAX_CLIENTS ) - { - vic = &g_entities[ id ]; - Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) ); - Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) ); - } - else if( id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS && - g_admin_admins[ id - MAX_CLIENTS ] ) - { - Q_strncpyz( adminname, g_admin_admins[ id - MAX_CLIENTS ]->name, - sizeof( adminname ) ); - Q_strncpyz( guid, g_admin_admins[ id - MAX_CLIENTS ]->guid, - sizeof( guid ) ); - for( i = 0; i < level.maxclients; i++ ) + for( i = 0; i < level.maxclients && matches < 2; i++ ) { if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) continue; - if( !Q_stricmp( level.clients[ i ].pers.guid, guid ) ) + + if( matches && level.clients[ i ].pers.admin && + level.clients[ i ].pers.admin == a ) + { + vic = &g_entities[ i ]; + continue; + } + + G_SanitiseString( level.clients[ i ].pers.netname, testname, + sizeof( testname ) ); + if( strstr( testname, name ) ) { vic = &g_entities[ i ]; - Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) ); + a = vic->client->pers.admin; + matches++; } } - } - else - { - return qfalse; - } - if( !Q_stricmp( guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) - { - ADMP( va( "^3!setlevel: ^7%s does not have a valid GUID\n", adminname ) ); - return qfalse; + if( matches == 0 ) + { + ADMP( "^3setlevel:^7 no match. use listplayers or listadmins to " + "find an appropriate number to use instead of name.\n" ); + return qfalse; + } + if( matches > 1 ) + { + ADMP( "^3setlevel:^7 more than one match. Use the admin number " + "instead:\n" ); + admin_listadmins( ent, 0, name ); + return qfalse; + } } - if( ent && !admin_higher_guid( ent->client->pers.guid, guid ) ) + + if( ent && !admin_higher_admin( ent->client->pers.admin, a ) ) { - ADMP( "^3!setlevel: ^7sorry, but your intended victim has a higher" + ADMP( "^3setlevel: ^7sorry, but your intended victim has a higher" " admin level than you\n" ); return qfalse; } - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ];i++ ) + if( vic && vic->client->pers.guidless ) { - if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) ) - { - g_admin_admins[ i ]->level = l; - Q_strncpyz( g_admin_admins[ i ]->name, adminname, - sizeof( g_admin_admins[ i ]->name ) ); - updated = qtrue; - } + ADMP( va( "^3setlevel: ^7%s^7 has no GUID\n", vic->client->pers.netname ) ); + return qfalse; } - if( !updated ) + + if( !a && vic ) { - if( i == MAX_ADMIN_ADMINS ) - { - ADMP( "^3!setlevel: ^7too many admins\n" ); - return qfalse; - } - a = G_Alloc( sizeof( g_admin_admin_t ) ); - a->level = l; - Q_strncpyz( a->name, adminname, sizeof( a->name ) ); - Q_strncpyz( a->guid, guid, sizeof( a->guid ) ); - *a->flags = '\0'; - g_admin_admins[ i ] = a; + for( a = g_admin_admins; a && a->next; a = a->next ); + if( a ) + a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) ); + else + a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) ); + vic->client->pers.admin = a; + Q_strncpyz( a->guid, vic->client->pers.guid, sizeof( a->guid ) ); } - AP( va( - "print \"^3!setlevel: ^7%s^7 was given level %d admin rights by %s\n\"", - adminname, l, ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + a->level = l->level; + if( vic ) + Q_strncpyz( a->name, vic->client->pers.netname, sizeof( a->name ) ); + + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", a->level, a->guid, + a->name ) ); + + AP( va( + "print \"^3setlevel: ^7%s^7 was given level %d admin rights by %s\n\"", + a->name, a->level, ( ent ) ? ent->client->pers.netname : "console" ) ); + + admin_writeconfig(); if( vic ) { - vic->client->pers.adminLevel = l; - G_admin_set_adminname( vic ); + G_admin_authlog( vic ); + G_admin_cmdlist( vic ); } - - if( !g_admin.string[ 0 ] ) - ADMP( "^3!setlevel: ^7WARNING g_admin not set, not saving admin record " - "to a file\n" ); - else - admin_writeconfig(); return qtrue; } -static int SortFlags( const void *pa, const void *pb ) +static void admin_create_ban( gentity_t *ent, + char *netname, + char *guid, + addr_t *ip, + int seconds, + char *reason ) { - char *a = (char *)pa; - char *b = (char *)pb; - - if( *a == '-' || *a == '+' ) - a++; - if( *b == '-' || *b == '+' ) - b++; - return strcmp(a, b); -} + g_admin_ban_t *b = NULL; + qtime_t qt; + int t; + int i; + char *name; + char disconnect[ MAX_STRING_CHARS ]; -#define MAX_USER_FLAGS 200 -const char *G_admin_user_flag( char *oldflags, char *flag, qboolean add, qboolean clear, - char *newflags, int size ) -{ - char *token, *token_p; - char *key; - char head_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; - char tail_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; - char allflag[ MAX_ADMIN_FLAG_LEN ]; - char newflag[ MAX_ADMIN_FLAG_LEN ]; - int head_count = 0; - int tail_count = 0; - qboolean flagset = qfalse; - int i; + t = trap_RealTime( &qt ); - if( !flag[ 0 ] ) + for( b = g_admin_bans; b; b = b->next ) { - return "invalid admin flag"; + if( !b->next ) + break; } - allflag[ 0 ] = '\0'; - token_p = oldflags; - while( *( token = COM_Parse( &token_p ) ) ) + if( b ) { - key = token; - if( *key == '-' || *key == '+' ) - key++; + if( !b->next ) + b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) ); + } + else + b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); - if( !strcmp( key, flag ) ) - { - if( flagset ) - continue; - flagset = qtrue; - if( clear ) - { - // clearing ALLFLAGS will result in clearing any following flags - if( !strcmp( key, ADMF_ALLFLAGS ) ) - break; - else - continue; - } - Com_sprintf( newflag, sizeof( newflag ), "%s%s", - ( add ) ? "+" : "-", key ); - } - else - { - Q_strncpyz( newflag, token, sizeof( newflag ) ); - } + Q_strncpyz( b->name, netname, sizeof( b->name ) ); + Q_strncpyz( b->guid, guid, sizeof( b->guid ) ); + memcpy( &b->ip, ip, sizeof( b->ip ) ); - if( !strcmp( key, ADMF_ALLFLAGS ) ) - { - if( !allflag[ 0 ] ) - Q_strncpyz( allflag, newflag, sizeof( allflag ) ); - continue; - } + Com_sprintf( b->made, sizeof( b->made ), "%04i-%02i-%02i %02i:%02i:%02i", + qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, + qt.tm_hour, qt.tm_min, qt.tm_sec ); - if( !allflag[ 0 ] ) - { - if( head_count < MAX_USER_FLAGS ) - { - Q_strncpyz( head_flags[ head_count ], newflag, - sizeof( head_flags[ head_count ] ) ); - head_count++; - } - } - else - { - if( tail_count < MAX_USER_FLAGS ) - { - Q_strncpyz( tail_flags[ tail_count ], newflag, - sizeof( tail_flags[ tail_count ] ) ); - tail_count++; - } - } - } + if( ent && ent->client->pers.admin ) + name = ent->client->pers.admin->name; + else if( ent ) + name = ent->client->pers.netname; + else + name = "console"; + + Q_strncpyz( b->banner, name, sizeof( b->banner ) ); + if( !seconds ) + b->expires = 0; + else + b->expires = t + seconds; + if( !*reason ) + Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) ); + else + Q_strncpyz( b->reason, reason, sizeof( b->reason ) ); + + G_admin_ban_message( NULL, b, disconnect, sizeof( disconnect ), NULL, 0 ); - if( !flagset && !clear ) + for( i = 0; i < level.maxclients; i++ ) { - if( !strcmp( flag, ADMF_ALLFLAGS ) ) - { - Com_sprintf( allflag, sizeof( allflag ), "%s%s", - ( add ) ? "" : "-", ADMF_ALLFLAGS ); - } - else if( !allflag[ 0 ] ) - { - if( head_count < MAX_USER_FLAGS ) - { - Com_sprintf( head_flags[ head_count ], sizeof( head_flags[ head_count ] ), - "%s%s", ( add ) ? "" : "-", flag ); - head_count++; - } - } - else + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + if( G_admin_ban_matches( b, &g_entities[ i ] ) ) { - if( tail_count < MAX_USER_FLAGS ) - { - Com_sprintf( tail_flags[ tail_count ], sizeof( tail_flags[ tail_count ] ), - "%s%s", ( add ) ? "+" : "-", flag ); - tail_count++; - } + trap_SendServerCommand( i, va( "disconnect \"%s\"", disconnect ) ); + + trap_DropClient( i, va( "has been kicked by %s^7. reason: %s", + b->banner, b->reason ) ); } } +} - qsort( head_flags, head_count, sizeof( head_flags[ 0 ] ), SortFlags ); - qsort( tail_flags, tail_count, sizeof( tail_flags[ 0 ] ), SortFlags ); - - // rebuild flags - newflags[ 0 ] = '\0'; - for( i = 0; i < head_count; i++ ) - { - Q_strcat( newflags, size, - va( "%s%s", ( i ) ? " ": "", head_flags[ i ] ) ); - } - if( allflag[ 0 ] ) +int G_admin_parse_time( const char *time ) +{ + int seconds = 0, num = 0; + if( !*time ) + return -1; + while( *time ) { - Q_strcat( newflags, size, - va( "%s%s", ( newflags[ 0 ] ) ? " ": "", allflag ) ); + if( !isdigit( *time ) ) + return -1; + while( isdigit( *time ) ) + num = num * 10 + *time++ - '0'; - for( i = 0; i < tail_count; i++ ) + if( !*time ) + break; + switch( *time++ ) { - Q_strcat( newflags, size, - va( " %s", tail_flags[ i ] ) ); + case 'w': num *= 7; + case 'd': num *= 24; + case 'h': num *= 60; + case 'm': num *= 60; + case 's': break; + default: return -1; } + seconds += num; + num = 0; } - - return NULL; + if( num ) + seconds += num; + return seconds; } -typedef struct { - char *flag; - char *description; -} AdminFlagListEntry_t; -static AdminFlagListEntry_t adminFlagList[] = -{ - { ADMF_ACTIVITY, "inactivity rules do not apply" }, - { ADMF_ADMINCHAT, "can see and use admin chat" }, - { ADMF_HIGHADMINCHAT, "can see and use high admin chat" }, - { ADMF_ALLFLAGS, "has all flags and can use any command" }, - { ADMF_BAN_IMMUNITY, "immune from IP bans" }, - { ADMF_CAN_PERM_BAN, "can permanently ban players" }, - { ADMF_DBUILDER, "permanent designated builder" }, - { ADMF_FORCETEAMCHANGE, "team balance rules do not apply" }, - { ADMF_INCOGNITO, "does not show as admin in !listplayers" }, - { ADMF_SEESINCOGNITO, "sees registered name of players flagged with INCOGNITO" }, - { ADMF_IMMUNITY, "cannot be vote kicked or muted" }, - { ADMF_IMMUTABLE, "admin commands cannot be used on them" }, - { ADMF_NOCENSORFLOOD, "no flood protection" }, - { ADMF_NO_VOTE_LIMIT, "vote limitations do not apply" }, - { ADMF_SEESFULLLISTPLAYERS, "sees all info in !listplayers" }, - { ADMF_SPEC_ALLCHAT, "can see team chat as spectator" }, - { ADMF_ADMINSTEALTH, "uses admin stealth" }, - { ADMF_TEAMCHANGEFREE, "keeps credits on team switch" }, - { ADMF_TEAMCHAT_CMD, "can run commands from team chat" }, - { ADMF_UNACCOUNTABLE, "does not need to specify reason for kick/ban" }, - { ADMF_NOSCRIMRESTRICTION, "team joining, vote and chat restrictions during scrims do not apply" }, - { ADMF_NO_BUILD, "can not build" }, - { ADMF_NO_CHAT, "can not talk" }, - { ADMF_NO_VOTE, "can not call votes" }, - { ADMF_NOAUTOBAHN, "ignored by the Autobahn system" } -}; -static int adminNumFlags= sizeof( adminFlagList ) / sizeof( adminFlagList[ 0 ] ); - -#define MAX_LISTCOMMANDS 128 -qboolean G_admin_flaglist( gentity_t *ent, int skiparg ) +qboolean G_admin_setdevmode( gentity_t *ent ) { - qboolean shown[ MAX_LISTCOMMANDS ]; - int i, j; - int count = 0; - - ADMBP_begin(); - - ADMBP( "^3Ability flags:\n" ); - - for( i = 0; i < adminNumFlags; i++ ) + char str[ 5 ]; + if( trap_Argc() != 2 ) { - ADMBP( va( " %s%-20s ^7%s\n", - ( adminFlagList[ i ].flag[ 0 ] != '.' ) ? "^5" : "^1", - adminFlagList[ i ].flag, - adminFlagList[ i ].description ) ); + ADMP( "^3setdevmode: ^7usage: setdevmode [on|off]\n" ); + return qfalse; } - - ADMBP( "^3Command flags:\n" ); - - memset( shown, 0, sizeof( shown ) ); - for( i = 0; i < adminNumCmds; i++ ) - { - if( i < MAX_LISTCOMMANDS && shown[ i ] ) - continue; - ADMBP( va( " ^5%-20s^7", g_admin_cmds[ i ].flag ) ); - for( j = i; j < adminNumCmds; j++ ) - { - if( !strcmp( g_admin_cmds[ j ].flag, g_admin_cmds[ i ].flag ) ) - { - ADMBP( va( " %s", g_admin_cmds[ j ].keyword ) ); - if( j < MAX_LISTCOMMANDS ) - shown[ j ] = qtrue; - } - } - ADMBP( "\n" ); - count++; - } - - ADMBP( va( "^3!flaglist: ^7listed %d abilities and %d command flags\n", - adminNumFlags, count ) ); - - ADMBP_end(); - - return qtrue; -} - -qboolean G_admin_flag( gentity_t *ent, int skiparg ) -{ - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - char name[ MAX_NAME_LENGTH ]; - char flagbuf[ MAX_ADMIN_FLAG_LEN ]; - char *flag; - int id; - char adminname[ MAX_NAME_LENGTH ] = {""}; - const char *result; - qboolean add = qtrue; - qboolean clear = qfalse; - int admin_level = -1; - int i, level; - - G_SayArgv( skiparg, command, sizeof( command ) ); - cmd = command; - if( *cmd == '!' ) - cmd++; - - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( va( "^3!%s: ^7usage: !%s slot# flag\n", cmd, cmd ) ); - return qfalse; - } - - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( name[ 0 ] == '*' ) - { - if( ent ) - { - ADMP( va( "^3!%s: only console can change admin level flags\n", cmd ) ); - return qfalse; - } - id = atoi( name + 1 ); - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - if( g_admin_levels[ i ]->level == id ) - { - admin_level = i; - break; - } - } - if( admin_level < 0 ) - { - ADMP( va( "^3!%s: admin level %d does not exist\n", cmd, id ) ); - return qfalse; - } - Com_sprintf( adminname, sizeof( adminname ), "admin level %d", id ); - } - else - { - id = G_admin_find_admin_slot( ent, name, cmd, adminname, sizeof( adminname ) ); - if( id < 0 ) - return qfalse; - - if( ent && !admin_higher_guid( ent->client->pers.guid, g_admin_admins[ id ]->guid ) ) - { - ADMP( va( "^3%s:^7 your intended victim has a higher admin level than you\n", cmd ) ); - return qfalse; - } - } - - if( G_SayArgc() < 3 + skiparg ) - { - flag = ""; - level = 0; - if( admin_level < 0 ) - { - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - if( g_admin_admins[ id ]->level == g_admin_levels[ i ]->level ) - { - flag = g_admin_levels[ i ]->flags; - level = g_admin_admins[ id ]->level; - break; - } - } - ADMP( va( "^3%s:^7 flags for %s^7 are '^3%s^7'\n", - cmd, adminname, g_admin_admins[ id ]->flags) ); - } - else - { - flag = g_admin_levels[ admin_level ]->flags; - level = g_admin_levels[ admin_level ]->level; - } - ADMP( va( "^3%s:^7 level %d flags are '%s'\n", - cmd, level, flag ) ); - - return qtrue; - } - - G_SayArgv( 2 + skiparg, flagbuf, sizeof( flagbuf ) ); - flag = flagbuf; - if( flag[ 0 ] == '-' || flag[ 0 ] == '+' ) - { - add = ( flag[ 0 ] == '+' ); - flag++; - } - if( ent && !Q_stricmp( ent->client->pers.guid, g_admin_admins[ id ]->guid ) ) - { - ADMP( va( "^3%s:^7 you may not change your own flags (use rcon)\n", cmd ) ); - return qfalse; - } - if( flag[ 0 ] != '.' && !G_admin_permission( ent, flag ) ) - { - ADMP( va( "^3%s:^7 you can only change flags that you also have\n", cmd ) ); - return qfalse; - } - - if( !Q_stricmp( cmd, "unflag" ) ) - { - clear = qtrue; - } - - if( admin_level < 0 ) - { - result = G_admin_user_flag( g_admin_admins[ id ]->flags, flag, add, clear, - g_admin_admins[ id ]->flags, sizeof( g_admin_admins[ id ]->flags ) ); - } - else - { - result = G_admin_user_flag( g_admin_levels[ admin_level ]->flags, flag, add, clear, - g_admin_levels[ admin_level ]->flags, - sizeof( g_admin_levels[ admin_level ]->flags ) ); - } - if( result ) - { - ADMP( va( "^3!flag: ^7an error occured setting flag '^3%s^7', %s\n", - flag, result ) ); - return qfalse; - } - - if( !Q_stricmp( cmd, "flag" ) ) - { - G_AdminsPrintf( "^3!%s: ^7%s^7 was %s admin flag '%s' by %s\n", - cmd, adminname, - ( add ) ? "given" : "denied", - flag, - ( ent ) ? ent->client->pers.netname : "console" ); - } - else - { - G_AdminsPrintf( "^3!%s: ^7admin flag '%s' for %s^7 cleared by %s\n", - cmd, flag, adminname, - ( ent ) ? ent->client->pers.netname : "console" ); - } - - if( !g_admin.string[ 0 ] ) - ADMP( va( "^3!%s: ^7WARNING g_admin not set, not saving admin record " - "to a file\n", cmd ) ); - else - admin_writeconfig(); - - return qtrue; -} - -int G_admin_parse_time( const char *time ) -{ - int seconds = 0, num = 0; - int i; - for( i = 0; time[ i ]; i++ ) - { - if( isdigit( time[ i ] ) ) - { - num = num * 10 + time[ i ] - '0'; - continue; - } - if( i == 0 || !isdigit( time[ i - 1 ] ) ) - return -1; - switch( time[ i ] ) - { - case 'w': num *= 7; - case 'd': num *= 24; - case 'h': num *= 60; - case 'm': num *= 60; - case 's': break; - default: return -1; - } - seconds += num; - num = 0; - } - if( num ) - seconds += num; - // overflow - if( seconds < 0 ) - seconds = 0; - return seconds; -} - -static qboolean admin_create_ban( gentity_t *ent, - char *netname, - char *guid, - char *ip, - int seconds, - char *reason ) -{ - g_admin_ban_t *b = NULL; - qtime_t qt; - int t; - int i; - - t = trap_RealTime( &qt ); - b = G_Alloc( sizeof( g_admin_ban_t ) ); - - if( !b ) - return qfalse; - - Q_strncpyz( b->name, netname, sizeof( b->name ) ); - Q_strncpyz( b->guid, guid, sizeof( b->guid ) ); - Q_strncpyz( b->ip, ip, sizeof( b->ip ) ); - b->suspend = 0; - - //strftime( b->made, sizeof( b->made ), "%m/%d/%y %H:%M:%S", lt ); - Q_strncpyz( b->made, va( "%02i/%02i/%02i %02i:%02i:%02i", - (qt.tm_mon + 1), qt.tm_mday, (qt.tm_year - 100), - qt.tm_hour, qt.tm_min, qt.tm_sec ), - sizeof( b->made ) ); - - Q_strncpyz( b->banner, G_admin_get_adminname( ent ), sizeof( b->banner ) ); - - if( ent ) - b->bannerlevel = G_admin_level( ent ); - else - b->bannerlevel = 0; - - if( !seconds ) - b->expires = 0; - else - b->expires = t + seconds; - if( !*reason ) - Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) ); - else - Q_strncpyz( b->reason, reason, sizeof( b->reason ) ); - for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) - ; - if( i == MAX_ADMIN_BANS ) - { - ADMP( "^3!ban: ^7too many bans\n" ); - G_Free( b ); - return qfalse; - } - g_admin_bans[ i ] = b; - return qtrue; -} - -qboolean G_admin_kick( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; - int minargc; - gentity_t *vic; - char notice[51]; - - trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); - - minargc = 3 + skiparg; - if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) - minargc = 2 + skiparg; - - if( G_SayArgc() < minargc ) - { - ADMP( "^3!kick: ^7usage: !kick [name] [reason]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - reason = G_SayConcatArgs( 2 + skiparg ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!kick: ^7%s\n", err ) ); - return qfalse; - } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( "^3!kick: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); - return qfalse; - } - vic = &g_entities[ pids[ 0 ] ]; - admin_create_ban( ent, - vic->client->pers.netname, - vic->client->pers.guid, - vic->client->pers.ip, G_admin_parse_time( g_adminTempBan.string ), - ( *reason ) ? reason : "kicked by admin" ); - if( g_admin.string[ 0 ] ) - admin_writeconfig(); - - trap_SendServerCommand( pids[ 0 ], - va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", - ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", - ( *reason ) ? reason : "kicked by admin", notice ) ); - - trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s", - ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console", - ( *reason ) ? reason : "kicked by admin" ) ); - - return qtrue; -} - -qboolean G_admin_ban( gentity_t *ent, int skiparg ) -{ - int seconds; - char search[ MAX_NAME_LENGTH ]; - char secs[ 7 ]; - char *reason; - int minargc; - char duration[ 32 ]; - int logmatch = -1, logmatches = 0; - int i, j; - qboolean exactmatch = qfalse; - char n2[ MAX_NAME_LENGTH ]; - char s2[ MAX_NAME_LENGTH ]; - char guid_stub[ 9 ]; - char notice[51]; - - trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); - - if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && - G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) - { - minargc = 2 + skiparg; - } - else if( ( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) || g_adminMaxBan.integer ) || - G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) - { - minargc = 3 + skiparg; - } - else - { - minargc = 4 + skiparg; - } - if( G_SayArgc() < minargc ) - { - ADMP( "^3!ban: ^7usage: !ban [name|slot|ip] [time] [reason]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, search, sizeof( search ) ); - G_SanitiseString( search, s2, sizeof( s2 ) ); - G_SayArgv( 2 + skiparg, secs, sizeof( secs ) ); - - seconds = G_admin_parse_time( secs ); - if( seconds <= 0 ) - { - if( g_adminMaxBan.integer && !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) - { - ADMP( va( "^3!ban: ^7using your admin level's maximum ban length of %s\n", - g_adminMaxBan.string ) ); - seconds = G_admin_parse_time( g_adminMaxBan.string ); - } - else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) - { - seconds = 0; - } - else - { - ADMP( "^3!ban: ^7ban time must be positive\n" ); - return qfalse; - } - reason = G_SayConcatArgs( 2 + skiparg ); - } - else - { - reason = G_SayConcatArgs( 3 + skiparg ); - - if( g_adminMaxBan.integer && - seconds > G_admin_parse_time( g_adminMaxBan.string ) && - !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) - { - seconds = G_admin_parse_time( g_adminMaxBan.string ); - ADMP( va( "^3!ban: ^7ban length limited to %s for your admin level\n", - g_adminMaxBan.string ) ); - } - } - - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - // skip players in the namelog who have already been banned - if( g_admin_namelog[ i ]->banned ) - continue; - - // skip disconnected players when banning on slot number - if( g_admin_namelog[ i ]->slot == -1 ) - continue; - - if( !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) ) - { - logmatches = 1; - logmatch = i; - exactmatch = qtrue; - break; - } - } - - for( i = 0; - !exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; - i++ ) - { - // skip players in the namelog who have already been banned - if( g_admin_namelog[ i ]->banned ) - continue; - - if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) ) - { - logmatches = 1; - logmatch = i; - exactmatch = qtrue; - break; - } - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES - && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) - { - G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); - if( strstr( n2, s2 ) ) - { - if( logmatch != i ) - logmatches++; - logmatch = i; - } - } - } - - if( !logmatches ) - { - ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" ); - return qfalse; - } - else if( logmatches > 1 ) - { - ADMBP_begin(); - ADMBP( "^3!ban: ^7multiple recent clients match name, use IP or slot#:\n" ); - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - for( j = 0; j < 8; j++ ) - guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ]; - guid_stub[ j ] = '\0'; - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES - && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) - { - G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); - if( strstr( n2, s2 ) ) - { - if( g_admin_namelog[ i ]->slot > -1 ) - ADMBP( "^3" ); - ADMBP( va( "%-2s (*%s) %15s ^7'%s^7'\n", - (g_admin_namelog[ i ]->slot > -1) ? - va( "%d", g_admin_namelog[ i ]->slot ) : "-", - guid_stub, - g_admin_namelog[ i ]->ip, - g_admin_namelog[ i ]->name[ j ] ) ); - } - } - } - ADMBP_end(); - return qfalse; - } - - G_admin_duration( ( seconds ) ? seconds : -1, - duration, sizeof( duration ) ); - - if( ent && !admin_higher_guid( ent->client->pers.guid, - g_admin_namelog[ logmatch ]->guid ) ) - { - - ADMP( "^3!ban: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); - return qfalse; - } - - admin_create_ban( ent, - g_admin_namelog[ logmatch ]->name[ 0 ], - g_admin_namelog[ logmatch ]->guid, - g_admin_namelog[ logmatch ]->ip, - seconds, reason ); - - g_admin_namelog[ logmatch ]->banned = qtrue; - - if( !g_admin.string[ 0 ] ) - ADMP( "^3!ban: ^7WARNING g_admin not set, not saving ban to a file\n" ); - else - admin_writeconfig(); - - if( g_admin_namelog[ logmatch ]->slot == -1 ) - { - // client is already disconnected so stop here - AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7 " - "duration: %s, reason: %s\n\"", - g_admin_namelog[ logmatch ]->name[ 0 ], - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - duration, - ( *reason ) ? reason : "banned by admin" ) ); - return qtrue; - } - - trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot, - va( "disconnect \"You have been banned.\n" - "admin:\n%s^7\nduration:\n%s\nreason:\n%s\n%s\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - duration, - ( *reason ) ? reason : "banned by admin", notice ) ); - - trap_DropClient( g_admin_namelog[ logmatch ]->slot, - va( "banned by %s^7, duration: %s, reason: %s", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - duration, - ( *reason ) ? reason : "banned by admin" ) ); - return qtrue; -} - -// If true then don't let the player join a team, use the chat or run commands. -qboolean G_admin_is_restricted(gentity_t *ent, qboolean sendMessage) -{ - schachtmeisterJudgement_t *j = NULL; - int i; - - // Never restrict admins or whitelisted players. - if (G_admin_permission(ent, ADMF_NOAUTOBAHN) || - G_admin_permission(ent, ADMF_IMMUNITY)) - return qfalse; - - // Find the relevant namelog. - // FIXME: this shouldn't require looping over *all* namelogs. - for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; i++) { - if (g_admin_namelog[i]->slot == ent - g_entities) { - j = &g_admin_namelog[i]->smj; - break; - } - } - - // A missing namelog shouldn't happen. - if (!j) - return qfalse; - - // Restrictions concern only unrated players. - if (j->ratingTime) - return qfalse; - - // Don't wait forever, allow up to 15 seconds. - if (level.time - j->queryTime >= 15000) - return qfalse; - - if (sendMessage) - trap_SendServerCommand(ent - g_entities, "print \"Please wait a moment before doing anything.\n\""); - - return qtrue; -} - -static void admin_autobahn(gentity_t *ent, int rating) -{ - // Allow per-GUID exceptions and never autoban admins. - if (G_admin_permission(ent, ADMF_NOAUTOBAHN) || - G_admin_permission(ent, ADMF_IMMUNITY)) - return; - - // Don't do anything if the rating is clear. - if (rating >= g_schachtmeisterClearThreshold.integer) - return; - - // Ban only if the rating is low enough. - if (rating > g_schachtmeisterAutobahnThreshold.integer) { - G_AdminsPrintf("%s^7 (#%d) has rating %d\n", - ent->client->pers.netname, ent - g_entities, - rating); - return; - } - - G_LogAutobahn(ent, NULL, rating, qfalse); - - trap_SendServerCommand(ent - g_entities, va("disconnect \"%s\"\n", - g_schachtmeisterAutobahnMessage.string)); - trap_DropClient(ent - g_entities, "dropped by the Autobahn"); -} - -void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment ) -{ - int i; - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - if( !strcmp( g_admin_namelog[ i ]->ip, ipa ) ) - { - schachtmeisterJudgement_t *j = &g_admin_namelog[ i ]->smj; - - j->ratingTime = level.time; - j->queryTime = 0; - j->dispatchTime = 0; - - j->rating = rating; - - if( j->comment ) - G_Free( j->comment ); - - if( comment ) - { - j->comment = G_Alloc( strlen( comment ) + 1 ); - strcpy( j->comment, comment ); - } - else - j->comment = NULL; - - if( g_admin_namelog[ i ]->slot != -1 ) - admin_autobahn( g_entities + g_admin_namelog[ i ]->slot, j->rating ); - } - } -} - -qboolean G_admin_adjustban( gentity_t *ent, int skiparg ) -{ - int bnum; - int length; - int expires; - int time = trap_RealTime( NULL ); - char duration[ 32 ] = {""}; - char *reason; - char bs[ 5 ]; - char secs[ MAX_TOKEN_CHARS ]; - char mode = '\0'; - - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!adjustban: ^7usage: !adjustban [ban#] [time] [reason]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); - bnum = atoi( bs ); - if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] ) - { - ADMP( "^3!adjustban: ^7invalid ban#\n" ); - return qfalse; - } - if( g_admin_bans[ bnum - 1 ]->expires == 0 && - !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) - { - ADMP( "^3!adjustban: ^7you cannot modify permanent bans\n" ); - return qfalse; - } - if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) - { - ADMP( "^3!adjustban: ^7you cannot modify Bans made by admins higher than you\n" ); - return qfalse; - } - if( g_adminMaxBan.integer && - !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && - g_admin_bans[ bnum - 1 ]->expires - time > G_admin_parse_time( g_adminMaxBan.string ) ) - { - ADMP( va( "^3!adjustban: ^7your admin level cannot modify bans longer than %s\n", - g_adminMaxBan.string ) ); - return qfalse; - } - G_SayArgv( 2 + skiparg, secs, sizeof( secs ) ); - if( secs[ 0 ] == '+' || secs[ 0 ] == '-' ) - mode = secs[ 0 ]; - length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] ); - if( length < 0 ) - skiparg--; - else - { - if( length ) - { - if( g_admin_bans[ bnum - 1 ]->expires == 0 && mode ) - { - ADMP( "^3!adjustban: ^7new duration must be explicit\n" ); - return qfalse; - } - if( mode == '+' ) - expires = g_admin_bans[ bnum - 1 ]->expires + length; - else if( mode == '-' ) - expires = g_admin_bans[ bnum - 1 ]->expires - length; - else - expires = time + length; - if( expires <= time ) - { - ADMP( "^3!adjustban: ^7ban time must be positive\n" ); - return qfalse; - } - if( g_adminMaxBan.integer && - !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && - expires - time > G_admin_parse_time( g_adminMaxBan.string ) ) - { - ADMP( va( "^3!adjustban: ^7ban length is limited to %s for your admin level\n", - g_adminMaxBan.string ) ); - length = G_admin_parse_time( g_adminMaxBan.string ); - expires = time + length; - } - } - else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) - expires = 0; - else - { - ADMP( "^3!adjustban: ^7ban time must be positive\n" ); - return qfalse; - } - - g_admin_bans[ bnum - 1 ]->expires = expires; - G_admin_duration( ( expires ) ? expires - time : -1, - duration, sizeof( duration ) ); - } - reason = G_SayConcatArgs( 3 + skiparg ); - if( *reason ) - Q_strncpyz( g_admin_bans[ bnum - 1 ]->reason, reason, - sizeof( g_admin_bans[ bnum - 1 ]->reason ) ); - AP( va( "print \"^3!adjustban: ^7ban #%d for %s^7 has been updated by %s^7 " - "%s%s%s%s%s\n\"", - bnum, - g_admin_bans[ bnum - 1 ]->name, - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - ( length >= 0 ) ? "duration: " : "", - duration, - ( length >= 0 && *reason ) ? ", " : "", - ( *reason ) ? "reason: " : "", - reason ) ); - if( ent ) { - Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, G_admin_get_adminname( ent ), - sizeof( g_admin_bans[ bnum - 1 ]->banner ) ); - g_admin_bans[ bnum - 1 ]->bannerlevel = G_admin_level( ent ); - } - - if( g_admin.string[ 0 ] ) - admin_writeconfig(); - return qtrue; -} - - -qboolean G_admin_subnetban( gentity_t *ent, int skiparg ) -{ - int bnum; - int mask; - unsigned int IPRlow = 0, IPRhigh = 0; - char cIPRlow[ 32 ], cIPRhigh[ 32 ]; - char bs[ 5 ]; - char strmask[ 5 ]; - char exl[2]; - int k, IP[5]; - - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!subnetban: ^7usage: !subnetban [ban#] [mask]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); - bnum = atoi( bs ); - if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] ) - { - ADMP( "^3!subnetban: ^7invalid ban#\n" ); - return qfalse; - } - if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) - { - ADMP( "^3!subnetban: ^7you cannot subnetban Bans on bans made by admins higher than you\n" ); - return qfalse; - } - - G_SayArgv( 2 + skiparg, strmask, sizeof( strmask ) ); - mask = atoi( strmask ); - - if( mask >= 0 && mask <= 32) - { - G_SayArgv( 3 + skiparg, exl, sizeof( exl ) ); - if( mask >= 0 && mask < 16 ) - { - if( ent ) - { - ADMP( "^3!subnetban: ^7Only console may ban such a large network. Regular admins may only ban >=16.\n" ); - return qfalse; - } - if( strcmp(exl, "!") ) - { - ADMP( "^3!subnetban: ^1WARNING:^7 you are about to ban a large network, use !subnetban [ban] [mask] ! to force^7\n" ); - return qfalse; - } - } - - memset( IP, 0, sizeof( IP ) ); - sscanf(g_admin_bans[ bnum - 1 ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]); - for(k = 4; k >= 1; k--) - { - if( IP[k] ) - IPRlow |= IP[k] << 8*(k-1); - } - IPRhigh = IPRlow; - if( mask == 32 ) - { - Q_strncpyz( - g_admin_bans[ bnum - 1 ]->ip, - va("%i.%i.%i.%i", IP[4], IP[3], IP[2], IP[1]), - sizeof( g_admin_bans[ bnum - 1 ]->ip ) - ); - } - else - { - Q_strncpyz( - g_admin_bans[ bnum - 1 ]->ip, - va("%i.%i.%i.%i/%i", IP[4], IP[3], IP[2], IP[1], mask ), - sizeof( g_admin_bans[ bnum - 1 ]->ip ) - ); - IPRlow &= ~((1 << (32-mask)) - 1); - IPRhigh |= ((1 << (32-mask)) - 1); - } - } - else - { - ADMP( "^3!subnetban: ^7mask is out of range, please use 0-32 inclusive\n" ); - return qfalse; - } - if( mask > 0 ) - { - Q_strncpyz( - cIPRlow, - va("%u.%u.%u.%u", (IPRlow & (255 << 24)) >> 24, (IPRlow & (255 << 16)) >> 16, (IPRlow & (255 << 8)) >> 8, IPRlow & 255), - sizeof( cIPRlow ) - ); - Q_strncpyz( - cIPRhigh, - va("%u.%u.%u.%u", (IPRhigh & (255 << 24)) >> 24, (IPRhigh & (255 << 16)) >> 16, (IPRhigh & (255 << 8)) >> 8, IPRhigh & 255), - sizeof( cIPRhigh ) - ); - } - else - { - Q_strncpyz( cIPRlow, "0.0.0.0", sizeof( cIPRlow ) ); - Q_strncpyz( cIPRhigh, "255.255.255.255", sizeof( cIPRhigh ) ); - - } - - AP( va( "print \"^3!subnetban: ^7ban #%d for %s^7 has been updated by %s^7 " - "%s (%s - %s)\n\"", - bnum, - g_admin_bans[ bnum - 1 ]->name, - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - g_admin_bans[ bnum - 1 ]->ip, - cIPRlow, - cIPRhigh) ); - if( ent ) - Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, G_admin_get_adminname( ent ), - sizeof( g_admin_bans[ bnum - 1 ]->banner ) ); - if( g_admin.string[ 0 ] ) - admin_writeconfig(); - return qtrue; -} - -qboolean G_admin_suspendban( gentity_t *ent, int skiparg ) -{ - int bnum; - int length; - int timenow = 0; - int expires = 0; - char *arg; - char bs[ 5 ]; - char duration[ 32 ]; - qtime_t qt; - - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!suspendban: ^7usage: !suspendban [ban #] [length]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); - bnum = atoi( bs ); - if( bnum < 1 || !g_admin_bans[ bnum - 1] ) - { - ADMP( "^3!suspendban: ^7invalid ban #\n" ); - return qfalse; - } - if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) - { - ADMP( "^3!suspendban: ^7you cannot suspend Bans made by admins higher than you\n" ); - return qfalse; - } - - arg = G_SayConcatArgs( 2 + skiparg ); - timenow = trap_RealTime( &qt ); - length = G_admin_parse_time( arg ); - - if( length < 0 ) - { - ADMP( "^3!suspendban: ^7invalid length\n" ); - return qfalse; - } - if( length > MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60 ) - { - length = MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60; - ADMP( va( "^3!suspendban: ^7maximum ban suspension is %d days\n", - MAX_ADMIN_BANSUSPEND_DAYS ) ); - } else if( g_admin_bans[ bnum - 1 ]->expires > 0 && length + timenow > g_admin_bans[ bnum - 1 ]->expires ) { - length = g_admin_bans[ bnum - 1 ]->expires - timenow; - G_admin_duration( length , duration, sizeof( duration ) ); - ADMP( va( "^3!suspendban: ^7Suspension Duration trimmed to Ban duration: %s\n", - duration ) ); - } - - if ( length > 0 ) - { - expires = timenow + length; - } - if( g_admin_bans[ bnum - 1 ]->suspend == expires ) - { - ADMP( "^3!suspendban: ^7no change\n" ); - return qfalse; - } - - g_admin_bans[ bnum - 1 ]->suspend = expires; - if ( length > 0 ) - { - G_admin_duration( length , duration, sizeof( duration ) ); - AP( va( "print \"^3!suspendban: ^7ban #%d suspended for %s\n\"", - bnum, duration ) ); - } - else - { - AP( va( "print \"^3!suspendban: ^7ban #%d suspension removed\n\"", - bnum ) ); - } - - if( !g_admin.string[ 0 ] ) - ADMP( "^3!adjustban: ^7WARNING g_admin not set, not saving ban to a file\n" ); - else - admin_writeconfig(); - return qtrue; -} - -qboolean G_admin_unban( gentity_t *ent, int skiparg ) -{ - int bnum; - char bs[ 5 ]; - int t; - - t = trap_RealTime( NULL ); - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( "^3!unban: ^7usage: !unban [ban#]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); - bnum = atoi( bs ); - if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1 ] ) - { - ADMP( "^3!unban: ^7invalid ban#\n" ); - return qfalse; - } - if( g_admin_bans[ bnum - 1 ]->expires == 0 && - !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) - { - ADMP( "^3!unban: ^7you cannot remove permanent bans\n" ); - return qfalse; - } - if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) - { - ADMP( "^3!unban: ^7you cannot Remove Bans made by admins higher than you\n" ); - return qfalse; - } - if( g_adminMaxBan.integer && - !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && - g_admin_bans[ bnum - 1 ]->expires - t > G_admin_parse_time( g_adminMaxBan.string ) ) - { - ADMP( va( "^3!unban: ^7your admin level cannot remove bans longer than %s\n", - g_adminMaxBan.string ) ); - return qfalse; - } - g_admin_bans[ bnum -1 ]->expires = t; - AP( va( "print \"^3!unban: ^7ban #%d for %s^7 has been removed by %s\n\"", - bnum, - g_admin_bans[ bnum - 1 ]->name, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - if( g_admin.string[ 0 ] ) - admin_writeconfig(); - return qtrue; -} - -qboolean G_admin_putteam( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ]; - gentity_t *vic; - pTeam_t teamnum = PTE_NONE; - char teamdesc[ 32 ] = {"spectators"}; - char secs[ 7 ]; - int seconds = 0; - qboolean useDuration = qfalse; - - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - G_SayArgv( 2 + skiparg, team, sizeof( team ) ); - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s] (duration)\n" ); - return qfalse; - } - - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!putteam: ^7%s\n", err ) ); - return qfalse; - } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher " - " admin level than you\n" ); - return qfalse; - } - vic = &g_entities[ pids[ 0 ] ]; - - if ( vic->client->sess.invisible == qtrue ) - { - ADMP( "^3!putteam: ^7invisible players cannot join a team\n" ); - return qfalse; - } - - switch( team[ 0 ] ) - { - case 'a': - teamnum = PTE_ALIENS; - Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) ); - break; - case 'h': - teamnum = PTE_HUMANS; - Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) ); - break; - case 's': - teamnum = PTE_NONE; - break; - default: - ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) ); - return qfalse; - } - //duration code - if( G_SayArgc() > 3 + skiparg ) { - //can only lock players in spectator - if ( teamnum != PTE_NONE ) - { - ADMP( "^3!putteam: ^7You can only lock a player into the spectators team\n" ); - return qfalse; - } - G_SayArgv( 3 + skiparg, secs, sizeof( secs ) ); - seconds = G_admin_parse_time( secs ); - useDuration = qtrue; - } - - if( vic->client->pers.teamSelection == teamnum && teamnum != PTE_NONE ) - { - ADMP( va( "^3!putteam: ^7%s ^7is already on the %s team\n", vic->client->pers.netname, teamdesc ) ); - return qfalse; - } - - if( useDuration == qtrue && seconds > 0 ) { - vic->client->pers.specExpires = level.time + ( seconds * 1000 ); - } - G_ChangeTeam( vic, teamnum ); - - AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team%s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - vic->client->pers.netname, teamdesc, - ( seconds ) ? va( " for %i seconds", seconds ) : "" ) ); - return qtrue; -} - -qboolean G_admin_seen(gentity_t *ent, int skiparg ) -{ - char name[ MAX_NAME_LENGTH ]; - char search[ MAX_NAME_LENGTH ]; - char sduration[ 32 ]; - qboolean numeric = qtrue; - int i, j; - int id = -1; - int count = 0; - int t; - qtime_t qt; - gentity_t *vic; - qboolean ison; - - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( "^3!seen: ^7usage: !seen [name|admin#]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - G_SanitiseString( name, search, sizeof( search ) ); - for( i = 0; i < sizeof( search ) && search[ i ] ; i++ ) - { - if( search[ i ] < '0' || search[ i ] > '9' ) - { - numeric = qfalse; - break; - } - } - - if( numeric ) - { - id = atoi( name ); - search[ 0 ] = '\0'; - } - - ADMBP_begin(); - t = trap_RealTime( &qt ); - - for( i = 0; i < level.maxclients && count < 10; i ++ ) - { - vic = &g_entities[ i ]; - - if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) - continue; - - G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) ); - - if( i == id || (search[ 0 ] && strstr( name, search ) ) ) - { - if ( vic->client->sess.invisible == qfalse ) - { - ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", i, vic->client->pers.netname ) ); - count++; - } - } - } - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && count < 10; i++ ) - { - G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) ); - if( i + MAX_CLIENTS == id || (search[ 0 ] && strstr( name, search ) ) ) - { - ison = qfalse; - for( j = 0; j < level.maxclients; j++ ) - { - vic = &g_entities[ j ]; - if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) - continue; - G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) ); - if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) - && strstr( name, search ) ) - { - if ( vic->client->sess.invisible == qfalse ) - { - ison = qtrue; - break; - } - } - } - - if( ison ) - { - if( id == -1 ) - continue; - ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", - i + MAX_CLIENTS, g_admin_admins[ i ]->name ) ); - } - else - { - G_admin_duration( t - g_admin_admins[ i ]->seen, - sduration, sizeof( sduration ) ); - ADMBP( va( "%4d %s^7 last seen %s%s\n", - i + MAX_CLIENTS, g_admin_admins[ i ]->name , - ( g_admin_admins[ i ]->seen ) ? sduration : "", - ( g_admin_admins[ i ]->seen ) ? " ago" : "time is unknown" ) ); - } - count++; - } - } - - if( search[ 0 ] ) - ADMBP( va( "^3!seen:^7 found %d player%s matching '%s'\n", - count, (count == 1) ? "" : "s", search ) ); - else if ( !count ) - ADMBP( "^3!seen:^7 no one connectd by that slot number\n" ); - - ADMBP_end(); - return qtrue; -} - -void G_admin_seen_update( char *guid ) -{ - int i; - - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) - { - if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) ) - { - qtime_t qt; - - g_admin_admins[ i ]->seen = trap_RealTime( &qt ); - return; - } - } -} - -void G_admin_adminlog_cleanup( void ) -{ - int i; - - for( i = 0; i < MAX_ADMIN_ADMINLOGS && g_admin_adminlog[ i ]; i++ ) - { - G_Free( g_admin_adminlog[ i ] ); - g_admin_adminlog[ i ] = NULL; - } - - admin_adminlog_index = 0; -} - -void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success ) -{ - g_admin_adminlog_t *adminlog; - int previous; - int count = 1; - int i; - - if( !command ) - return; - - if( !Q_stricmp( command, "adminlog" ) || - !Q_stricmp( command, "admintest" ) || - !Q_stricmp( command, "help" ) || - !Q_stricmp( command, "info" ) || - !Q_stricmp( command, "listadmins" ) || - !Q_stricmp( command, "listplayers" ) || - !Q_stricmp( command, "namelog" ) || - !Q_stricmp( command, "showbans" ) || - !Q_stricmp( command, "seen" ) || - !Q_stricmp( command, "time" ) ) - return; - - previous = admin_adminlog_index - 1; - if( previous < 0 ) - previous = MAX_ADMIN_ADMINLOGS - 1; - - if( g_admin_adminlog[ previous ] ) - count = g_admin_adminlog[ previous ]->id + 1; - - if( g_admin_adminlog[ admin_adminlog_index ] ) - adminlog = g_admin_adminlog[ admin_adminlog_index ]; - else - adminlog = G_Alloc( sizeof( g_admin_adminlog_t ) ); - - memset( adminlog, 0, sizeof( *adminlog ) ); - adminlog->id = count; - adminlog->time = level.time - level.startTime; - adminlog->success = success; - Q_strncpyz( adminlog->command, command, sizeof( adminlog->command ) ); - - if ( args ) - Q_strncpyz( adminlog->args, args, sizeof( adminlog->args ) ); - else - Q_strncpyz( adminlog->args, G_SayConcatArgs( 1 + skiparg ), sizeof( adminlog->args ) ); - - if( ent ) - { - qboolean found = qfalse; - // real admin name - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) - { - Q_strncpyz( adminlog->name, g_admin_admins[ i ]->name, sizeof( adminlog->name ) ); - found = qtrue; - break; - } - } - if( !found ) - Q_strncpyz( adminlog->name, ent->client->pers.netname, sizeof( adminlog->name ) ); - - adminlog->level = ent->client->pers.adminLevel; - } - else - { - Q_strncpyz( adminlog->name, "console", sizeof( adminlog->name ) ); - adminlog->level = 10000; - } - - g_admin_adminlog[ admin_adminlog_index ] = adminlog; - admin_adminlog_index++; - if( admin_adminlog_index >= MAX_ADMIN_ADMINLOGS ) - admin_adminlog_index = 0; -} - -qboolean G_admin_adminlog( gentity_t *ent, int skiparg ) -{ - g_admin_adminlog_t *results[ 10 ]; - int result_index = 0; - char *search_cmd = NULL; - char *search_name = NULL; - int index; - int skip = 0; - int skipped = 0; - int checked = 0; - char n1[ MAX_NAME_LENGTH ]; - char fmt_name[ 16 ]; - char argbuf[ 32 ]; - int name_length = 12; - int max_id = 0; - int i; - qboolean match; - - memset( results, 0, sizeof( results ) ); - - index = admin_adminlog_index; - for( i = 0; i < 10; i++ ) - { - int prev; - - prev = index - 1; - if( prev < 0 ) - prev = MAX_ADMIN_ADMINLOGS - 1; - if( !g_admin_adminlog[ prev ] ) - break; - if( g_admin_adminlog[ prev ]->id > max_id ) - max_id = g_admin_adminlog[ prev ]->id; - index = prev; - } - - if( G_SayArgc() > 1 + skiparg ) - { - G_SayArgv( 1 + skiparg, argbuf, sizeof( argbuf ) ); - if( ( *argbuf >= '0' && *argbuf <= '9' ) || *argbuf == '-' ) - { - int id; - - id = atoi( argbuf ); - if( id < 0 ) - id += ( max_id - 9 ); - else if( id <= max_id - MAX_ADMIN_ADMINLOGS ) - id = max_id - MAX_ADMIN_ADMINLOGS + 1; - - if( id + 9 >= max_id ) - id = max_id - 9; - if( id < 1 ) - id = 1; - for( i = 0; i < MAX_ADMIN_ADMINLOGS; i++ ) - { - if( g_admin_adminlog[i] && g_admin_adminlog[ i ]->id == id ) - { - index = i; - break; - } - } - } - else if ( *argbuf == '!' ) - { - search_cmd = argbuf + 1; - } - else - { - search_name = argbuf; - } - - if( G_SayArgc() > 2 + skiparg && ( search_cmd || search_name ) ) - { - char skipbuf[ 4 ]; - G_SayArgv( 2 + skiparg, skipbuf, sizeof( skipbuf ) ); - skip = atoi( skipbuf ); - } - } - - if( search_cmd || search_name ) - { - g_admin_adminlog_t *result_swap[ 10 ]; - - memset( result_swap, 0, sizeof( result_swap ) ); - - index = admin_adminlog_index - 1; - if( index < 0 ) - index = MAX_ADMIN_ADMINLOGS - 1; - - while( g_admin_adminlog[ index ] && - checked < MAX_ADMIN_ADMINLOGS && - result_index < 10 ) - { - match = qfalse; - if( search_cmd ) - { - if( !Q_stricmp( search_cmd, g_admin_adminlog[ index ]->command ) ) - match = qtrue; - } - if( search_name ) - { - G_SanitiseString( g_admin_adminlog[ index ]->name, n1, sizeof( n1 ) ); - if( strstr( n1, search_name ) ) - match = qtrue; - } - - if( match && skip > 0 ) - { - match = qfalse; - skip--; - skipped++; - } - if( match ) - { - result_swap[ result_index ] = g_admin_adminlog[ index ]; - result_index++; - } - - checked++; - index--; - if( index < 0 ) - index = MAX_ADMIN_ADMINLOGS - 1; - } - // search runs backwards, turn it around - for( i = 0; i < result_index; i++ ) - results[ i ] = result_swap[ result_index - i - 1 ]; - } - else - { - while( g_admin_adminlog[ index ] && result_index < 10 ) - { - results[ result_index ] = g_admin_adminlog[ index ]; - result_index++; - index++; - if( index >= MAX_ADMIN_ADMINLOGS ) - index = 0; - } - } - - for( i = 0; results[ i ] && i < 10; i++ ) - { - int l; - - G_DecolorString( results[ i ]->name, n1 ); - l = strlen( n1 ); - if( l > name_length ) - name_length = l; - } - ADMBP_begin( ); - for( i = 0; results[ i ] && i < 10; i++ ) - { - char levelbuf[ 8 ]; - int t; - - t = results[ i ]->time / 1000; - G_DecolorString( results[ i ]->name, n1 ); - Com_sprintf( fmt_name, sizeof( fmt_name ), "%%%ds", - ( name_length + strlen( results[ i ]->name ) - strlen( n1 ) ) ); - Com_sprintf( n1, sizeof( n1 ), fmt_name, results[ i ]->name ); - Com_sprintf( levelbuf, sizeof( levelbuf ), "%2d", results[ i ]->level ); - ADMBP( va( "%s%3d %3d:%02d %2s ^7%s^7 %s!%s ^7%s^7\n", - ( results[ i ]->success ) ? "^7" : "^1", - results[ i ]->id, - t / 60, t % 60, - ( results[ i ]->level ) < 10000 ? levelbuf : " -", - n1, - ( results[ i ]->success ) ? "^3" : "^1", - results[ i ]->command, - results[ i ]->args ) ); - } - if( search_cmd || search_name ) - { - ADMBP( va( "^3!adminlog:^7 Showing %d matches for '%s^7'.", - result_index, - argbuf ) ); - if( checked < MAX_ADMIN_ADMINLOGS && g_admin_adminlog[ checked ] ) - ADMBP( va( " run '!adminlog %s^7 %d' to see more", - argbuf, - skipped + result_index ) ); - ADMBP( "\n" ); - } - else if ( results[ 0 ] ) - { - ADMBP( va( "^3!adminlog:^7 Showing %d - %d of %d.\n", - results[ 0 ]->id, - results[ result_index - 1 ]->id, - max_id ) ); - } - else - { - ADMBP( "^3!adminlog:^7 log is empty.\n" ); - } - ADMBP_end( ); - - return qtrue; -} - -qboolean G_admin_map( gentity_t *ent, int skiparg ) -{ - char map[ MAX_QPATH ]; - char layout[ MAX_QPATH ] = { "" }; - - if( G_SayArgc( ) < 2 + skiparg ) - { - ADMP( "^3!map: ^7usage: !map [map] (layout)\n" ); - return qfalse; - } - - G_SayArgv( skiparg + 1, map, sizeof( map ) ); - - if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) ) - { - ADMP( va( "^3!map: ^7invalid map name '%s'\n", map ) ); - return qfalse; - } - - if( G_SayArgc( ) > 2 + skiparg ) - { - G_SayArgv( skiparg + 2, layout, sizeof( layout ) ); - if( !Q_stricmp( layout, "*BUILTIN*" ) || - trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), - NULL, FS_READ ) > 0 ) - { - trap_Cvar_Set( "g_layouts", layout ); - } - else - { - ADMP( va( "^3!map: ^7invalid layout name '%s'\n", layout ) ); - return qfalse; - } - } - - trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) ); - level.restarted = qtrue; - AP( va( "print \"^3!map: ^7map '%s' started by %s^7 %s\n\"", map, - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); - G_admin_maplog_result( "M" ); - return qtrue; -} - -qboolean G_admin_devmap( gentity_t *ent, int skiparg ) -{ - char map[ MAX_QPATH ]; - char layout[ MAX_QPATH ] = { "" }; - - if( G_SayArgc( ) < 2 + skiparg ) - { - ADMP( "^3!devmap: ^7usage: !devmap [map] (layout)\n" ); - return qfalse; - } - - G_SayArgv( skiparg + 1, map, sizeof( map ) ); - - if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) ) - { - ADMP( va( "^3!devmap: ^7invalid map name '%s'\n", map ) ); - return qfalse; - } - - if( G_SayArgc( ) > 2 + skiparg ) - { - G_SayArgv( skiparg + 2, layout, sizeof( layout ) ); - if( !Q_stricmp( layout, "*BUILTIN*" ) || - trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), - NULL, FS_READ ) > 0 ) - { - trap_Cvar_Set( "g_layouts", layout ); - } - else - { - ADMP( va( "^3!devmap: ^7invalid layout name '%s'\n", layout ) ); - return qfalse; - } - } - - trap_SendConsoleCommand( EXEC_APPEND, va( "devmap %s", map ) ); - level.restarted = qtrue; - AP( va( "print \"^3!devmap: ^7map '%s' started by %s^7 with cheats %s\n\"", map, - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); - G_admin_maplog_result( "D" ); - return qtrue; -} - -void G_admin_maplog_update( void ) -{ - char map[ 64 ]; - char maplog[ MAX_CVAR_VALUE_STRING ]; - char *ptr; - int count = 0; - - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - - Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) ); - ptr = maplog; - while( *ptr && count < MAX_ADMIN_MAPLOG_LENGTH ) - { - while( *ptr != ' ' && *ptr != '\0' ) ptr++; - - count++; - if( count >= MAX_ADMIN_MAPLOG_LENGTH ) - { - *ptr = '\0'; - } - - if( *ptr == ' ' ) ptr++; - } - - trap_Cvar_Set( "g_adminMapLog", va( "%s%s%s", - map, - ( maplog[0] != '\0' ) ? " " : "", - maplog ) ); -} - -void G_admin_maplog_result( char *flag ) -{ - static int lastTime = 0; - char maplog[ MAX_CVAR_VALUE_STRING ]; - int t; - - if( !flag ) - return; - - // avoid race when called in same frame - if( level.time == lastTime ) - return; - - lastTime = level.time; - - if( g_adminMapLog.string[ 0 ] && - g_adminMapLog.string[ 1 ] == ';' ) - { - // only one result allowed - return; - } - - if ( level.surrenderTeam != PTE_NONE ) - { - if( flag[ 0 ] == 'a' ) - { - if( level.surrenderTeam == PTE_HUMANS ) - flag = "A"; - } - else if( flag[ 0 ] == 'h' ) - { - if( level.surrenderTeam == PTE_ALIENS ) - flag = "H"; - } - } - - t = ( level.time - level.startTime ) / 1000; - Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) ); - trap_Cvar_Set( "g_adminMapLog", va( "%1s;%03d:%02d;%s", - flag, - t / 60, t % 60, - maplog ) ); -} - - -qboolean G_admin_maplog( gentity_t *ent, int skiparg ) -{ - char maplog[ MAX_CVAR_VALUE_STRING ]; - char *ptr; - int count = 0; - - Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) ); - - ADMBP_begin( ); - ptr = maplog; - while( *ptr != '\0' && count < MAX_ADMIN_MAPLOG_LENGTH + 1 ) - { - char *end; - const char *result = NULL; - char *clock = NULL; - char *colon; - - end = ptr; - while( *end != ' ' && *end != '\0' ) end++; - if( *end == ' ' ) - { - *end = '\0'; - end++; - } - - if( ptr[ 0 ] && ptr[ 1 ] == ';' ) - { - switch( ptr[ 0 ] ) - { - case 't': - result = "^7tie"; - break; - case 'a': - result = "^1Alien win"; - break; - case 'A': - result = "^1Alien win ^7/ Humans admitted defeat"; - break; - case 'h': - result = "^4Human win"; - break; - case 'H': - result = "^4Human win ^7/ Aliens admitted defeat"; - break; - case 'd': - result = "^5draw vote"; - break; - case 'N': - result = "^6admin loaded next map"; - break; - case 'r': - result = "^2restart vote"; - break; - case 'R': - result = "^6admin restarted map"; - break; - case 'm': - result = "^2map vote"; - break; - case 'M': - result = "^6admin changed map"; - break; - case 'D': - result = "^6admin loaded devmap"; - break; - default: - result = ""; - break; - } - ptr += 2; - colon = strchr( ptr, ';' ); - if ( colon ) - { - clock = ptr; - ptr = colon + 1; - *colon = '\0'; - - // right justification with -6%s doesnt work.. - if( clock[ 0 ] == '0' && clock[ 1 ] != ':' ) - { - if( clock[ 1 ] == '0' && clock[ 2 ] != ':' ) - clock[ 1 ] = ' '; - clock[ 0 ] = ' '; - } - } - } - else if( count == 0 ) - { - result = "^3current map"; - clock = " -:--"; - } - - ADMBP( va( "%s%20s %-6s %s^7\n", - ( count == 0 ) ? "^3" : "^7", - ptr, - ( clock ) ? clock : "", - ( result ) ? result : "" ) ); - - ptr = end; - count++; - } - ADMBP_end( ); - - return qtrue; -} - -qboolean G_admin_layoutsave( gentity_t *ent, int skiparg ) -{ - char layout[ MAX_QPATH ]; - - if( G_SayArgc( ) < 2 + skiparg ) - { - ADMP( "^3!layoutsave: ^7usage: !layoutsave [layout]\n" ); - return qfalse; - } - - G_SayArgv( skiparg + 1, layout, sizeof( layout ) ); - - trap_SendConsoleCommand( EXEC_APPEND, va( "layoutsave %s", layout ) ); - AP( va( "print \"^3!layoutsave: ^7layout saved as '%s' by %s\n\"", layout, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - return qtrue; -} - -qboolean G_admin_demo( gentity_t *ent, int skiparg ) -{ - if( !ent ) - { - ADMP( "!demo: console can not use demo.\n" ); - return qfalse; - } - - ent->client->pers.ignoreAdminWarnings = !( ent->client->pers.ignoreAdminWarnings ); - - ADMP( va( "^3!demo: ^7your visibility of admin chat is now %s\n", - ( ent->client->pers.ignoreAdminWarnings ) ? "^1disabled" : "^2enabled" ) ); - - return qtrue; -} - -qboolean G_admin_mute( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - gentity_t *vic; - char secs[ 7 ]; - int seconds = 0; - - G_SayArgv( skiparg, command, sizeof( command ) ); - cmd = command; - - if( cmd && *cmd == '!' ) - cmd++; - - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( va( "^3!%s: ^7usage: !%s [name|slot#] (duration)\n", cmd, cmd ) ); - return qfalse; - } - - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); - return qfalse; - } - - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" - " level than you\n", cmd ) ); - return qfalse; - } - - vic = &g_entities[ pids[ 0 ] ]; - if( !Q_stricmp( cmd, "unmute" ) ) - { - if( vic->client->pers.muted == qfalse ) - { - ADMP( "^3!unmute: ^7player is not currently muted\n" ); - return qtrue; - } - - vic->client->pers.muteExpires = 0; - vic->client->pers.muted = qfalse; - - CPx( pids[ 0 ], "cp \"^1You have been unmuted\"" ); - AP( va( "print \"^3!unmute: ^7%s^7 has been unmuted by %s\n\"", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - } else { - // Duration - if( G_SayArgc() > 2 + skiparg ) - { - G_SayArgv( 2 + skiparg, secs, sizeof( secs ) ); - seconds = G_admin_parse_time( secs ); - vic->client->pers.muteExpires = level.time + ( seconds * 1000 ); - } - - vic->client->pers.muted = qtrue; - - CPx( pids[ 0 ], "cp \"^1You've been muted\"" ); - AP( va( "print \"^3!mute: ^7%s^7 has been muted by ^7%s%s\n\"", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - ( seconds ) ? va( " ^7for %i seconds", seconds ) : "" ) ); - } - - return qtrue; -} - -qboolean G_admin_cp( gentity_t *ent, int skiparg ) -{ - int minargc; - char *s; - - minargc = 2 + skiparg; - - if( G_SayArgc() < minargc ) - { - ADMP( "^3!cp: ^7usage: !cp [message]\n" ); - return qfalse; - } - - s = G_SayConcatArgs( 1 + skiparg ); - G_CP(ent); - return qtrue; -} - -qboolean G_admin_denybuild( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - gentity_t *vic; - - G_SayArgv( skiparg, command, sizeof( command ) ); - cmd = command; - if( cmd && *cmd == '!' ) - cmd++; - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( va( "^3!%s: ^7usage: !%s [name|slot#]\n", cmd, cmd ) ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); - return qfalse; - } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" - " level than you\n", cmd ) ); - return qfalse; - } - vic = &g_entities[ pids[ 0 ] ]; - if( vic->client->pers.denyBuild ) - { - if( !Q_stricmp( cmd, "denybuild" ) ) - { - ADMP( "^3!denybuild: ^7player already has no building rights\n" ); - return qtrue; - } - vic->client->pers.denyBuild = qfalse; - CPx( pids[ 0 ], "cp \"^1You've regained your building rights\"" ); - AP( va( - "print \"^3!allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - } - else - { - if( !Q_stricmp( cmd, "allowbuild" ) ) - { - ADMP( "^3!allowbuild: ^7player already has building rights\n" ); - return qtrue; - } - vic->client->pers.denyBuild = qtrue; - vic->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - if( vic->client->ps.stats[ STAT_PCLASS ]== PCL_ALIEN_BUILDER0 || vic->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) - { - vic->suicideTime = level.time + 1000; - } - CPx( pids[ 0 ], "cp \"^1You've lost your building rights\"" ); - AP( va( - "print \"^3!denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - } - return qtrue; -} - -qboolean G_admin_denyweapon( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - char buffer[ 32 ]; - int weapon = WP_NONE; - int class = PCL_NONE; - char *realname; - gentity_t *vic; - int flag; - qboolean all = qfalse; - - G_SayArgv( skiparg, command, sizeof( command ) ); - cmd = command; - if( cmd && *cmd == '!' ) - cmd++; - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( va( "^3!%s: ^7usage: !%s [name|slot#] [class|weapon]\n", cmd, cmd ) ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); - return qfalse; - } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" - " level than you\n", cmd ) ); - return qfalse; - } - vic = &g_entities[ pids[ 0 ] ]; - - if( vic == ent && - !Q_stricmp( cmd, "denyweapon" ) ) - { - ADMP( va( "^3!%s: ^7sorry, you cannot %s yourself\n", cmd, cmd ) ); - return qfalse; - } - - G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); - - if( !Q_stricmp( buffer, "all" ) && - !Q_stricmp( cmd, "allowweapon" ) ) - { - if( vic->client->pers.denyHumanWeapons || - vic->client->pers.denyAlienClasses ) - { - vic->client->pers.denyHumanWeapons = 0; - vic->client->pers.denyAlienClasses = 0; - - CPx( pids[ 0 ], "cp \"^1You've regained all weapon and class rights\"" ); - AP( va( "print \"^3!%s: ^7all weapon and class rights for ^7%s^7 restored by %s\n\"", - cmd, - vic->client->pers.netname, - ( ent ) ? ent->client->pers.netname : "console" ) ); - } - else - { - ADMP( va( "^3!%s: ^7player already has all rights\n", cmd ) ); - } - return qtrue; - } - - if( !Q_stricmp( buffer, "all" ) && - !Q_stricmp( cmd, "denyweapon" ) ) - { - all = qtrue; - weapon = WP_NONE; - class = PCL_NONE; - - if( vic->client->pers.denyHumanWeapons == 0xFFFFFF && - vic->client->pers.denyAlienClasses == 0xFFFFFF ) - { - ADMP( va( "^3!%s: ^7player already has no weapon or class rights\n", cmd ) ); - return qtrue; - } - - if( vic->client->pers.teamSelection == PTE_HUMANS ) - { - weapon = vic->client->ps.weapon; - if( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) - weapon = WP_NONE; - } - if( vic->client->pers.teamSelection == PTE_ALIENS ) - { - class = vic->client->pers.classSelection; - if( class < PCL_ALIEN_LEVEL1 || class > PCL_ALIEN_LEVEL4 ) - class = PCL_NONE; - } - - vic->client->pers.denyHumanWeapons = 0xFFFFFF; - vic->client->pers.denyAlienClasses = 0xFFFFFF; - } - else - { - weapon = BG_FindWeaponNumForName( buffer ); - if( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) - class = BG_FindClassNumForName( buffer ); - if( ( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) && - ( class < PCL_ALIEN_LEVEL1 || class > PCL_ALIEN_LEVEL4 ) ) - { - { - ADMP( va( "^3!%s: ^7unknown weapon or class\n", cmd ) ); - return qfalse; - } - } - } - - if( class == PCL_NONE ) - { - realname = BG_FindHumanNameForWeapon( weapon ); - flag = 1 << (weapon - WP_BLASTER); - if( !Q_stricmp( cmd, "denyweapon" ) ) - { - if( ( vic->client->pers.denyHumanWeapons & flag ) && !all ) - { - ADMP( va( "^3!%s: ^7player already has no %s rights\n", cmd, realname ) ); - return qtrue; - } - vic->client->pers.denyHumanWeapons |= flag; - if( vic->client->pers.teamSelection == PTE_HUMANS ) - { - if( ( weapon == WP_GRENADE || all ) && - BG_InventoryContainsUpgrade( UP_GRENADE, vic->client->ps.stats ) ) - { - BG_RemoveUpgradeFromInventory( UP_GRENADE, vic->client->ps.stats ); - G_AddCreditToClient( vic->client, (short)BG_FindPriceForUpgrade( UP_GRENADE ), qfalse ); - } - if( BG_InventoryContainsWeapon( weapon, vic->client->ps.stats ) ) - { - int maxAmmo, maxClips; - - BG_RemoveWeaponFromInventory( weapon, vic->client->ps.stats ); - G_AddCreditToClient( vic->client, (short)BG_FindPriceForWeapon( weapon ), qfalse ); - - BG_AddWeaponToInventory( WP_MACHINEGUN, vic->client->ps.stats ); - BG_FindAmmoForWeapon( WP_MACHINEGUN, &maxAmmo, &maxClips ); - vic->client->ps.ammo = maxAmmo; - vic->client->ps.clips = maxClips; - G_ForceWeaponChange( vic, WP_MACHINEGUN ); - vic->client->ps.stats[ STAT_MISC ] = 0; - ClientUserinfoChanged( pids[ 0 ], qfalse ); - } - } - } - else - { - if( !( vic->client->pers.denyHumanWeapons & flag ) ) - { - ADMP( va( "^3!%s: ^7player already has %s rights\n", cmd, realname ) ); - return qtrue; - } - vic->client->pers.denyHumanWeapons &= ~flag; - } - } - else - { - realname = BG_FindHumanNameForClassNum( class ); - flag = 1 << class; - if( !Q_stricmp( cmd, "denyweapon" ) ) - { - if( ( vic->client->pers.denyAlienClasses & flag ) && !all ) - { - ADMP( va( "^3!%s: ^7player already has no %s rights\n", cmd, realname ) ); - return qtrue; - } - vic->client->pers.denyAlienClasses |= flag; - if( vic->client->pers.teamSelection == PTE_ALIENS && - vic->client->pers.classSelection == class ) - { - vec3_t infestOrigin; - short cost; - - G_RoomForClassChange( vic, PCL_ALIEN_LEVEL0, infestOrigin ); - - vic->client->pers.evolveHealthFraction = (float)vic->client->ps.stats[ STAT_HEALTH ] / - (float)BG_FindHealthForClass( class ); - if( vic->client->pers.evolveHealthFraction < 0.0f ) - vic->client->pers.evolveHealthFraction = 0.0f; - else if( vic->client->pers.evolveHealthFraction > 1.0f ) - vic->client->pers.evolveHealthFraction = 1.0f; - - vic->client->pers.classSelection = PCL_ALIEN_LEVEL0; - cost = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, class, 9, 0 ); - if( cost < 0 ) cost = 0; - G_AddCreditToClient( vic->client, cost, qfalse ); - ClientUserinfoChanged( pids[ 0 ], qfalse ); - VectorCopy( infestOrigin, vic->s.pos.trBase ); - ClientSpawn( vic, vic, vic->s.pos.trBase, vic->s.apos.trBase ); - } - } - else - { - if( !( vic->client->pers.denyAlienClasses & flag ) ) - { - ADMP( va( "^3!%s: ^7player already has %s rights\n", cmd, realname ) ); - return qtrue; - } - vic->client->pers.denyAlienClasses &= ~flag; - } - } - - if( all ) - realname = "weapon and class"; - - CPx( pids[ 0 ], va( "cp \"^1You've %s your %s rights\"", - ( !Q_stricmp( cmd, "denyweapon" ) ) ? "lost" : "regained", - realname ) ); - AP( va( - "print \"^3!%s: ^7%s rights for ^7%s^7 %s by %s\n\"", - cmd, realname, - vic->client->pers.netname, - ( !Q_stricmp( cmd, "denyweapon" ) ) ? "revoked" : "restored", - ( ent ) ? ent->client->pers.netname : "console" ) ); - - return qtrue; -} - -qboolean G_admin_listadmins( gentity_t *ent, int skiparg ) -{ - int i, found = 0; - char search[ MAX_NAME_LENGTH ] = {""}; - char s[ MAX_NAME_LENGTH ] = {""}; - int start = 0; - qboolean numeric = qtrue; - int drawn = 0; - int minlevel = 1; - - if( G_SayArgc() == 3 + skiparg ) - { - G_SayArgv( 2 + skiparg, s, sizeof( s ) ); - if( s[ 0 ] >= '0' && s[ 0] <= '9' ) - { - minlevel = atoi( s ); - if( minlevel < 1 ) - minlevel = 1; - } - } - - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( g_admin_admins[ i ]->level >= minlevel ) - found++; - } - if( !found ) - { - if( minlevel > 1 ) - { - ADMP( va( "^3!listadmins: ^7no admins level %i or greater found\n", minlevel ) ); - } - else - { - ADMP( "^3!listadmins: ^7no admins defined\n" ); - } - return qfalse; - } - - if( G_SayArgc() >= 2 + skiparg ) - { - G_SayArgv( 1 + skiparg, s, sizeof( s ) ); - for( i = 0; i < sizeof( s ) && s[ i ]; i++ ) - { - if( s[ i ] >= '0' && s[ i ] <= '9' ) - continue; - numeric = qfalse; - } - if( numeric ) - { - start = atoi( s ); - if( start > 0 ) - start -= 1; - else if( start < 0 ) - start = found + start; - } - else - G_SanitiseString( s, search, sizeof( search ) ); - } - - if( start >= found || start < 0 ) - start = 0; - - drawn = admin_listadmins( ent, start, search, minlevel ); - - if( search[ 0 ] ) - { - if( drawn <= 20 ) - { - ADMP( va( "^3!listadmins:^7 found %d admins level %i or greater matching '%s^7'\n", - drawn, minlevel, search ) ); - } - else - { - ADMP( va( "^3!listadmins:^7 found >20 admins level %i or greater matching '%s^7. Try a more narrow search.'\n", - minlevel, search ) ); - } - } - else - { - ADMBP_begin(); - ADMBP( va( "^3!listadmins:^7 showing admins level %i or greater %d - %d of %d. ", - minlevel, - ( found ) ? ( start + 1 ) : 0, - ( ( start + MAX_ADMIN_LISTITEMS ) > found ) ? - found : ( start + MAX_ADMIN_LISTITEMS ), - found ) ); - if( ( start + MAX_ADMIN_LISTITEMS ) < found ) - { - if( minlevel > 1) - { - ADMBP( va( "run '!listadmins %d %d' to see more", - ( start + MAX_ADMIN_LISTITEMS + 1 ), minlevel ) ); - } - else - { - ADMBP( va( "run '!listadmins %d' to see more", - ( start + MAX_ADMIN_LISTITEMS + 1 ) ) ); - } - } - ADMBP( "\n" ); - ADMBP_end(); - } - return qtrue; -} - -qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ) -{ - char list[ MAX_CVAR_VALUE_STRING ]; - char map[ MAX_QPATH ]; - int count = 0; - char *s; - char layout[ MAX_QPATH ] = { "" }; - int i = 0; - - if( G_SayArgc( ) == 2 + skiparg ) - G_SayArgv( 1 +skiparg, map, sizeof( map ) ); - else - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - - count = G_LayoutList( map, list, sizeof( list ) ); - ADMBP_begin( ); - ADMBP( va( "^3!listlayouts:^7 %d layouts found for '%s':\n", count, map ) ); - s = &list[ 0 ]; - while( *s ) - { - if( *s == ' ' ) - { - ADMBP( va ( " %s\n", layout ) ); - layout[ 0 ] = '\0'; - i = 0; - } - else if( i < sizeof( layout ) - 2 ) - { - layout[ i++ ] = *s; - layout[ i ] = '\0'; - } - s++; - } - if( layout[ 0 ] ) - ADMBP( va ( " %s\n", layout ) ); - ADMBP_end( ); - return qtrue; -} - -qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) -{ - int i, j; - int invisiblePlayers = 0; - gclient_t *p; - char c[ 3 ], t[ 2 ]; // color and team letter - char n[ MAX_NAME_LENGTH ] = {""}; - char n2[ MAX_NAME_LENGTH ] = {""}; - char n3[ MAX_NAME_LENGTH ] = {""}; - char lname[ MAX_NAME_LENGTH ]; - char lname2[ MAX_NAME_LENGTH ]; - char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ], immune[ 2 ], guidless[ 2 ]; - int l; - char lname_fmt[ 5 ]; - - //get amount of invisible players - for( i = 0; i < level.maxclients; i++ ) { - p = &level.clients[ i ]; - if ( p->sess.invisible == qtrue ) - invisiblePlayers++; - } - - ADMBP_begin(); - ADMBP( va( "^3!listplayers^7: %d players connected:\n", - level.numConnectedClients - invisiblePlayers ) ); - for( i = 0; i < level.maxclients; i++ ) - { - p = &level.clients[ i ]; - - // Ignore invisible players - if ( p->sess.invisible == qtrue ) - continue; - - Q_strncpyz( t, "S", sizeof( t ) ); - Q_strncpyz( c, S_COLOR_YELLOW, sizeof( c ) ); - if( p->pers.teamSelection == PTE_HUMANS ) - { - Q_strncpyz( t, "H", sizeof( t ) ); - Q_strncpyz( c, S_COLOR_BLUE, sizeof( c ) ); - } - else if( p->pers.teamSelection == PTE_ALIENS ) - { - Q_strncpyz( t, "A", sizeof( t ) ); - Q_strncpyz( c, S_COLOR_RED, sizeof( c ) ); - } - - if( p->pers.connected == CON_CONNECTING ) - { - Q_strncpyz( t, "C", sizeof( t ) ); - Q_strncpyz( c, S_COLOR_CYAN, sizeof( c ) ); - } - else if( p->pers.connected != CON_CONNECTED ) - { - continue; - } - - guidless[ 0 ] = '\0'; - if( !Q_stricmp( p->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) - { - Q_strncpyz( guidless, "G", sizeof( guidless ) ); - } - muted[ 0 ] = '\0'; - if( G_admin_permission( &g_entities[ i ], ADMF_NO_VOTE ) || G_admin_permission( &g_entities[ i ], ADMF_FAKE_NO_VOTE ) ) - { - Q_strncpyz( muted, "V", sizeof( muted ) ); - } - if( p->pers.muted ) - { - Q_strncpyz( muted, "M", sizeof( muted ) ); - } - denied[ 0 ] = '\0'; - if( p->pers.denyBuild ) - { - Q_strncpyz( denied, "B", sizeof( denied ) ); - } - if( p->pers.denyHumanWeapons || p->pers.denyAlienClasses ) - { - Q_strncpyz( denied, "W", sizeof( denied ) ); - } - - dbuilder[ 0 ] = '\0'; - if( p->pers.designatedBuilder ) - { - if( !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && - G_admin_permission( &g_entities[ i ], ADMF_DBUILDER ) && - G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) - { - Q_strncpyz( dbuilder, "P", sizeof( dbuilder ) ); - } - else - { - Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) ); - } - } - immune[ 0 ] = '\0'; - if( G_admin_permission( &g_entities[ i ], ADMF_BAN_IMMUNITY ) && - G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) - { - Q_strncpyz( immune, "I", sizeof( immune ) ); - } - - if( p->pers.paused ) - { - // use immunity slot for paused player status - Q_strncpyz( immune, "L", sizeof( immune ) ); - } - - l = 0; - G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); - n[ 0 ] = '\0'; - for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ ) - { - if( !Q_stricmp( g_admin_admins[ j ]->guid, p->pers.guid ) ) - { - - // don't gather aka or level info if the admin is incognito - if( ent && G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && !G_admin_permission(ent, ADMF_SEESINCOGNITO) ) - { - break; - } - l = g_admin_admins[ j ]->level; - G_SanitiseString( g_admin_admins[ j ]->name, n3, sizeof( n3 ) ); - if( Q_stricmp( n2, n3 ) ) - { - Q_strncpyz( n, g_admin_admins[ j ]->name, sizeof( n ) ); - } - break; - } - } - lname[ 0 ] = '\0'; - Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); - for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) - { - if( g_admin_levels[ j ]->level == l ) - { - Q_strncpyz( lname, g_admin_levels[ j ]->name, sizeof( lname ) ); - if( *lname ) - { - G_DecolorString( lname, lname2 ); - Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is", - ( admin_level_maxname + (int)( strlen( lname ) - strlen( lname2 ) ) ) ); - Com_sprintf( lname2, sizeof( lname2 ), lname_fmt, lname ); - } - break; - } - - } - - if ( G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) { - ADMBP( va( "%2i %s%s^7 %-2i %s^7 ^1%1s%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n", - i, - c, - t, - l, - ( *lname ) ? lname2 : "", - immune, - muted, - dbuilder, - denied, - guidless, - p->pers.netname, - ( *n ) ? "(a.k.a. " : "", - n, - ( *n ) ? ")" : "" - ) ); - } else { - ADMBP( va( "%2i %s%s^7 %-2i %s^7 ^1%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n", - i, - c, - t, - l, - ( *lname ) ? lname2 : "", - immune, - muted, - dbuilder, - denied, - p->pers.netname, - ( *n ) ? "(a.k.a. " : "", - n, - ( *n ) ? ")" : "" - ) ); - } - } - ADMBP_end(); - return qtrue; -} - -#define MAX_LISTMAPS_MAPS 256 - -static int SortMaps(const void *a, const void *b) -{ - return strcmp(*(char **)a, *(char **)b); -} - -qboolean G_admin_listmaps( gentity_t *ent, int skiparg ) -{ - char fileList[ 4096 ] = {""}; - char *fileSort[ MAX_LISTMAPS_MAPS ]; - char search[ 16 ] = {""}; - int numFiles; - int i; - int fileLen = 0; - int count = 0; - char *filePtr; - int rows; - - if( G_SayArgc( ) > 1 + skiparg ) - { - G_SayArgv( skiparg + 1, search, sizeof( search ) ); - } - - numFiles = trap_FS_GetFileList( "maps/", ".bsp", - fileList, sizeof( fileList ) ); - filePtr = fileList; - for( i = 0; i < numFiles && count < MAX_LISTMAPS_MAPS; i++, filePtr += fileLen + 1 ) - { - fileLen = strlen( filePtr ); - if (fileLen < 5) - continue; - - filePtr[ fileLen - 4 ] = '\0'; - - if( search[ 0 ] && !strstr( filePtr, search ) ) - continue; - - fileSort[ count ] = filePtr; - count++; - } - - qsort(fileSort, count, sizeof(fileSort[ 0 ]), SortMaps); - - rows = count / 3; - if ( rows * 3 < count ) rows++; - - ADMBP_begin(); - for( i = 0; i < rows; i++ ) - { - ADMBP( va( "^7%20s %20s %20s\n", - fileSort[ i ], - ( rows + i < count ) ? fileSort[ rows + i ] : "", - ( rows * 2 + i < count ) ? fileSort[ rows * 2 + i ] : "" ) ); - } - if ( search[ 0 ] ) - ADMBP( va( "^3!listmaps: ^7found %d maps matching '%s^7'.\n", count, search ) ); - else - ADMBP( va( "^3!listmaps: ^7listing %d maps.\n", count ) ); - - ADMBP_end(); - - return qtrue; -} - -qboolean G_admin_listrotation( gentity_t *ent, int skiparg ) -{ - int i, j, statusColor; - char mapnames[ MAX_STRING_CHARS ]; - char *status = NULL; - - extern mapRotations_t mapRotations; - - // Check for an active map rotation - if ( !G_MapRotationActive() || - g_currentMapRotation.integer == NOT_ROTATING ) - { - trap_SendServerCommand( ent-g_entities, "print \"^3!rotation: ^7There is no active map rotation on this server\n\"" ); - return qfalse; - } - - // Locate the active map rotation and output its contents - for( i = 0; i < mapRotations.numRotations; i++ ) - { - if ( i == g_currentMapRotation.integer ) - { - int currentMap = G_GetCurrentMap( i ); - - ADMBP_begin(); - ADMBP( va( "^3!rotation: ^7%s\n", mapRotations.rotations[ i ].name ) ); - - for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) - { - Q_strncpyz( mapnames, mapRotations.rotations[ i ].maps[ j ].name, sizeof( mapnames ) ); - - if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*" ) ) - { - char slotMap[ 64 ]; - int lineLen = 0; - int k; - - trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) ); - mapnames[ 0 ] = '\0'; - for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) - { - char *thisMap; - int mc = 7; - - if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_VOTE ) - continue; - - thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest; - lineLen += strlen( thisMap ) + 1; - - if( currentMap == j && !Q_stricmp( thisMap, slotMap ) ) - mc = 3; - Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s", - ( k ) ? ", " : "", - ( lineLen > 50 ) ? "\n " : "", - mc, thisMap ) ); - if( lineLen > 50 ) - lineLen = strlen( thisMap ) + 2; - else - lineLen++; - } - - if( currentMap == j ) - { - statusColor = 3; - status = "current slot"; - } - else if( !k ) - { - statusColor = 1; - status = "empty vote"; - } - else - { - statusColor = 7; - status = "vote"; - } - } else if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*" ) ) - { - char slotMap[ 64 ]; - int lineLen = 0; - int k; - - trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) ); - mapnames[ 0 ] = '\0'; - for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) - { - char *thisMap; - int mc = 7; - - if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_SELECTEDRANDOM ) - continue; - - thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest; - lineLen += strlen( thisMap ) + 1; - - if( currentMap == j && !Q_stricmp( thisMap, slotMap ) ) - mc = 3; - Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s", - ( k ) ? ", " : "", - ( lineLen > 50 ) ? "\n " : "", - mc, thisMap ) ); - if( lineLen > 50 ) - lineLen = strlen( thisMap ) + 2; - else - lineLen++; - } - - if( currentMap == j ) - { - statusColor = 3; - status = "current slot"; - } - else if( !k ) - { - statusColor = 1; - status = "empty Random"; - } - else - { - statusColor = 7; - status = "Random"; - } - } - else if ( currentMap == j ) - { - statusColor = 3; - status = "current slot"; - } - else if ( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) ) - { - statusColor = 1; - status = "missing"; - } - else - { - statusColor = 7; - status = ""; - } - ADMBP( va( " ^%i%-12s %3i %s\n", statusColor, status, j + 1, mapnames ) ); - } - - ADMBP_end(); - - // No maps were found in the active map rotation - if ( mapRotations.rotations[ i ].numMaps < 1 ) - { - trap_SendServerCommand( ent-g_entities, "print \" - ^7Empty!\n\"" ); - return qfalse; - } - } - } - - if( g_nextMap.string[0] ) - { - ADMP( va ("^5 Next map overriden by g_nextMap to: %s\n", g_nextMap.string ) ); - } - - return qtrue; -} - - -qboolean G_admin_showbans( gentity_t *ent, int skiparg ) -{ - int i, found = 0; - int t; - char duration[ 32 ]; - char sduration[ 32 ]; - char suspended[ 64 ] = { "" }; - char name_fmt[ 32 ] = { "%s" }; - char banner_fmt[ 32 ] = { "%s" }; - int max_name = 1, max_banner = 1; - int secs; - int start = 0; - char filter[ MAX_NAME_LENGTH ] = {""}; - char date[ 11 ]; - char *made; - int j; - char n1[ MAX_NAME_LENGTH * 2 ] = {""}; - char n2[ MAX_NAME_LENGTH * 2 ] = {""}; - int bannerslevel = 0; - qboolean numeric = qtrue; - char *ip_match = NULL; - int ip_match_len = 0; - char name_match[ MAX_NAME_LENGTH ] = {""}; - int show_count = 0; - qboolean subnetfilter = qfalse; - - t = trap_RealTime( NULL ); - - for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) - { - if( g_admin_bans[ i ]->expires != 0 - && ( g_admin_bans[ i ]->expires - t ) < 1 ) - { - continue; - } - found++; - } - - if( G_SayArgc() >= 2 + skiparg ) - { - G_SayArgv( 1 + skiparg, filter, sizeof( filter ) ); - if( G_SayArgc() >= 3 + skiparg ) - { - start = atoi( filter ); - G_SayArgv( 2 + skiparg, filter, sizeof( filter ) ); - } - for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ ) - { - if( ( filter[ i ] < '0' || filter[ i ] > '9' ) - && filter[ i ] != '.' && filter[ i ] != '-' ) - { - numeric = qfalse; - break; - } - } - - if (!numeric) - { - if( filter[ 0 ] != '-' ) - { - G_SanitiseString( filter, name_match, sizeof( name_match) ); - - } - else - { - if( !Q_strncmp( filter, "-sub", 4 ) ) - { - subnetfilter = qtrue; - } - else - { - ADMP( va( "^3!showbans: ^7invalid argument %s\n", filter ) ); - return qfalse; - } - } - } - else if( strchr( filter, '.' ) != NULL ) - { - ip_match = filter; - ip_match_len = strlen(ip_match); - } - else - { - start = atoi( filter ); - filter[0] = '\0'; - } - // showbans 1 means start with ban 0 - if( start > 0 ) - start -= 1; - else if( start < 0 ) - start = found + start; - } - - if( start >= MAX_ADMIN_BANS || start < 0 ) - start = 0; - - for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ] - && show_count < MAX_ADMIN_SHOWBANS; i++ ) - { - qboolean match = qfalse; - - if (!numeric) - { - if( !subnetfilter ) - { - G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) ); - if (strstr( n1, name_match) ) - match = qtrue; - } - else - { - int mask = -1; - int dummy; - int scanflen = 0; - scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); - if( scanflen == 5 && mask < 32 ) - { - match = qtrue; - } - } - } - - if ( ( match ) || !ip_match - || Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len) == 0 ) - { - G_DecolorString( g_admin_bans[ i ]->name, n1 ); - G_DecolorString( g_admin_bans[ i ]->banner, n2 ); - if( strlen( n1 ) > max_name ) - { - max_name = strlen( n1 ); - } - if( strlen( n2 ) > max_banner ) - max_banner = strlen( n2 ); - - show_count++; - } - } - - if( start >= found ) - { - ADMP( va( "^3!showbans: ^7there are %d active bans\n", found ) ); - return qfalse; - } - ADMBP_begin(); - show_count = 0; - for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ] - && show_count < MAX_ADMIN_SHOWBANS; i++ ) - { - if (!numeric) - { - if( !subnetfilter ) - { - G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) ); - if ( strstr ( n1, name_match ) == NULL ) - continue; - } - else - { - int mask = -1; - int dummy; - int scanflen = 0; - scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); - if( scanflen != 5 || mask >= 32 ) - { - continue; - } - } - } - else if( ip_match != NULL - && Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len ) != 0) - continue; - - // only print out the the date part of made - date[ 0 ] = '\0'; - made = g_admin_bans[ i ]->made; - for( j = 0; made && *made; j++ ) - { - if( ( j + 1 ) >= sizeof( date ) ) - break; - if( *made == ' ' ) - break; - date[ j ] = *made; - date[ j + 1 ] = '\0'; - made++; - } - - if( g_admin_bans[ i ]->expires != 0 - && ( g_admin_bans[ i ]->expires - t ) < 1 ) - { - Com_sprintf( duration, sizeof( duration ), "^1*EXPIRED*^7" ); - } else { - secs = ( g_admin_bans[ i ]->expires - t ); - G_admin_duration( secs, duration, sizeof( duration ) ); - } - - suspended[ 0 ] = '\0'; - if( g_admin_bans[ i ]->suspend > t ) - { - G_admin_duration( g_admin_bans[ i ]->suspend - t, sduration, sizeof( sduration ) ); - Com_sprintf( suspended, sizeof( suspended ), "^3*SUSPENDED*^7 for %s^7", - sduration ); - } - - G_DecolorString( g_admin_bans[ i ]->name, n1 ); - Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is", - ( max_name + strlen( g_admin_bans[ i ]->name ) - strlen( n1 ) ) ); - Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_bans[ i ]->name ); - - G_DecolorString( g_admin_bans[ i ]->banner, n2 ); - Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%%is", - ( max_banner + strlen( g_admin_bans[ i ]->banner ) - strlen( n2 ) ) ); - Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner ); - bannerslevel = g_admin_bans[ i ]->bannerlevel; - - ADMBP( va( "%4i %s^7 %-15s %-8s %-10s\n | %-15s^7 Level:%2i\n | %s\n \\__ %s\n", - ( i + 1 ), - n1, - g_admin_bans[ i ]->ip, - date, - duration, - n2, - bannerslevel, - suspended, - g_admin_bans[ i ]->reason ) ); - - show_count++; - } - - if (!numeric || ip_match) - { - char matchmethod[50]; - if( numeric ) - Com_sprintf( matchmethod, sizeof(matchmethod), "IP" ); - else if( !subnetfilter ) - Com_sprintf( matchmethod, sizeof(matchmethod), "name" ); - else - Com_sprintf( matchmethod, sizeof(matchmethod), "ip range size" ); - - - ADMBP( va( "^3!showbans:^7 found %d matching bans by %s. ", - show_count, - matchmethod ) ); - } - else - { - ADMBP( va( "^3!showbans:^7 showing bans %d - %d of %d. ", - ( found ) ? ( start + 1 ) : 0, - ( ( start + MAX_ADMIN_SHOWBANS ) > found ) ? - found : ( start + MAX_ADMIN_SHOWBANS ), - found ) ); - } - - if( ( start + MAX_ADMIN_SHOWBANS ) < found ) - { - ADMBP( va( "run !showbans %d %s to see more", - ( start + MAX_ADMIN_SHOWBANS + 1 ), - (filter[0]) ? filter : "" ) ); - } - ADMBP( "\n" ); - ADMBP_end(); - return qtrue; -} - -qboolean G_admin_help( gentity_t *ent, int skiparg ) -{ - int i; - int commandsPerLine = 6; - - if( G_SayArgc() < 2 + skiparg ) - { - int j = 0; - int count = 0; - - ADMBP_begin(); - for( i = 0; i < adminNumCmds; i++ ) - { - if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) - { - ADMBP( va( "^3!%-12s", g_admin_cmds[ i ].keyword ) ); - j++; - count++; - } - // show 6 commands per line - if( j == 6 ) - { - ADMBP( "\n" ); - j = 0; - } - } - for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) - { - if( !G_admin_permission( ent, g_admin_commands[ i ]->flag ) ) - continue; - ADMBP( va( "^3!%-12s", g_admin_commands[ i ]->command ) ); - j++; - count++; - // show 6 commands per line - if( j == 6 ) - { - ADMBP( "\n" ); - j = 0; - } - } - - if( count ) - ADMBP( "\n" ); - ADMBP( va( "^3!help: ^7%i available commands\n", count ) ); - ADMBP( "run !help [^3command^7] for help with a specific command.\n" ); - ADMBP( "The following non-standard /commands may also be available to you: \n" ); - count = 1; - - if( ent && g_AllStats.integer ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "allstats" ) ); - count++; - } - if ( ent ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "builder" ) ); - count++; - } - if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "callvote" ) ); - count++; - } - if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "callteamvote" ) ); - count++; - } - if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "donate" ) ); - count++; - } - if( g_privateMessages.integer ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "m" ) ); - count++; - } - if( ent && g_markDeconstruct.integer == 2 && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "mark" ) ); - count++; - } - if( ent && g_actionPrefix.string[0] ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "me" ) ); - count++; - } - if( ent && g_actionPrefix.string[0] ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "me_team" ) ); - count++; - } - if( ent && g_actionPrefix.string[0] && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "mt" ) ); - count++; - } - if( ent && g_myStats.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "mystats" ) ); - count++; - } - if( ent && ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "protect" ) ); - count++; - } - if( ent && ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "resign" ) ); - count++; - } - if( g_publicSayadmins.integer || G_admin_permission( ent, ADMF_ADMINCHAT ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "say_admins" ) ); - count++; - } - if( ent && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "say_area" ) ); - count++; - } - if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "share" ) ); - count++; - } - if( ent && g_teamStatus.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "teamstatus" ) ); - count++; - } - ADMBP( "\n" ); - ADMBP_end(); - - return qtrue; - } - else - { - //!help param - char param[ MAX_ADMIN_CMD_LEN ]; - char *cmd; - - G_SayArgv( 1 + skiparg, param, sizeof( param ) ); - cmd = ( param[0] == '!' ) ? ¶m[1] : ¶m[0]; - ADMBP_begin(); - for( i = 0; i < adminNumCmds; i++ ) - { - if( !Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) ) - { - if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) - { - ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n", - g_admin_cmds[ i ].keyword ) ); - ADMBP_end(); - return qfalse; - } - ADMBP( va( "^3!help: ^7help for '!%s':\n", - g_admin_cmds[ i ].keyword ) ); - ADMBP( va( " ^3Function: ^7%s\n", g_admin_cmds[ i ].function ) ); - ADMBP( va( " ^3Syntax: ^7!%s %s\n", g_admin_cmds[ i ].keyword, - g_admin_cmds[ i ].syntax ) ); - ADMBP( va( " ^3Flag: ^7'%s'\n", g_admin_cmds[ i ].flag ) ); - ADMBP_end(); - return qtrue; - } - } - for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) - { - if( !Q_stricmp( cmd, g_admin_commands[ i ]->command ) ) - { - if( !G_admin_permission( ent, g_admin_commands[ i ]->flag ) ) - { - ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n", - g_admin_commands[ i ]->command ) ); - ADMBP_end(); - return qfalse; - } - ADMBP( va( "^3!help: ^7help for '%s':\n", - g_admin_commands[ i ]->command ) ); - ADMBP( va( " ^3Description: ^7%s\n", g_admin_commands[ i ]->desc ) ); - ADMBP( va( " ^3Syntax: ^7!%s\n", g_admin_commands[ i ]->command ) ); - ADMBP_end(); - return qtrue; - } - } - ADMBP( va( "^3!help: ^7no help found for '%s'\n", cmd ) ); - ADMBP_end(); - return qfalse; - } -} - -qboolean G_admin_admintest( gentity_t *ent, int skiparg ) -{ - int i, l = 0; - qboolean found = qfalse; - qboolean lname = qfalse; - - if( !ent ) - { - ADMP( "^3!admintest: ^7you are on the console.\n" ); - return qtrue; - } - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) - { - if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) - { - found = qtrue; - break; - } - } - - if( found ) - { - l = g_admin_admins[ i ]->level; - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - if( g_admin_levels[ i ]->level != l ) - continue; - if( *g_admin_levels[ i ]->name ) - { - lname = qtrue; - break; - } - } - } - AP( va( "print \"^3!admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"", - ent->client->pers.netname, - l, - ( lname ) ? "(" : "", - ( lname ) ? g_admin_levels[ i ]->name : "", - ( lname ) ? ")" : "" ) ); - return qtrue; -} - -qboolean G_admin_allready( gentity_t *ent, int skiparg ) -{ - int i = 0; - gclient_t *cl; - - if( !level.intermissiontime ) - { - ADMP( "^3!allready: ^7this command is only valid during intermission\n" ); - return qfalse; - } - - for( i = 0; i < g_maxclients.integer; i++ ) - { - cl = level.clients + i; - if( cl->pers.connected != CON_CONNECTED ) - continue; - - if( cl->pers.teamSelection == PTE_NONE ) - continue; - - cl->readyToExit = 1; - } - AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - return qtrue; -} - -qboolean G_admin_cancelvote( gentity_t *ent, int skiparg ) -{ - - if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] ) - { - ADMP( "^3!cancelvote^7: no vote in progress\n" ); - return qfalse; - } - level.voteNo = level.numConnectedClients; - level.voteYes = 0; - CheckVote( ); - level.teamVoteNo[ 0 ] = level.numConnectedClients; - level.teamVoteYes[ 0 ] = 0; - CheckTeamVote( PTE_HUMANS ); - level.teamVoteNo[ 1 ] = level.numConnectedClients; - level.teamVoteYes[ 1 ] = 0; - CheckTeamVote( PTE_ALIENS ); - AP( va( "print \"^3!cancelvote: ^7%s^7 decided that everyone voted No\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - return qtrue; -} - -qboolean G_admin_passvote( gentity_t *ent, int skiparg ) -{ - if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] ) - { - ADMP( "^3!passvote^7: no vote in progress\n" ); - return qfalse; - } - level.voteYes = level.numConnectedClients; - level.voteNo = 0; - CheckVote( ); - level.teamVoteYes[ 0 ] = level.numConnectedClients; - level.teamVoteNo[ 0 ] = 0; - CheckTeamVote( PTE_HUMANS ); - level.teamVoteYes[ 1 ] = level.numConnectedClients; - level.teamVoteNo[ 1 ] = 0; - CheckTeamVote( PTE_ALIENS ); - AP( va( "print \"^3!passvote: ^7%s^7 decided that everyone voted Yes\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - return qtrue; -} - -static qboolean G_admin_global_pause( gentity_t *ent, int skiparg ) -{ - if( level.paused ) - { - level.pauseTime = level.time; - ADMP( "^3!pause: ^7Game is already paused, unpause timer reset\n" ); - return qfalse; - } - - level.paused = qtrue; - level.pauseTime = level.time; - - level.pause_speed = g_speed.value; - level.pause_gravity = g_gravity.value; - level.pause_knockback = g_knockback.value; - level.pause_ff = g_friendlyFire.integer; - level.pause_ffb = g_friendlyBuildableFire.integer; - - g_speed.value = 0; - g_gravity.value = 0; - g_knockback.value = 0; - g_friendlyFire.integer = 0; - g_friendlyBuildableFire.integer = 0; - - CP( "cp \"^1Game is PAUSED\"" ); - AP( va( "print \"^3!pause: ^7The game has been paused by %s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - - return qtrue; -} - -static qboolean G_admin_global_unpause( gentity_t *ent, int skiparg ) -{ - if( !level.paused ) - { - ADMP( "^3!unpause: ^7Game is not paused\n" ); - return qfalse; - } - - level.paused = qfalse; - - g_speed.value = level.pause_speed; - g_gravity.value = level.pause_gravity; - g_knockback.value = level.pause_knockback; - g_friendlyFire.integer = level.pause_ff; - g_friendlyBuildableFire.integer = level.pause_ffb; - - CP( "cp \"^2Game is RESUMED\"" ); - AP( va( "print \"^3!unpause: ^7The game has been resumed by %s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - - return qtrue; -} - -static qboolean is_numeric(const char *str) -{ - const char *p; - - if (!*str) - return qfalse; - - for (p = str; *p; p++) - if (*p < '0' || *p > '9') - return qfalse; - - return qtrue; -} - -qboolean G_admin_pause( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - int i; - int count = 0; - gentity_t *vic; - - G_SayArgv( skiparg, command, sizeof( command ) ); - cmd = command; - if( cmd && *cmd == '!' ) - cmd++; - - if( G_SayArgc() < 2 + skiparg ) - { - // global pause - if( !Q_stricmp( cmd, "pause" ) ) - return G_admin_global_pause( ent, skiparg ); - else - return G_admin_global_unpause( ent, skiparg ); - } - if( G_SayArgc() != 2 + skiparg ) - { - ADMP( va( "^3!%s: ^7usage: !%s (^5name|slot|*^7)\n", cmd, cmd ) ); - return qfalse; - } - - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( !Q_stricmp( name, "*" ) ) - { - for( i = 0; i < MAX_CLIENTS; i++ ) - { - vic = &g_entities[ i ]; - if( vic && vic->client && - vic->client->pers.connected == CON_CONNECTED ) - { - pids[ count ] = i; - count++; - } - } - } - else - { - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); - return qfalse; - } - count = 1; - } - - for( i = 0; i < count; i++ ) - { - vic = &g_entities[ pids[ i ] ]; - if ( !vic || !vic->client ) continue; - if( !admin_higher( ent, vic ) ) - { - if( count == 1 ) - ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" - " level than you\n", cmd ) ); - continue; - } - if( vic->client->pers.paused ) - { - if( !Q_stricmp( cmd, "pause" ) ) - { - if( count == 1 ) - ADMP( "^3!pause: ^7player is already paused\n" ); - continue; - } - vic->client->pers.paused = qfalse; - CPx( pids[ i ], "cp \"^2You've been unpaused\"" ); - if( count == 1 ) - AP( va( "print \"^3!unpause: ^7%s^7 unpaused by %s\n\"", - vic->client->pers.netname, - ( ent ) ? ent->client->pers.netname : "console" ) ); - } - else - { - if( !Q_stricmp( cmd, "unpause" ) ) - { - if( count == 1 ) - ADMP( "^3!unpause: ^7player is already unpaused\n" ); - continue; - } - if( count == 1 && ent == vic) - { - ADMP( "^3!pause: ^7you can not pause yourself\n" ); - continue; - } - vic->client->pers.paused = qtrue; - CPx( pids[ i ], va( "cp \"^1You've been paused by ^7%s\"", - ( ent ) ? ent->client->pers.netname : "console" ) ); - if( count == 1 ) - AP( va( "print \"^3!pause: ^7%s^7 paused by %s\n\"", - vic->client->pers.netname, - ( ent ) ? ent->client->pers.netname : "console" ) ); + trap_Argv( 1, str, sizeof( str ) ); + if( !Q_stricmp( str, "on" ) ) + { + if( g_cheats.integer ) + { + ADMP( "^3setdevmode: ^7developer mode is already on\n" ); + return qfalse; } - ClientUserinfoChanged( pids[ i ], qfalse ); + trap_Cvar_Set( "sv_cheats", "1" ); + trap_Cvar_Update( &g_cheats ); + AP( va( "print \"^3setdevmode: ^7%s ^7has switched developer mode on\n\"", + ent ? ent->client->pers.netname : "console" ) ); + return qtrue; } - return qtrue; -} - -qboolean G_admin_spec999( gentity_t *ent, int skiparg ) -{ - int i; - gentity_t *vic; - - for( i = 0; i < level.maxclients; i++ ) + else if( !Q_stricmp( str, "off" ) ) { - vic = &g_entities[ i ]; - if( !vic->client ) - continue; - if( vic->client->pers.connected != CON_CONNECTED ) - continue; - if( vic->client->pers.teamSelection == PTE_NONE ) - continue; - if( vic->client->ps.ping == 999 ) + if( !g_cheats.integer ) { - G_ChangeTeam( vic, PTE_NONE ); - AP( va( "print \"^3!spec999: ^7%s^7 moved ^7%s^7 to spectators\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - vic->client->pers.netname ) ); + ADMP( "^3setdevmode: ^7developer mode is already off\n" ); + return qfalse; } + trap_Cvar_Set( "sv_cheats", "0" ); + trap_Cvar_Update( &g_cheats ); + AP( va( "print \"^3setdevmode: ^7%s ^7has switched developer mode off\n\"", + ent ? ent->client->pers.netname : "console" ) ); + return qtrue; + } + else + { + ADMP( "^3setdevmode: ^7usage: setdevmode [on|off]\n" ); + return qfalse; } - return qtrue; } -qboolean G_admin_register(gentity_t *ent, int skiparg ){ - int level = 0; - - if( !ent ) return qtrue; +qboolean G_admin_kick( gentity_t *ent ) +{ + int pid; + char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; + int minargc; + gentity_t *vic; - level = G_admin_level(ent); + minargc = 3; + if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + minargc = 2; - if( level == 0 ) - level = 1; - - if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) + if( trap_Argc() < minargc ) { - ADMP( va( "^3!register: ^7 You cannot register for name protection until you update your client. Please replace your client executable with the one at http://trem.tjw.org/backport/ and reconnect. Updating your client will also allow you to have faster map downloads.\n" ) ); + ADMP( "^3kick: ^7usage: kick [name] [reason]\n" ); return qfalse; } - - if( g_newbieNumbering.integer - && g_newbieNamePrefix.string[ 0 ] - && !Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) ) + trap_Argv( 1, name, sizeof( name ) ); + reason = ConcatArgs( 2 ); + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^3kick: ^7%s", err ) ); + return qfalse; + } + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) + { + ADMP( "^3kick: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + if( vic->client->pers.localClient ) { - ADMP( va( "^3!register: ^7 You cannot register names that begin with '%s^7'.\n", - g_newbieNamePrefix.string ) ); + ADMP( "^3kick: ^7disconnecting the host would end the game\n" ); return qfalse; } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"", + pid, + vic->client->pers.guid, + vic->client->pers.netname, + reason ) ); + admin_create_ban( ent, + vic->client->pers.netname, + vic->client->pers.guidless ? "" : vic->client->pers.guid, + &vic->client->pers.ip, + MAX( 1, G_admin_parse_time( g_adminTempBan.string ) ), + ( *reason ) ? reason : "kicked by admin" ); + admin_writeconfig(); - trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d %d;",ent - g_entities, level) ); - - AP( va( "print \"^3!register: ^7%s^7 is now a protected nickname.\n\"", ent->client->pers.netname) ); - return qtrue; } -qboolean G_admin_rename( gentity_t *ent, int skiparg ) +qboolean G_admin_setivo( gentity_t* ent ) { - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; - char newname[ MAX_NAME_LENGTH ]; - char oldname[ MAX_NAME_LENGTH ]; - char err[ MAX_STRING_CHARS ]; - char userinfo[ MAX_INFO_STRING ]; - char *s; - gentity_t *victim = NULL; + char arg[ 3 ]; + const char *cn; + gentity_t *spot; - if( G_SayArgc() < 3 + skiparg ) + if( !ent ) { - ADMP( "^3!rename: ^7usage: !rename [name] [newname]\n" ); + ADMP( "^3setivo: ^7the console can't position intermission view overrides\n" ); return qfalse; } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - s = G_SayConcatArgs( 2 + skiparg ); - Q_strncpyz( newname, s, sizeof( newname ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) + + if( trap_Argc() != 2 ) { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!rename: ^7%s\n", err ) ); + ADMP( "^3setivo: ^7usage: setivo {s|a|h}\n" ); return qfalse; } - victim = &g_entities[ pids[ 0 ] ] ; - if( !admin_higher( ent, victim ) ) + trap_Argv( 1, arg, sizeof( arg ) ); + if( !Q_stricmp( arg, "s" ) ) + cn = "info_player_intermission"; + else if( !Q_stricmp( arg, "a" ) ) + cn = "info_alien_intermission"; + else if( !Q_stricmp( arg, "h" ) ) + cn = "info_human_intermission"; + else { - ADMP( "^3!rename: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); + ADMP( "^3setivo: ^7the argument must be either s, a or h\n" ); return qfalse; } - if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) ) + + spot = G_Find( NULL, FOFS( classname ), cn ); + if( !spot ) { - G_admin_print( ent, va( "^3!rename: Invalid name: ^7%s\n", err ) ); - return qfalse; + spot = G_Spawn(); + spot->classname = (char *)cn; } - level.clients[ pids[ 0 ] ].pers.nameChanges--; - level.clients[ pids[ 0 ] ].pers.nameChangeTime = 0; - trap_GetUserinfo( pids[ 0 ], userinfo, sizeof( userinfo ) ); - s = Info_ValueForKey( userinfo, "name" ); - Q_strncpyz( oldname, s, sizeof( oldname ) ); - Info_SetValueForKey( userinfo, "name", newname ); - trap_SetUserinfo( pids[ 0 ], userinfo ); - ClientUserinfoChanged( pids[ 0 ], qtrue ); - if( strcmp( oldname, level.clients[ pids[ 0 ] ].pers.netname ) ) - AP( va( "print \"^3!rename: ^7%s^7 has been renamed to %s^7 by %s\n\"", - oldname, - level.clients[ pids[ 0 ] ].pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + spot->count = 1; + + BG_GetClientViewOrigin( &ent->client->ps, spot->r.currentOrigin ); + VectorCopy( ent->client->ps.viewangles, spot->r.currentAngles ); + + ADMP( "^3setivo: ^7intermission view override positioned\n" ); return qtrue; } -qboolean G_admin_restart( gentity_t *ent, int skiparg ) +qboolean G_admin_ban( gentity_t *ent ) { - char layout[ MAX_CVAR_VALUE_STRING ] = { "" }; - char teampref[ MAX_CVAR_VALUE_STRING ] = { "" }; + int seconds; + char search[ MAX_NAME_LENGTH ]; + char secs[ MAX_TOKEN_CHARS ]; + char *reason; + char duration[ MAX_DURATION_LENGTH ]; int i; - gclient_t *cl; - - if( G_SayArgc( ) > 1 + skiparg ) - { - char map[ MAX_QPATH ]; - - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - G_SayArgv( skiparg + 1, layout, sizeof( layout ) ); + addr_t ip; + qboolean ipmatch = qfalse; + namelog_t *match = NULL; + qboolean cidr = qfalse; - if( Q_stricmp( layout, "keepteams" ) && Q_stricmp( layout, "keepteamslock" ) && Q_stricmp( layout, "switchteams" ) && Q_stricmp( layout, "switchteamslock" ) ) - { - if( !Q_stricmp( layout, "*BUILTIN*" ) || - trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), - NULL, FS_READ ) > 0 ) - { - trap_Cvar_Set( "g_layouts", layout ); - } - else - { - ADMP( va( "^3!restart: ^7layout '%s' does not exist\n", layout ) ); - return qfalse; - } - } - else - { - strcpy(layout,""); - G_SayArgv( skiparg + 1, teampref, sizeof( teampref ) ); - } - } - - if( G_SayArgc( ) > 2 + skiparg ) + if( trap_Argc() < 2 ) { - G_SayArgv( skiparg + 2, teampref, sizeof( teampref ) ); + ADMP( "^3ban: ^7usage: ban [name|slot|IP(/mask)] [duration] [reason]\n" ); + return qfalse; } - - - if( !Q_stricmp( teampref, "keepteams" ) || !Q_stricmp( teampref, "keepteamslock" ) ) - { - for( i = 0; i < g_maxclients.integer; i++ ) - { - cl = level.clients + i; - if( cl->pers.connected != CON_CONNECTED ) - continue; - - if( cl->pers.teamSelection == PTE_NONE ) - continue; + trap_Argv( 1, search, sizeof( search ) ); + trap_Argv( 2, secs, sizeof( secs ) ); - cl->sess.restartTeam = cl->pers.teamSelection; - } - } - else if(!Q_stricmp( teampref, "switchteams" ) || !Q_stricmp( teampref, "switchteamslock" )) + seconds = G_admin_parse_time( secs ); + if( seconds <= 0 ) { - for( i = 0; i < g_maxclients.integer; i++ ) - { - cl = level.clients + i; - if( cl->pers.connected != CON_CONNECTED ) - continue; - - if( cl->pers.teamSelection == PTE_NONE ) - continue; - - if( cl->pers.teamSelection == PTE_ALIENS ) - cl->sess.restartTeam = PTE_HUMANS; - else if(cl->pers.teamSelection == PTE_HUMANS ) - cl->sess.restartTeam = PTE_ALIENS; - } + seconds = 0; + reason = ConcatArgs( 2 ); } - - if( !Q_stricmp( teampref, "switchteamslock" ) || !Q_stricmp( teampref, "keepteamslock" ) ) + else { - trap_Cvar_Set( "g_lockTeamsAtStart", "1" ); + reason = ConcatArgs( 3 ); } - - trap_SendConsoleCommand( EXEC_APPEND, "map_restart" ); - - if(teampref[ 0 ]) - strcpy(teampref,va( "^7(with teams option: '%s^7')", teampref )); - - G_admin_maplog_result( "R" ); - - AP( va( "print \"^3!restart: ^7map restarted by %s %s %s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "", - teampref ) ); - return qtrue; -} - -qboolean G_admin_nobuild( gentity_t *ent, int skiparg ) -{ - char buffer[ MAX_STRING_CHARS ]; - int i, tmp; - - if( G_SayArgc() < 2 + skiparg ) + if( !*reason && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) { - ADMP( "^3!nobuild: ^7usage: !nobuild (^5enable / disable / log / remove / save^7)\n" ); + ADMP( "^3ban: ^7you must specify a reason\n" ); return qfalse; } - - G_SayArgv( 1 + skiparg, buffer, sizeof( buffer ) ); - - if( !Q_stricmp( buffer, "enable" ) || !Q_stricmp( buffer, "Enable" ) ) - { - if( G_SayArgc() < 4 + skiparg ) - { - ADMP( "^3!nobuild: ^7usage: !nobuild enable (^5area^7) (^5height^7)\n" ); - return qfalse; - } - - if( !level.noBuilding ) - { - - level.noBuilding = qtrue; - - // Grab and set the area - G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); - tmp = atoi( buffer ); - level.nbArea = tmp; - - // Grab and set the height - G_SayArgv( 3 + skiparg, buffer, sizeof( buffer ) ); - tmp = atoi( buffer ); - level.nbHeight = tmp; - - ADMP( "^3!nobuild: ^7nobuild is now enabled, please place a buildable to spawn a marker\n" ); - return qtrue; - } - else - { - ADMP( "^3!nobuild: ^7nobuild is already enabled. type !nobuild disable to disable nobuild mode.\n" ); - return qfalse; - } - } - - if( !Q_stricmp( buffer, "disable" ) || !Q_stricmp( buffer, "Disable" ) ) - { - if( level.noBuilding ) - { - level.noBuilding = qfalse; - level.nbArea = 0.0f; - level.nbHeight = 0.0f; - ADMP( "^3!nobuild: ^7nobuild is now disabled\n" ); - return qtrue; - } - else - { - ADMP( "^3!nobuild: ^7nobuild is disabled. type !nobuild enable (^5area^7) (^5height^7) to enable nobuild mode.\n" ); - return qfalse; - } - } - - if( !Q_stricmp( buffer, "log" ) || !Q_stricmp( buffer, "Log" ) ) - { - ADMBP_begin(); - - tmp = 0; - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker == NULL ) - continue; - - // This will display a start at 1, and not 0. This is to stop confusion by server ops - ADMBP( va( "^7#%i at origin (^5%f %f %f^7)^7\n", i + 1, level.nbMarkers[ i ].Origin[0], level.nbMarkers[ i ].Origin[1], level.nbMarkers[ i ].Origin[2] ) ); - tmp++; - } - ADMBP( va( "^3!nobuild:^7 displaying %i marker(s)\n", tmp ) ); - - ADMBP_end(); - return qtrue; - } - - if( !Q_stricmp( buffer, "remove" ) || !Q_stricmp( buffer, "Remove" ) ) - { - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!nobuild: ^7usage: !nobuild remove (^5marker #^7)\n" ); - return qfalse; - } - - G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); - tmp = atoi( buffer ) - 1; - // ^ that was to work with the display number... - - if( level.nbMarkers[ tmp ].Marker == NULL ) - { - ADMP( "^3!nobuild: ^7that is not a valid marker number\n" ); - return qfalse; - } - // Donno why im doing this, but lets clear this... - level.nbMarkers[ tmp ].Marker->noBuild.isNB = qfalse; - level.nbMarkers[ tmp ].Marker->noBuild.Area = 0.0f; - level.nbMarkers[ tmp ].Marker->noBuild.Height = 0.0f; - - // Free the entitiy and null it out... - G_FreeEntity( level.nbMarkers[ tmp ].Marker ); - level.nbMarkers[ tmp ].Marker = NULL; - - // That is to work with the display number... - ADMP( va( "^3!nobuild:^7 marker %i has been removed\n", tmp + 1 ) ); - return qtrue; - } - - if( !Q_stricmp( buffer, "save" ) || !Q_stricmp( buffer, "Save" ) ) - { - int i, tmp; - - G_NobuildSave( ); - - tmp = 0; - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker == NULL ) - continue; - - tmp++; - } - ADMP( va( "^3!nobuild:^7 %i nobuild markers have been saved\n", tmp ) ); - return qtrue; + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) + { + int maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) ); + if( seconds == 0 || seconds > maximum ) + { + ADMP( "^3ban: ^7you may not issue permanent bans\n" ); + seconds = maximum; + } } - return qfalse; -} -qboolean G_admin_nextmap( gentity_t *ent, int skiparg ) -{ - AP( va( "print \"^3!nextmap: ^7%s^7 decided to load the next map\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - level.lastWin = PTE_NONE; - trap_SetConfigstring( CS_WINNER, "Evacuation" ); - LogExit( va( "nextmap was run by %s", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - G_admin_maplog_result( "N" ); - return qtrue; -} + if( G_AddressParse( search, &ip ) ) + { + int max = ip.type == IPv4 ? 32 : 128; + int min; -static const char *displaySchachtmeisterJudgement(const schachtmeisterJudgement_t *j, - g_admin_namelog_t *namelog) -{ - static char buffer[20]; - - if (G_admin_permission_guid(namelog->guid, ADMF_INCOGNITO)) { - Com_sprintf(buffer, sizeof(buffer), "^2 +0"); - } else if (strcmp(namelog->guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") && - (G_admin_permission_guid(namelog->guid, ADMF_NOAUTOBAHN) || - G_admin_permission_guid(namelog->guid, ADMF_IMMUNITY))) { - Com_sprintf(buffer, sizeof(buffer), "^7 N/A "); - } else if (!j->ratingTime) { - Com_sprintf(buffer, sizeof(buffer), "^5 wait"); - } else { - int color; - - if (j->rating >= g_schachtmeisterClearThreshold.integer) - color = 2; - else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) - color = 1; - else - color = 3; - - Com_sprintf(buffer, sizeof(buffer), "^%i%+5i", color, j->rating); - } - - return buffer; -} + cidr = G_admin_permission( ent, ADMF_CAN_IP_BAN ); + if( cidr ) + min = ent ? max / 2 : 1; + else + min = max; -qboolean G_admin_namelog( gentity_t *ent, int skiparg ) -{ - int i, j; - char search[ MAX_NAME_LENGTH ] = {""}; - char s2[ MAX_NAME_LENGTH ] = {""}; - char n2[ MAX_NAME_LENGTH ] = {""}; - char guid_stub[ 9 ]; - qboolean found = qfalse; - int printed = 0; + if( ip.mask < min || ip.mask > max ) + { + ADMP( va( "^3ban: ^7invalid netmask (%d is not in the range %d-%d)\n", + ip.mask, min, max ) ); + return qfalse; + } + ipmatch = qtrue; - if( G_SayArgc() > 1 + skiparg ) - { - G_SayArgv( 1 + skiparg, search, sizeof( search ) ); - G_SanitiseString( search, s2, sizeof( s2 ) ); - } - ADMBP_begin(); - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - if( search[0] ) + for( match = level.namelogs; match; match = match->next ) { - found = qfalse; - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && - g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) + // skip players in the namelog who have already been banned + if( match->banned ) + continue; + + for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ ) { - G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); - if( strstr( n2, s2 ) ) - { - found = qtrue; + if( G_AddressCompare( &ip, &match->ip[ i ] ) ) break; - } } - if( !found ) - continue; + if( i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ] ) + break; } - printed++; - for( j = 0; j < 8; j++ ) - guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ]; - guid_stub[ j ] = '\0'; - if( g_admin_namelog[ i ]->slot > -1 ) - ADMBP( "^3" ); - ADMBP( va( "%-2s (*%s) %15s %s^7", - (g_admin_namelog[ i ]->slot > -1 ) ? - va( "%d", g_admin_namelog[ i ]->slot ) : "-", - guid_stub, g_admin_namelog[ i ]->ip, - displaySchachtmeisterJudgement( &g_admin_namelog[ i ]->smj, g_admin_namelog[ i ] ) ) ); - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && - g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) + + if( !match ) { - ADMBP( va( " '%s^7'", g_admin_namelog[ i ]->name[ j ] ) ); + if( cidr ) + { + ADMP( "^3ban: ^7no player found by that IP address; banning anyway\n" ); + } + else + { + ADMP( "^3ban: ^7no player found by that IP address\n" ); + return qfalse; + } } - ADMBP( "\n" ); - } - ADMBP( va( "^3!namelog:^7 %d recent clients found\n", printed ) ); - ADMBP_end(); - return qtrue; -} - -qboolean G_admin_lock( gentity_t *ent, int skiparg ) -{ - char teamName[2] = {""}; - pTeam_t team; + } + else if( !( match = G_NamelogFromString( ent, search ) ) || match->banned ) + { + ADMP( "^3ban: ^7no match\n" ); + return qfalse; + } - if( G_SayArgc() < 2 + skiparg ) + if( ent && match && !admin_higher_guid( ent->client->pers.guid, match->guid ) ) { - ADMP( "^3!lock: ^7usage: !lock [a|h]\n" ); + + ADMP( "^3ban: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); return qfalse; } - G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) ); - if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' ) - team = PTE_ALIENS; - else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' ) - team = PTE_HUMANS; - else + if( match && match->slot > -1 && level.clients[ match->slot ].pers.localClient ) { - ADMP( va( "^3!lock: ^7invalid team\"%c\"\n", teamName[0] ) ); + ADMP( "^3ban: ^7disconnecting the host would end the game\n" ); return qfalse; } - if( team == PTE_ALIENS ) + G_admin_duration( ( seconds ) ? seconds : -1, + duration, sizeof( duration ) ); + + AP( va( "print \"^3ban:^7 %s^7 has been banned by %s^7 " + "duration: %s, reason: %s\n\"", + match ? match->name[ match->nameOffset ] : "an IP address", + ( ent ) ? ent->client->pers.netname : "console", + duration, + ( *reason ) ? reason : "banned by admin" ) ); + + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"", + seconds, + match ? match->guid : "", + match ? match->name[ match->nameOffset ] : "IP ban", + reason ) ); + if( ipmatch ) { - if( level.alienTeamLocked ) + if( match ) { - ADMP( "^3!lock: ^7Alien team is already locked\n" ); - return qfalse; + admin_create_ban( ent, + match->slot == -1 ? + match->name[ match->nameOffset ] : + level.clients[ match->slot ].pers.netname, + match->guidless ? "" : match->guid, + &ip, + seconds, reason ); } else - level.alienTeamLocked = qtrue; + { + admin_create_ban( ent, "IP ban", "", &ip, seconds, reason ); + } + admin_log( va( "[%s]", ip.str ) ); } - else if( team == PTE_HUMANS ) { - if( level.humanTeamLocked ) + else + { + // ban all IP addresses used by this player + for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ ) { - ADMP( "^3!lock: ^7Human team is already locked\n" ); - return qfalse; + admin_create_ban( ent, + match->slot == -1 ? + match->name[ match->nameOffset ] : + level.clients[ match->slot ].pers.netname, + match->guidless ? "" : match->guid, + &match->ip[ i ], + seconds, reason ); + admin_log( va( "[%s]", match->ip[ i ].str ) ); } - else - level.humanTeamLocked = qtrue; } - AP( va( "print \"^3!lock: ^7%s team has been locked by %s\n\"", - ( team == PTE_ALIENS ) ? "Alien" : "Human", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + if( match ) + match->banned = qtrue; + + if( !g_admin.string[ 0 ] ) + ADMP( "^3ban: ^7WARNING g_admin not set, not saving ban to a file\n" ); + else + admin_writeconfig(); + return qtrue; -} +} -qboolean G_admin_unlock( gentity_t *ent, int skiparg ) +qboolean G_admin_unban( gentity_t *ent ) { - char teamName[2] = {""}; - pTeam_t team; + int bnum; + int time = trap_RealTime( NULL ); + char bs[ 5 ]; + int i; + g_admin_ban_t *ban; - if( G_SayArgc() < 2 + skiparg ) + if( trap_Argc() < 2 ) { - ADMP( "^3!unlock: ^7usage: !unlock [a|h]\n" ); + ADMP( "^3unban: ^7usage: unban [ban#]\n" ); return qfalse; } - G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) ); - if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' ) - team = PTE_ALIENS; - else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' ) - team = PTE_HUMANS; - else + trap_Argv( 1, bs, sizeof( bs ) ); + bnum = atoi( bs ); + for( ban = g_admin_bans, i = 1; ban && i < bnum; ban = ban->next, i++ ) + ; + if( i != bnum || !ban ) { - ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) ); + ADMP( "^3unban: ^7invalid ban#\n" ); return qfalse; } - - if( team == PTE_ALIENS ) + if( ban->expires > 0 && ban->expires - time <= 0 ) { - if( !level.alienTeamLocked ) - { - ADMP( "^3!unlock: ^7Alien team is not currently locked\n" ); - return qfalse; - } - else - level.alienTeamLocked = qfalse; + ADMP( "^3unban: ^7ban already expired\n" ); + return qfalse; } - else if( team == PTE_HUMANS ) { - if( !level.humanTeamLocked ) - { - ADMP( "^3!unlock: ^7Human team is not currently locked\n" ); - return qfalse; - } - else - level.humanTeamLocked = qfalse; + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + ( ban->expires == 0 || ( ban->expires - time > MAX( 1, + G_admin_parse_time( g_adminMaxBan.string ) ) ) ) ) + { + ADMP( "^3unban: ^7you cannot remove permanent bans\n" ); + return qfalse; + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]", + ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason, + ban->ip.str ) ); + AP( va( "print \"^3unban: ^7ban #%d for %s^7 has been removed by %s\n\"", + bnum, + ban->name, + ( ent ) ? ent->client->pers.netname : "console" ) ); + ban->expires = time; + admin_writeconfig(); + return qtrue; +} + +qboolean G_admin_addlayout( gentity_t *ent ) +{ + char layout[ MAX_QPATH ]; + + if( trap_Argc( ) != 2 ) + { + ADMP( "^3addlayout: ^7usage: addlayout \n" ); + return qfalse; } - AP( va( "print \"^3!unlock: ^7%s team has been unlocked by %s\n\"", - ( team == PTE_ALIENS ) ? "Alien" : "Human", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + trap_Argv( 1, layout, sizeof( layout ) ); + + G_LayoutLoad( layout ); + + AP( va( "print \"^3addlayout: ^7some layout elements have been placed by %s\n\"", + ent ? ent->client->pers.netname : "console" ) ); return qtrue; -} +} -qboolean G_admin_designate( gentity_t *ent, int skiparg ) +qboolean G_admin_adjustban( gentity_t *ent ) { - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - gentity_t *vic; + int bnum; + int length, maximum; + int expires; + int time = trap_RealTime( NULL ); + char duration[ MAX_DURATION_LENGTH ] = {""}; + char *reason; + char bs[ 5 ]; + char secs[ MAX_TOKEN_CHARS ]; + char mode = '\0'; + g_admin_ban_t *ban; + int mask = 0; + int i; + int skiparg = 0; - if( G_SayArgc() < 2 + skiparg ) + if( trap_Argc() < 3 ) { - ADMP( "^3!designate: ^7usage: designate [name|slot#]\n" ); + ADMP( "^3adjustban: ^7usage: adjustban [ban#] [/mask] [duration] [reason]" + "\n" ); return qfalse; } - G_SayArgv( skiparg, command, sizeof( command ) ); - cmd = command; - if( cmd && *cmd == '!' ) - cmd++; - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) + trap_Argv( 1, bs, sizeof( bs ) ); + bnum = atoi( bs ); + for( ban = g_admin_bans, i = 1; ban && i < bnum; ban = ban->next, i++ ); + if( i != bnum || !ban ) { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!designate: ^7%s\n", err ) ); + ADMP( "^3adjustban: ^7invalid ban#\n" ); return qfalse; } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) && - !Q_stricmp( cmd, "undesignate" ) ) + maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) ); + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + ( ban->expires == 0 || ban->expires - time > maximum ) ) { - ADMP( "^3!designate: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); + ADMP( "^3adjustban: ^7you cannot modify permanent bans\n" ); return qfalse; } - vic = &g_entities[ pids[ 0 ] ]; - if( vic->client->pers.designatedBuilder == qtrue ) + trap_Argv( 2, secs, sizeof( secs ) ); + if( secs[ 0 ] == '/' ) + { + int max = ban->ip.type == IPv6 ? 128 : 32; + int min; + + if( G_admin_permission( ent, ADMF_CAN_IP_BAN ) ) + min = ent ? max / 2 : 1; + else + min = max; + + mask = atoi( secs + 1 ); + if( mask < min || mask > max ) + { + ADMP( va( "^3adjustban: ^7invalid netmask (%d is not in range %d-%d)\n", + mask, min, max ) ); + return qfalse; + } + trap_Argv( 3 + skiparg++, secs, sizeof( secs ) ); + } + if( secs[ 0 ] == '+' || secs[ 0 ] == '-' ) + mode = secs[ 0 ]; + length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] ); + if( length < 0 ) + skiparg--; + else { - if( !Q_stricmp( cmd, "designate" ) ) + if( length ) + { + if( ban->expires == 0 && mode ) + { + ADMP( "^3adjustban: ^7new duration must be explicit\n" ); + return qfalse; + } + if( mode == '+' ) + expires = ban->expires + length; + else if( mode == '-' ) + expires = ban->expires - length; + else + expires = time + length; + if( expires <= time ) + { + ADMP( "^3adjustban: ^7ban duration must be positive\n" ); + return qfalse; + } + } + else + length = expires = 0; + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + ( length == 0 || length > maximum ) ) { - ADMP( "^3!designate: ^7player is already designated builder\n" ); - return qtrue; + ADMP( "^3adjustban: ^7you may not issue permanent bans\n" ); + expires = time + maximum; } - vic->client->pers.designatedBuilder = qfalse; - CPx( pids[ 0 ], "cp \"^1Your designation has been revoked\"" ); - AP( va( - "print \"^3!designate: ^7%s^7's designation has been revoked by %s\n\"", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - G_CheckDBProtection( ); + + ban->expires = expires; + G_admin_duration( ( expires ) ? expires - time : -1, duration, + sizeof( duration ) ); } - else + if( mask ) { - if( !Q_stricmp( cmd, "undesignate" ) ) - { - ADMP( "^3!undesignate: ^7player is not currently designated builder\n" ); - return qtrue; - } - vic->client->pers.designatedBuilder = qtrue; - CPx( pids[ 0 ], "cp \"^1You've been designated\"" ); - AP( va( "print \"^3!designate: ^7%s^7 has been designated by ^7%s\n\"", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + char *p = strchr( ban->ip.str, '/' ); + if( !p ) + p = ban->ip.str + strlen( ban->ip.str ); + if( mask == ( ban->ip.type == IPv6 ? 128 : 32 ) ) + *p = '\0'; + else + Com_sprintf( p, sizeof( ban->ip.str ) - ( p - ban->ip.str ), "/%d", mask ); + ban->ip.mask = mask; } + reason = ConcatArgs( 3 + skiparg ); + if( *reason ) + Q_strncpyz( ban->reason, reason, sizeof( ban->reason ) ); + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]", + ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason, + ban->ip.str ) ); + AP( va( "print \"^3adjustban: ^7ban #%d for %s^7 has been updated by %s^7 " + "%s%s%s%s%s%s\n\"", + bnum, + ban->name, + ( ent ) ? ent->client->pers.netname : "console", + ( mask ) ? + va( "netmask: /%d%s", mask, + ( length >= 0 || *reason ) ? ", " : "" ) : "", + ( length >= 0 ) ? "duration: " : "", + duration, + ( length >= 0 && *reason ) ? ", " : "", + ( *reason ) ? "reason: " : "", + reason ) ); + if( ent ) + Q_strncpyz( ban->banner, ent->client->pers.netname, sizeof( ban->banner ) ); + admin_writeconfig(); return qtrue; } - //!Warn by Gate (Daniel Evans) -qboolean G_admin_warn( gentity_t *ent, int skiparg ) -{//mostly copy and paste with the proper lines altered from !mute and !kick - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; - int minargc; +qboolean G_admin_putteam( gentity_t *ent ) +{ + int pid; + char name[ MAX_NAME_LENGTH ], team[ sizeof( "spectators" ) ], + err[ MAX_STRING_CHARS ]; gentity_t *vic; + team_t teamnum = TEAM_NONE; - minargc = 3 + skiparg; - if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) - minargc = 2 + skiparg; - - if( G_SayArgc() < minargc ) - { - ADMP( "^3!warn: ^7usage: warn [name] [reason]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - reason = G_SayConcatArgs( 2 + skiparg ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) + trap_Argv( 1, name, sizeof( name ) ); + trap_Argv( 2, team, sizeof( team ) ); + if( trap_Argc() < 3 ) { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!warn: ^7%s\n", err ) ); + ADMP( "^3putteam: ^7usage: putteam [name] [h|a|s]\n" ); return qfalse; } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) + + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) { - ADMP( "^3!warn: ^7sorry, but your intended victim has a higher admin" - " level than you.\n" ); + ADMP( va( "^3putteam: ^7%s", err ) ); return qfalse; } - - vic = &g_entities[ pids[ 0 ] ]; - //next line is the onscreen warning - CPx( pids[ 0 ],va("cp \"^1You have been warned by an administrator.\n ^3Cease immediately or face admin action!\n^1 %s%s\"",(*reason)? "REASON: " : "" ,(*reason)? reason : "") ); - AP( va( "print \"^3!warn: ^7%s^7 has been warned to cease and desist: %s by %s \n\"", - vic->client->pers.netname, (*reason) ? reason : "his current activity", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );//console announcement - return qtrue; -} - -qboolean G_admin_putmespec( gentity_t *ent, int skiparg ) -{ - if( !ent ) + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) { - ADMP( "!specme: sorry, but console isn't allowed on the spectators team\n"); + ADMP( "^3putteam: ^7sorry, but your intended victim has a higher " + " admin level than you\n" ); return qfalse; } - - if( level.paused ) + teamnum = G_TeamFromString( team ); + if( teamnum == NUM_TEAMS ) { - ADMP("!specme: disabled when game is paused\n"); + ADMP( va( "^3putteam: ^7unknown team %s\n", team ) ); return qfalse; } - - if(ent->client->pers.teamSelection == PTE_NONE) + if( vic->client->pers.teamSelection == teamnum ) return qfalse; - - //guard against build timer exploit - if( ent->client->pers.teamSelection != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR && - ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || - ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || - BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) || - BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) && - ent->client->ps.stats[ STAT_MISC ] > 0 ) - { - ADMP("!specme: You cannot leave your team until the build timer expires"); - return qfalse; - } - - G_ChangeTeam( ent, PTE_NONE ); - AP( va("print \"^3!specme: ^7%s^7 decided to join the spectators\n\"", ent->client->pers.netname ) ); + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, vic->client->pers.guid, + vic->client->pers.netname ) ); + G_ChangeTeam( vic, teamnum ); + + AP( va( "print \"^3putteam: ^7%s^7 put %s^7 on to the %s team\n\"", + ( ent ) ? ent->client->pers.netname : "console", + vic->client->pers.netname, BG_TeamName( teamnum ) ) ); return qtrue; } -qboolean G_admin_slap( gentity_t *ent, int skiparg ) +qboolean G_admin_changemap( gentity_t *ent ) { - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - gentity_t *vic; - vec3_t dir; - - if( level.intermissiontime ) return qfalse; + char map[ MAX_QPATH ]; + char layout[ MAX_QPATH ] = { "" }; - if( G_SayArgc() < 2 + skiparg ) + if( trap_Argc( ) < 2 ) { - ADMP( "^3!slap: ^7usage: !slap [name|slot#]\n" ); + ADMP( "^3changemap: ^7usage: changemap [map] (layout)\n" ); return qfalse; } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!slap: ^7%s\n", err ) ); - return qfalse; - } + trap_Argv( 1, map, sizeof( map ) ); - vic = &g_entities[ pids[ 0 ] ]; - if( !vic ) - { - ADMP( "^3!slap: ^7bad target\n" ); - return qfalse; - } - if( vic == ent ) + if( !G_MapExists( map ) ) { - ADMP( "^3!slap: ^7sorry, you cannot slap yourself\n" ); + ADMP( va( "^3changemap: ^7invalid map name '%s'\n", map ) ); return qfalse; } - if( !admin_higher( ent, vic ) ) - { - ADMP( "^3!slap: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); - return qfalse; - } - if( vic->client->pers.teamSelection == PTE_NONE || - vic->client->pers.classSelection == PCL_NONE ) - { - ADMP( "^3!slap: ^7can't slap spectators\n" ); - return qfalse; - } - - // knockback in a random direction - dir[0] = crandom(); - dir[1] = crandom(); - dir[2] = random(); - G_Knockback( vic, dir, g_slapKnockback.integer ); - trap_SendServerCommand( vic-g_entities, - va( "cp \"%s^7 is not amused\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - - if( g_slapDamage.integer > 0 ) + if( trap_Argc( ) > 2 ) { - int damage; - - if( G_SayArgc() > 2 + skiparg ) + trap_Argv( 2, layout, sizeof( layout ) ); + if( G_LayoutExists( map, layout ) ) { - char dmg_str[ MAX_STRING_CHARS ]; - G_SayArgv( 2 + skiparg, dmg_str, sizeof( dmg_str ) ); - damage = atoi(dmg_str); - if( damage < 0 ) damage = 0; + trap_Cvar_Set( "g_nextLayout", layout ); } else { - if( g_slapDamage.integer > 100 ) g_slapDamage.integer = 100; - damage = BG_FindHealthForClass( vic->client->ps.stats[ STAT_PCLASS ] ) * - g_slapDamage.integer / 100; - if( damage < 1 ) damage = 1; - } - - vic->health -= damage; - vic->client->ps.stats[ STAT_HEALTH ] = vic->health; - vic->lastDamageTime = level.time; - if( vic->health <= 0 ) - { - vic->flags |= FL_NO_KNOCKBACK; - vic->enemy = &g_entities[ pids[ 0 ] ]; - vic->die( vic, ent, ent, damage, MOD_SLAP ); - } - else if( vic->pain ) - { - vic->pain( vic, &g_entities[ pids[ 0 ] ], damage ); + ADMP( va( "^3changemap: ^7invalid layout name '%s'\n", layout ) ); + return qfalse; } } + admin_log( map ); + admin_log( layout ); + + G_MapConfigs( map ); + trap_SendConsoleCommand( EXEC_APPEND, va( "%smap \"%s\"\n", + ( g_cheats.integer ? "dev" : "" ), map ) ); + level.restarted = qtrue; + AP( va( "print \"^3changemap: ^7map '%s' started by %s^7 %s\n\"", map, + ( ent ) ? ent->client->pers.netname : "console", + ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); return qtrue; } -qboolean G_admin_drop( gentity_t *ent, int skiparg ) +qboolean G_admin_mute( gentity_t *ent ) { - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( "^3!drop: ^7usage: !drop [name|slot#] [message]\n" ); - return qfalse; - } + char name[ MAX_NAME_LENGTH ]; + char command[ MAX_ADMIN_CMD_LEN ]; + namelog_t *vic; - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) + trap_Argv( 0, command, sizeof( command ) ); + if( trap_Argc() < 2 ) { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!drop: ^7%s\n", err ) ); + ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) ); return qfalse; } - - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) + trap_Argv( 1, name, sizeof( name ) ); + if( !( vic = G_NamelogFromString( ent, name ) ) ) { - ADMP( "^3!drop: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); - return qfalse; - } - - // victim's message - if( G_SayArgc() > 2 + skiparg ) - trap_SendServerCommand( pids[ 0 ], - va( "disconnect \"You have been dropped.\n%s^7\n\"", - G_SayConcatArgs( 2 + skiparg ) ) ); - else - trap_SendServerCommand( pids[ 0 ], va( "disconnect" ) ); - - // server message - trap_DropClient( pids[ 0 ], va( "disconnected" ) ); - - return qtrue; -} - -qboolean G_admin_buildlog( gentity_t *ent, int skiparg ) -{ -#define LOG_DISPLAY_LENGTH 10 - buildHistory_t *ptr; - gentity_t *builder = NULL; - int skip = 0, start = 0, lastID = -1, firstID = -1, i, len, matchlen = 0; - pTeam_t team = PTE_NONE; - char message[ MAX_STRING_CHARS ], *teamchar; - char *name, *action, *buildablename, markstring[ MAX_STRING_CHARS ]; - if( !g_buildLogMaxLength.integer ) - { - ADMP( "^3!buildlog: ^7build logging is disabled" ); + ADMP( va( "^3%s: ^7no match\n", command ) ); return qfalse; } - if( G_SayArgc( ) >= 2 + skiparg ) - { - for( i = 1; i + skiparg < G_SayArgc( ); i++ ) - { - char argbuf[ 64 ], err[ MAX_STRING_CHARS ]; - int x = 0, pids[ MAX_CLIENTS ]; - G_SayArgv( i + skiparg, argbuf, sizeof argbuf ); - switch( argbuf[ 0 ]) - { - case 'x': - x = 1; - default: - skip = atoi( argbuf + x ); - start = 0; - break; - case '#': - start = atoi( argbuf + 1 ); - skip = 0; - break; - case '-': - if(G_ClientNumbersFromString(argbuf + 1, pids) != 1) - { - G_MatchOnePlayer(pids, err, sizeof(err)); - ADMP(va("^3!revert: ^7%s\n", err)); - return qfalse; - } - builder = g_entities + *pids; - break; - case 'A': - case 'a': - team = PTE_ALIENS; - break; - case 'H': - case 'h': - team = PTE_HUMANS; - break; - } - } - } - // !buildlog can be abused, so let everyone know when it is used - AP( va( "print \"^3!buildlog: ^7%s^7 requested a log of recent building" - " activity\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - len = G_CountBuildLog( ); // also clips the log if too long - if( !len ) + if( ent && !admin_higher_admin( ent->client->pers.admin, + G_admin_admin( vic->guid ) ) ) { - ADMP( "^3!buildlog: ^7no build log found\n" ); + ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin" + " level than you\n", command ) ); return qfalse; } - if( start ) + if( vic->muted ) { - // set skip based on start - for( ptr = level.buildHistory; ptr && ptr->ID != start; - ptr = ptr->next, skip++ ); - if( !ptr ) + if( !Q_stricmp( command, "mute" ) ) { - ADMP( "^3!buildlog: ^7log ID not found\n" ); - skip = 0; + ADMP( "^3mute: ^7player is already muted\n" ); + return qfalse; } + vic->muted = qfalse; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You have been unmuted\"" ); + AP( va( "print \"^3unmute: ^7%s^7 has been unmuted by %s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); } - // ensure skip is a useful value - if( skip > len - LOG_DISPLAY_LENGTH ) - skip = len - LOG_DISPLAY_LENGTH; - *message = '\0'; - // skip to start entry - for( ptr = level.buildHistory, i = len; ptr && i > len - skip; - ptr = ptr->next ) - { - // these checks could perhaps be done more efficiently but they are cheap - // in processor time so I'm not worrying - if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) ) - continue; - if( builder && builder != ptr->ent ) - continue; - matchlen++; - i--; - } - for( ; i + LOG_DISPLAY_LENGTH > len - skip && i > 0; i--, ptr = ptr->next ) + else { - if( !ptr ) - break; // run out of log - *markstring = '\0'; // reinit markstring - // check team - if( ( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) ) - || ( builder && builder != ptr->ent ) ) - { - skip++; // loop an extra time because we skipped one - continue; - } - if( lastID < 0 ) - lastID = ptr->ID; - firstID = ptr->ID; - matchlen++; - // set name to the ent's current name or last recorded name - if( ptr->ent ) - { - if( ptr->ent->client ) - name = ptr->ent->client->pers.netname; - else - name = ""; // any non-client action - } - else - name = ptr->name; - switch( ptr->fate ) - { - case BF_BUILT: - action = "^2built^7 a"; - break; - case BF_DECONNED: - action = "^3DECONSTRUCTED^7 a"; - break; - case BF_DESTROYED: - action = "destroyed a"; - break; - case BF_TEAMKILLED: - action = "^1TEAMKILLED^7 a"; - break; - default: - action = "\0"; // erm - break; - } - // handle buildables removed by markdecon - if( ptr->marked ) - { - buildHistory_t *mark; - int j, markdecon[ BA_NUM_BUILDABLES ], and = 2; - char bnames[ 32 ], *article; - mark = ptr; - // count the number of buildables - memset( markdecon, 0, sizeof( markdecon ) ); - while( ( mark = mark->marked ) ) - markdecon[ mark->buildable ]++; - // reverse order makes grammar easier - for( j = BA_NUM_BUILDABLES; j >= 0; j-- ) - { - buildablename = BG_FindHumanNameForBuildable( j ); - // plural is easy - if( markdecon[ j ] > 1 ) - Com_sprintf( bnames, 32, "%d %ss", markdecon[ j ], buildablename ); - // use an appropriate article - else if( markdecon[ j ] == 1 ) - { - if( BG_FindUniqueTestForBuildable( j ) ) - article = "the"; // if only one - else if( strchr( "aeiouAEIOU", *buildablename ) ) - article = "an"; // if first char is vowel - else - article = "a"; - Com_sprintf( bnames, 32, "%s %s", article, buildablename ); - } - else - continue; // none of this buildable - // C grammar: x, y, and z - // the integer and is 2 initially, the test means it is used on the - // second sprintf only, the reverse order makes this second to last - // the comma is only printed if there is already some markstring i.e. - // not the first time ( which would put it on the end of the string ) - Com_sprintf( markstring, sizeof( markstring ), "%s%s %s%s", bnames, - ( *markstring ) ? "," : "", ( and-- == 1 ) ? "and " : "", markstring ); - } - } - buildablename = BG_FindHumanNameForBuildable( ptr->buildable ); - switch( BG_FindTeamForBuildable( ptr->buildable ) ) + if( !Q_stricmp( command, "unmute" ) ) { - case PTE_ALIENS: - teamchar = "^1A"; - break; - case PTE_HUMANS: - teamchar = "^4H"; - break; - default: - teamchar = " "; // space so it lines up neatly - break; + ADMP( "^3unmute: ^7player is not currently muted\n" ); + return qfalse; } - // prepend the information to the string as we go back in buildhistory - // so the earliest events are at the top - Com_sprintf( message, MAX_STRING_CHARS, "%3d %s^7 %s^7 %s%s %s%s%s\n%s", - ptr->ID, teamchar, name, action, - ( strchr( "aeiouAEIOU", buildablename[ 0 ] ) ) ? "n" : "", - buildablename, ( markstring[ 0 ] ) ? ", removing " : "", - markstring, message ); - } - for( ; ptr; ptr = ptr->next ) - { - if( builder && builder != ptr->ent ) - continue; - if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) ) - continue; - matchlen++; + vic->muted = qtrue; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You've been muted\"" ); + AP( va( "print \"^3mute: ^7%s^7 has been muted by ^7%s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); } - if( matchlen ) - ADMP( va( "%s^3!buildlog: showing log entries %d - %d of %d\n", message, - firstID, lastID, matchlen ) ); - else - ADMP( "^3!buildlog: ^7no log entries match those criteria\n" ); + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid, + vic->name[ vic->nameOffset ] ) ); return qtrue; } -qboolean G_admin_revert( gentity_t *ent, int skiparg ) +qboolean G_admin_denybuild( gentity_t *ent ) { - int i = 0, j = 0, repeat = 1, ID = 0, len, matchlen=0; - pTeam_t team = PTE_NONE; - qboolean force = qfalse, reached = qfalse; - gentity_t *builder = NULL, *targ; - buildHistory_t *ptr, *tmp, *mark, *prev; - vec3_t dist; - char argbuf[ 64 ], *name, *bname, *action, *article; - len = G_CountBuildLog( ); - if( !len ) - { - ADMP( "^3!revert: ^7no build log found\n" ); - return qfalse; - } - if( G_SayArgc( ) < 2 + skiparg ) + char name[ MAX_NAME_LENGTH ]; + char command[ MAX_ADMIN_CMD_LEN ]; + namelog_t *vic; + + trap_Argv( 0, command, sizeof( command ) ); + if( trap_Argc() < 2 ) { - ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" ); + ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) ); return qfalse; } - for( i = 1; i + skiparg < G_SayArgc( ); i++ ) + trap_Argv( 1, name, sizeof( name ) ); + if( !( vic = G_NamelogFromString( ent, name ) ) ) { - char arg[ 64 ], err[ MAX_STRING_CHARS ]; - int pids[ MAX_CLIENTS ]; - G_SayArgv( i + skiparg, arg, sizeof arg ); - switch( arg[ 0 ]) - { - case 'x': - repeat = atoi( arg + 1 ); - break; - case '#': - ID = atoi( arg + 1 ); - break; - case '-': - if(G_ClientNumbersFromString(arg + 1, pids) != 1) - { - G_MatchOnePlayer(pids, err, sizeof err); - ADMP(va("^3!revert: ^7%s\n", err)); - return qfalse; - } - builder = g_entities + *pids; - break; - case 'A': - case 'a': - team = PTE_ALIENS; - break; - case 'H': - case 'h': - team = PTE_HUMANS; - break; - case '!': - force = qtrue; - break; - default: - ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" ); - return qfalse; - } + ADMP( va( "^3%s: ^7no match\n", command ) ); + return qfalse; } - if( repeat > 25 ) + if( ent && !admin_higher_admin( ent->client->pers.admin, + G_admin_admin( vic->guid ) ) ) { - ADMP( "^3!revert: ^7to avoid flooding, can only revert 25 builds at a time\n" ); - repeat = 25; + ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin" + " level than you\n", command ) ); + return qfalse; } - for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 ) + if( vic->denyBuild ) { - if( !ptr ) - break; // run out of bhist - if( !reached && ID ) + if( !Q_stricmp( command, "denybuild" ) ) { - if( ptr->ID == ID ) - reached = qtrue; - else - { - prev = ptr; - ptr = ptr->next; - repeat++; - continue; - } + ADMP( "^3denybuild: ^7player already has no building rights\n" ); + return qfalse; } - if( ( team != PTE_NONE && - team != BG_FindTeamForBuildable( ptr->buildable ) ) || - ( builder && builder != ptr->ent )) + vic->denyBuild = qfalse; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You've regained your building rights\"" ); + AP( va( + "print \"^3allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + else + { + if( !Q_stricmp( command, "allowbuild" ) ) { - // team doesn't match, so skip this ptr and reset prev - prev = ptr; - ptr = ptr->next; - // we don't want to count this one so counteract the decrement by the for - repeat++; - continue; + ADMP( "^3allowbuild: ^7player already has building rights\n" ); + return qfalse; } - // get the ent's current or last recorded name - if( ptr->ent ) + vic->denyBuild = qtrue; + if( vic->slot > -1 ) { - if( ptr->ent->client ) - name = ptr->ent->client->pers.netname; - else - name = ""; // non-client actions + level.clients[ vic->slot ].ps.stats[ STAT_BUILDABLE ] = BA_NONE; + CPx( vic->slot, "cp \"^1You've lost your building rights\"" ); } - else - name = ptr->name; - bname = BG_FindHumanNameForBuildable( ptr->buildable ); - action = ""; - switch( ptr->fate ) - { - case BF_BUILT: - action = "^2build^7"; - for( j = MAX_CLIENTS, targ = g_entities + j; - j < level.num_entities; j++, targ++ ) - { - // easy checks first to save time - if( targ->s.eType != ET_BUILDABLE ) - continue; - if( targ->s.modelindex != ptr->buildable ) - continue; - VectorSubtract( targ->s.pos.trBase, ptr->origin, dist ); -#define FIND_BUILDABLE_TOLERANCE 5 - if( VectorLength( dist ) > FIND_BUILDABLE_TOLERANCE ) - continue; // number is somewhat arbitrary, watch for false pos/neg - // if we didn't continue then it's this one, unlink it but we can't - // free it yet, because the markdecon buildables might not place - trap_UnlinkEntity( targ ); - break; - } - // if there are marked buildables to replace, and we aren't overriding - // space check, check they can fit before acting - if( ptr->marked && !force ) - { - for( mark = ptr->marked; mark; mark = mark->marked ) - if( !G_RevertCanFit( mark ) ) - { - trap_LinkEntity( targ ); // put it back, we failed - // scariest sprintf ever: - Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!", - ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "", - ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "", - ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "", - ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" ); - ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would conflict with " - "another buildable, use ^3!revert %s ^7to override\n", action, argbuf ) ); - return qfalse; - } - } - // Prevent teleport glitch when reverting an occupied hovel - if( targ->s.modelindex == BA_A_HOVEL && - targ->active ) - { - gentity_t *builder = targ->builder; - vec3_t newOrigin; - vec3_t newAngles; - - VectorCopy( targ->s.angles, newAngles ); - newAngles[ ROLL ] = 0; - - VectorCopy( targ->s.origin, newOrigin ); - VectorMA( newOrigin, 1.0f, targ->s.origin2, newOrigin ); - - //prevent lerping - builder->client->ps.eFlags ^= EF_TELEPORT_BIT; - builder->client->ps.eFlags &= ~EF_NODRAW; - G_UnlaggedClear( builder ); - - G_SetOrigin( builder, newOrigin ); - VectorCopy( newOrigin, builder->client->ps.origin ); - G_SetClientViewAngle( builder, newAngles ); - - //client leaves hovel - builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; - } - - // if we haven't returned yet then we're good to go, free it - G_FreeEntity( targ ); - // put the marked buildables back and mark them again - if( ptr->marked ) // there may be a more efficient way of doing this - { - for( mark = ptr->marked; mark; mark = mark->marked ) - G_SpawnRevertedBuildable( mark, qtrue ); - } - break; - case BF_DECONNED: - if( !action[ 0 ] ) action = "^3deconstruction^7"; - case BF_TEAMKILLED: - if( !action[ 0 ] ) action ="^1TEAMKILL^7"; - case BF_DESTROYED: - if( !action[ 0 ] ) action = "destruction"; - // if we're not overriding and the replacement can't fit, as before - if( !force && !G_RevertCanFit( ptr ) ) - { - Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!", - ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "", - ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "", - ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "", - ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" ); - ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would " - "conflict with another buildable, use ^3!revert %s ^7to override\n", - action, argbuf ) ); - return qfalse; - } - // else replace it but don't mark it ( it might have been marked before - // but it isn't that important ) - G_SpawnRevertedBuildable( ptr, qfalse ); - break; - default: - // if this happens something has gone wrong - ADMP( "^3!revert: ^7incomplete or corrupted build log entry\n" ); - /* quarantine and dispose of the log, it's dangerous - trap_Cvar_Set( "g_buildLogMaxLength", "0" ); - G_CountBuildLog( ); - */ - return qfalse; - } - if( j == level.num_entities ) - { - ADMP( va( "^3!revert: ^7could not find logged buildable #%d\n", ptr->ID )); - prev = ptr; - ptr = ptr->next; - continue; - } - // this is similar to the buildlog stuff - if( BG_FindUniqueTestForBuildable( ptr->buildable ) ) - article = "the"; - else if( strchr( "aeiouAEIOU", *bname ) ) - article = "an"; - else - article = "a"; - AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s", - action, article, bname ) ); - matchlen++; - // remove the reverted entry - // ptr moves on, prev just readjusts ->next unless it is about to be - // freed, in which case it is forced to move on too - tmp = ptr; - if( ptr == level.buildHistory ) - prev = level.buildHistory = ptr = ptr->next; - else - prev->next = ptr = ptr->next; - G_Free( tmp ); - } - if( ID && !reached ) - { - ADMP( "^3!revert: ^7no buildlog entry with that ID\n" ); - return qfalse; - } - - if( !matchlen ) - { - ADMP( "^3!revert: ^7no log entries match those criteria\n" ); - return qfalse; - } - else - { - ADMP( va( "^3!revert: ^7reverted %d buildlog events\n", matchlen ) ); + AP( va( + "print \"^3denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); } - + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid, + vic->name[ vic->nameOffset ] ) ); return qtrue; } -void G_Unescape( char *input, char *output, int len ); -qboolean G_StringReplaceCvars( char *input, char *output, int len ); - -qboolean G_admin_info( gentity_t *ent, int skiparg ) +qboolean G_admin_listadmins( gentity_t *ent ) { - fileHandle_t infoFile; - int length; - char filename[ MAX_OSPATH ], message[ MAX_STRING_CHARS ]; - if( G_SayArgc() == 2 + skiparg ) - G_SayArgv( 1 + skiparg, filename, sizeof( filename ) ); - else if( G_SayArgc() == 1 + skiparg ) - Q_strncpyz( filename, "default", sizeof( filename ) ); - else - { - ADMP( "^3!info: ^7usage: ^3!info ^7(^5subject^7)\n" ); - return qfalse; - } - Com_sprintf( filename, sizeof( filename ), "info/info-%s.txt", filename ); - length = trap_FS_FOpenFile( filename, &infoFile, FS_READ ); - if( length <= 0 || !infoFile ) + int i; + char search[ MAX_NAME_LENGTH ] = {""}; + char s[ MAX_NAME_LENGTH ] = {""}; + int start = MAX_CLIENTS; + + if( trap_Argc() == 3 ) { - trap_FS_FCloseFile( infoFile ); - ADMP( "^3!info: ^7no relevant information is available\n" ); - return qfalse; + trap_Argv( 2, s, sizeof( s ) ); + start = atoi( s ); } - else + if( trap_Argc() > 1 ) { - int i; - char *cr; - trap_FS_Read( message, sizeof( message ), infoFile ); - if( length < sizeof( message ) ) - message[ length ] = '\0'; + trap_Argv( 1, s, sizeof( s ) ); + i = 0; + if( trap_Argc() == 2 ) + { + i = s[ 0 ] == '-'; + for( ; isdigit( s[ i ] ); i++ ); + } + if( i && !s[ i ] ) + start = atoi( s ); else - message[ sizeof( message ) - 1 ] = '\0'; - trap_FS_FCloseFile( infoFile ); - // strip carriage returns for windows platforms - while( ( cr = strchr( message, '\r' ) ) ) - memmove( cr, cr + 1, strlen( cr + 1 ) + 1 ); -#define MAX_INFO_PARSE_LOOPS 100 - for( i = 0; i < MAX_INFO_PARSE_LOOPS && - G_StringReplaceCvars( message, message, sizeof( message ) ); i++ ); - G_Unescape( message, message, sizeof( message ) ); - if( i == MAX_INFO_PARSE_LOOPS ) - G_Printf( S_COLOR_YELLOW "WARNING: %s exceeds MAX_INFO_PARSE_LOOPS\n", filename ); - ADMP( va( "%s\n", message ) ); - return qtrue; + G_SanitiseString( s, search, sizeof( search ) ); } + + admin_listadmins( ent, start, search ); + return qtrue; } -void G_Unescape( char *input, char *output, int len ) +qboolean G_admin_listlayouts( gentity_t *ent ) { - // \n -> newline, \%c -> %c - // output is terminated at output[len - 1] - // it's OK for input to equal output, because our position in input is always - // equal or greater than our position in output - // however, if output is later in the same string as input, a crash is pretty - // much inevitable - int i, j; - for( i = j = 0; input[i] && j + 1 < len; i++, j++ ) - { - if( input[i] == '\\' ) + char list[ MAX_CVAR_VALUE_STRING ]; + char map[ MAX_QPATH ]; + int count = 0; + char *s; + char layout[ MAX_QPATH ] = { "" }; + size_t i = 0; + + if( trap_Argc( ) == 2 ) + trap_Argv( 1, map, sizeof( map ) ); + else + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + count = G_LayoutList( map, list, sizeof( list ) ); + ADMBP_begin( ); + ADMBP( va( "^3listlayouts:^7 %d layouts found for '%s':\n", count, map ) ); + s = &list[ 0 ]; + while( *s ) + { + if( *s == ' ' ) { - if( !input[++i] ) - { - output[j] = '\0'; - return; - } - else if( input[i] == 'n' ) - output[j] = '\n'; - else - output[j] = input[i]; + ADMBP( va ( " %s\n", layout ) ); + layout[ 0 ] = '\0'; + i = 0; } - else - output[j] = input[i]; + else if( i < sizeof( layout ) - 2 ) + { + layout[ i++ ] = *s; + layout[ i ] = '\0'; + } + s++; } - output[j] = '\0'; + if( layout[ 0 ] ) + ADMBP( va ( " %s\n", layout ) ); + ADMBP_end( ); + return qtrue; } -qboolean G_StringReplaceCvars( char *input, char *output, int len ) +qboolean G_admin_listplayers( gentity_t *ent ) { - int i, outNum = 0; - char cvarName[ 64 ], cvarValue[ MAX_CVAR_VALUE_STRING ]; - char *outputBuffer; - qboolean doneAnything = qfalse; - if( len <= 0 ) - return qfalse; - // use our own internal buffer in case output == input - outputBuffer = G_Alloc( len ); - len -= 1; // fit in a terminator - while( *input && outNum < len ) + int i, j; + gclient_t *p; + char c, t; // color and team letter + char *registeredname; + char lname[ MAX_NAME_LENGTH ]; + char muted, denied; + int colorlen; + char namecleaned[ MAX_NAME_LENGTH ]; + char name2cleaned[ MAX_NAME_LENGTH ]; + g_admin_level_t *l; + g_admin_level_t *d = G_admin_level( 0 ); + qboolean hint; + qboolean canset = G_admin_permission( ent, "setlevel" ); + + ADMBP_begin(); + ADMBP( va( "^3listplayers: ^7%d players connected:\n", + level.numConnectedClients ) ); + for( i = 0; i < level.maxclients; i++ ) { - if( *input == '\\' && input[1] && outNum < len - 1 ) + p = &level.clients[ i ]; + if( p->pers.connected == CON_DISCONNECTED ) + continue; + if( p->pers.connected == CON_CONNECTING ) { - outputBuffer[ outNum++ ] = *input++; - outputBuffer[ outNum++ ] = *input++; + t = 'C'; + c = COLOR_YELLOW; + } + else + { + t = toupper( *( BG_TeamName( p->pers.teamSelection ) ) ); + if( p->pers.teamSelection == TEAM_HUMANS ) + c = COLOR_CYAN; + else if( p->pers.teamSelection == TEAM_ALIENS ) + c = COLOR_RED; + else + c = COLOR_WHITE; } - else if( *input == '$' ) + + muted = p->pers.namelog->muted ? 'M' : ' '; + denied = p->pers.namelog->denyBuild ? 'B' : ' '; + + l = d; + registeredname = NULL; + hint = canset; + if( p->pers.admin ) { - doneAnything = qtrue; - input++; - if( *input == '{' ) - input++; - for( i = 0; *input && ( isalnum( *input ) || *input == '_' ) && - i < 63; i++ ) - cvarName[ i ] = *input++; - cvarName[ i ] = '\0'; - if( *input == '}' ) - input++; - trap_Cvar_VariableStringBuffer( cvarName, cvarValue, sizeof( cvarValue ) ); - if( cvarValue[ 0 ] ) + if( hint ) + hint = admin_higher( ent, &g_entities[ i ] ); + if( hint || !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) ) { - for( i = 0; cvarValue[ i ] && outNum < len; i++ ) - outputBuffer[ outNum++ ] = cvarValue[ i ]; + l = G_admin_level( p->pers.admin->level ); + G_SanitiseString( p->pers.netname, namecleaned, + sizeof( namecleaned ) ); + G_SanitiseString( p->pers.admin->name, + name2cleaned, sizeof( name2cleaned ) ); + if( Q_stricmp( namecleaned, name2cleaned ) ) + registeredname = p->pers.admin->name; } } - else - outputBuffer[ outNum++ ] = *input++; - } - outputBuffer[ outNum ] = '\0'; - Q_strncpyz( output, outputBuffer, len ); - G_Free( outputBuffer ); - return doneAnything; -} -/* -================ - G_admin_print + if( l ) + Q_strncpyz( lname, l->name, sizeof( lname ) ); - This function facilitates the ADMP define. ADMP() is similar to CP except - that it prints the message to the server console if ent is not defined. -================ -*/ -void G_admin_print( gentity_t *ent, char *m ) -{ - if( ent ) - trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) ); - else - { - char m2[ MAX_STRING_CHARS ]; - if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) ) + for( colorlen = j = 0; lname[ j ]; j++ ) { - G_DecolorString( m, m2 ); - G_Printf( m2 ); + if( Q_IsColorString( &lname[ j ] ) ) + colorlen += 2; } - else - G_Printf( m ); + + ADMBP( va( "%2i ^%c%c %s %s^7 %*s^7 ^1%c%c^7 %s^7 %s%s%s\n", + i, + c, + t, + p->pers.guidless ? "^1---" : va( "^7%-2i^2%c", l ? l->level : 0, hint ? '*' : ' ' ), + p->pers.alternateProtocol == 2 ? "^11" : p->pers.alternateProtocol == 1 ? "^3G" : " ", + admin_level_maxname + colorlen, + lname, + muted, + denied, + p->pers.netname, + ( registeredname ) ? "(a.k.a. " : "", + ( registeredname ) ? registeredname : "", + ( registeredname ) ? S_COLOR_WHITE ")" : "" ) ); + } + ADMBP_end(); + return qtrue; } -void G_admin_buffer_begin() +static qboolean ban_matchip( void *ban, const void *ip ) { - g_bfb[ 0 ] = '\0'; + return G_AddressCompare( &((g_admin_ban_t *)ban)->ip, (addr_t *)ip ) || + G_AddressCompare( (addr_t *)ip, &((g_admin_ban_t *)ban)->ip ); } - -void G_admin_buffer_end( gentity_t *ent ) +static qboolean ban_matchname( void *ban, const void *name ) { - ADMP( g_bfb ); -} + char match[ MAX_NAME_LENGTH ]; -void G_admin_buffer_print( gentity_t *ent, char *m ) -{ - // 1022 - strlen("print 64 \"\"") - 1 - if( strlen( m ) + strlen( g_bfb ) >= 1009 ) - { - ADMP( g_bfb ); - g_bfb[ 0 ] = '\0'; - } - Q_strcat( g_bfb, sizeof( g_bfb ), m ); + G_SanitiseString( ( (g_admin_ban_t *)ban )->name, match, sizeof( match ) ); + return strstr( match, (const char *)name ) != NULL; } - - -void G_admin_cleanup() +static void ban_out( void *ban, char *str ) { - int i = 0; + size_t i; + int colorlen1 = 0; + char duration[ MAX_DURATION_LENGTH ]; + char *d_color = S_COLOR_WHITE; + char date[ 11 ]; + g_admin_ban_t *b = ( g_admin_ban_t * )ban; + int t = trap_RealTime( NULL ); + char *made = b->made; - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - G_Free( g_admin_levels[ i ] ); - g_admin_levels[ i ] = NULL; - } - for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + for( i = 0; b->name[ i ]; i++ ) { - G_Free( g_admin_admins[ i ] ); - g_admin_admins[ i ] = NULL; + if( Q_IsColorString( &b->name[ i ] ) ) + colorlen1 += 2; } - for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) - { - G_Free( g_admin_bans[ i ] ); - g_admin_bans[ i ] = NULL; - } - for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) + + // only print out the the date part of made + date[ 0 ] = '\0'; + for( i = 0; *made && *made != ' ' && i < sizeof( date ) - 1; i++ ) + date[ i ] = *made++; + date[ i ] = 0; + + if( !b->expires || b->expires - t > 0 ) + G_admin_duration( b->expires ? b->expires - t : - 1, + duration, sizeof( duration ) ); + else { - G_Free( g_admin_commands[ i ] ); - g_admin_commands[ i ] = NULL; + Q_strncpyz( duration, "expired", sizeof( duration ) ); + d_color = S_COLOR_CYAN; } -} -qboolean G_admin_L0(gentity_t *ent, int skiparg ){ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ] = {""}; - char testname[ MAX_NAME_LENGTH ] = {""}; - char err[ MAX_STRING_CHARS ]; - qboolean numeric = qtrue; + Com_sprintf( str, MAX_STRING_CHARS, "%-*s %s%-15s " S_COLOR_WHITE "%-8s %s" + "\n \\__ %s%-*s " S_COLOR_WHITE "%s", + MAX_NAME_LENGTH + colorlen1 - 1, b->name, + ( strchr( b->ip.str, '/' ) ) ? S_COLOR_RED : S_COLOR_WHITE, + b->ip.str, + date, + b->banner, + d_color, + MAX_DURATION_LENGTH - 1, + duration, + b->reason ); +} +qboolean G_admin_showbans( gentity_t *ent ) +{ int i; - int id = -1; - gentity_t *vic; + int start = 1; + char filter[ MAX_NAME_LENGTH ] = {""}; + char name_match[ MAX_NAME_LENGTH ] = {""}; + qboolean ipmatch = qfalse; + addr_t ip; - if( G_SayArgc() < 2 + skiparg ) + if( trap_Argc() == 3 ) { - ADMP( "^3!L0: ^7usage: !L0 [name|slot#|admin#]\n" ); - return qfalse; + trap_Argv( 2, filter, sizeof( filter ) ); + start = atoi( filter ); } - G_SayArgv( 1 + skiparg, testname, sizeof( testname ) ); - G_SanitiseString( testname, name, sizeof( name ) ); - for( i = 0; i < sizeof( name ) && name[ i ] ; i++ ) + if( trap_Argc() > 1 ) { - if( name[ i ] < '0' || name[ i ] > '9' ) - { - numeric = qfalse; - break; - } + trap_Argv( 1, filter, sizeof( filter ) ); + i = 0; + if( trap_Argc() == 2 ) + for( i = filter[ 0 ] == '-'; isdigit( filter[ i ] ); i++ ); + if( !filter[ i ] ) + start = atoi( filter ); + else if( !( ipmatch = G_AddressParse( filter, &ip ) ) ) + G_SanitiseString( filter, name_match, sizeof( name_match ) ); } - if( numeric ) - { - id = atoi( name ); - } - else - { - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!L0: ^7%s\n", err ) ); - return qfalse; - } - id = pids[ 0 ]; - } + admin_search( ent, "showbans", "bans", + ipmatch ? ban_matchip : ban_matchname, + ban_out, g_admin_bans, + ipmatch ? (void * )&ip : (void *)name_match, + start, 1, MAX_ADMIN_SHOWBANS ); + return qtrue; +} - if (id >= 0 && id < level.maxclients) +qboolean G_admin_adminhelp( gentity_t *ent ) +{ + g_admin_command_t *c; + if( trap_Argc() < 2 ) { - vic = &g_entities[ id ]; - if( !vic || !(vic->client) || vic->client->pers.connected != CON_CONNECTED ) + size_t i; + int count = 0; + + ADMBP_begin(); + for( i = 0; i < adminNumCmds; i++ ) { - ADMP( "^3!L0:^7 no one connected by that slot number\n" ); - return qfalse; + if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) + { + ADMBP( va( "^3%-12s", g_admin_cmds[ i ].keyword ) ); + count++; + // show 6 commands per line + if( count % 6 == 0 ) + ADMBP( "\n" ); + } } - - if( G_admin_level( vic ) != 1 ) + for( c = g_admin_commands; c; c = c->next ) { - ADMP( "^3!L0:^7 intended victim is not level 1\n" ); - return qfalse; + if( !G_admin_permission( ent, c->flag ) ) + continue; + ADMBP( va( "^3%-12s", c->command ) ); + count++; + // show 6 commands per line + if( count % 6 == 0 ) + ADMBP( "\n" ); } + if( count % 6 ) + ADMBP( "\n" ); + ADMBP( va( "^3adminhelp: ^7%i available commands\n", count ) ); + ADMBP( "run adminhelp [^3command^7] for adminhelp with a specific command.\n" ); + ADMBP_end(); + + return qtrue; } - else if (id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS - && g_admin_admins[ id - MAX_CLIENTS ] ) + else { - if( g_admin_admins[ id - MAX_CLIENTS ]->level != 1 ) + // adminhelp param + char param[ MAX_ADMIN_CMD_LEN ]; + g_admin_cmd_t *admincmd; + qboolean denied = qfalse; + + trap_Argv( 1, param, sizeof( param ) ); + ADMBP_begin(); + if( ( c = G_admin_command( param ) ) ) { - ADMP( "^3!L0:^7 intended victim is not level 1\n" ); - return qfalse; + if( G_admin_permission( ent, c->flag ) ) + { + ADMBP( va( "^3adminhelp: ^7help for '%s':\n", c->command ) ); + ADMBP( va( " ^3Description: ^7%s\n", c->desc ) ); + ADMBP( va( " ^3Syntax: ^7%s\n", c->command ) ); + ADMBP( va( " ^3Flag: ^7'%s'\n", c->flag ) ); + ADMBP_end( ); + return qtrue; + } + denied = qtrue; + } + if( ( admincmd = G_admin_cmd( param ) ) ) + { + if( G_admin_permission( ent, admincmd->flag ) ) + { + ADMBP( va( "^3adminhelp: ^7help for '%s':\n", admincmd->keyword ) ); + ADMBP( va( " ^3Description: ^7%s\n", admincmd->function ) ); + ADMBP( va( " ^3Syntax: ^7%s %s\n", admincmd->keyword, + admincmd->syntax ) ); + ADMBP( va( " ^3Flag: ^7'%s'\n", admincmd->flag ) ); + ADMBP_end(); + return qtrue; + } + denied = qtrue; } + ADMBP( va( "^3adminhelp: ^7%s '%s'\n", + denied ? "you do not have permission to use" : "no help found for", + param ) ); + ADMBP_end( ); + return qfalse; } - else +} + +qboolean G_admin_admintest( gentity_t *ent ) +{ + g_admin_level_t *l; + + if( !ent ) { - ADMP( "^3!L0:^7 no match. use !listplayers or !listadmins to " - "find an appropriate number to use instead of name.\n" ); - return qfalse; + ADMP( "^3admintest: ^7you are on the console.\n" ); + return qtrue; } - trap_SendConsoleCommand( EXEC_APPEND, va( "!setlevel %d 0;", id ) ); + l = G_admin_level( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ); + AP( va( "print \"^3admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"", + ent->client->pers.netname, + l ? l->level : 0, + l ? "(" : "", + l ? l->name : "", + l ? ")" : "" ) ); return qtrue; } -qboolean G_admin_L1(gentity_t *ent, int skiparg ){ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; - int minargc; - - minargc = 2 + skiparg; +qboolean G_admin_allready( gentity_t *ent ) +{ + int i = 0; + gclient_t *cl; - if( G_SayArgc() < minargc ) - { - ADMP( "^3!L1: ^7usage: !L1 [name]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - reason = G_SayConcatArgs( 2 + skiparg ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) + if( !level.intermissiontime ) { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!L1: ^7%s\n", err ) ); + ADMP( "^3allready: ^7this command is only valid during intermission\n" ); return qfalse; } - if( G_admin_level(&g_entities[ pids[ 0 ] ] )>0 ) + + for( i = 0; i < g_maxclients.integer; i++ ) { - ADMP( "^3!L1: ^7Sorry, but that person is already higher than level 0.\n" ); - return qfalse; + cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->pers.teamSelection == TEAM_NONE ) + continue; + + cl->readyToExit = qtrue; } - - trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d 1;", pids[ 0 ] ) ); + AP( va( "print \"^3allready:^7 %s^7 says everyone is READY now\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); return qtrue; } -qboolean G_admin_invisible( gentity_t *ent, int skiparg ) +qboolean G_admin_endvote( gentity_t *ent ) { - if( !ent ) + char teamName[ sizeof( "spectators" ) ] = {"s"}; + char command[ MAX_ADMIN_CMD_LEN ]; + team_t team; + qboolean cancel; + const char *msg; + + trap_Argv( 0, command, sizeof( command ) ); + cancel = !Q_stricmp( command, "cancelvote" ); + if( trap_Argc() == 2 ) + trap_Argv( 1, teamName, sizeof( teamName ) ); + team = G_TeamFromString( teamName ); + if( team == NUM_TEAMS ) { - ADMP( "!invisible: console can not become invisible.\n" ); + ADMP( va( "^3%s: ^7invalid team '%s'\n", command, teamName ) ); return qfalse; } - - if ( ent->client->sess.invisible != qtrue ) + msg = va( "print \"^3%s: ^7%s^7 decided that everyone voted %s\n\"", + command, ( ent ) ? ent->client->pers.netname : "console", + cancel ? "No" : "Yes" ); + if( !level.voteTime[ team ] ) { - // Make the player invisible - G_ChangeTeam( ent, PTE_NONE ); - ent->client->sess.invisible = qtrue; - ClientUserinfoChanged( ent->client->pers.connection->clientNum, qfalse ); - G_admin_namelog_update( ent->client, qtrue ); - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " disconnected\n\"", ent->client->pers.netname ) ); + ADMP( va( "^3%s: ^7no vote in progress\n", command ) ); + return qfalse; } + admin_log( BG_TeamName( team ) ); + level.voteNo[ team ] = cancel ? level.numVotingClients[ team ] : 0; + level.voteYes[ team ] = cancel ? 0 : level.numVotingClients[ team ]; + G_CheckVote( team ); + if( team == TEAM_NONE ) + AP( msg ); else - { - // Make the player visible - ent->client->sess.invisible = qfalse; - ClientUserinfoChanged( ent->client->pers.connection->clientNum, qfalse ); - G_admin_namelog_update( ent->client, qfalse ); - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", ent->client->pers.netname ) ); - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", ent->client->pers.netname ) ); - } + G_TeamCommand( team, msg ); return qtrue; } -qboolean G_admin_decon( gentity_t *ent, int skiparg ) +qboolean G_admin_spec999( gentity_t *ent ) { - int i = 0, j = 0, repeat = 24, pids[ MAX_CLIENTS ], len, matchlen = 0; - pTeam_t team = PTE_NONE; - qboolean force = qfalse, reached = qfalse; - gentity_t *builder = NULL, *targ; - buildHistory_t *ptr, *tmp, *mark, *prev; - vec3_t dist; - char arg[ 64 ], err[ MAX_STRING_CHARS ], *name, *bname, *action, *article, *reason; - len = G_CountBuildLog( ); - - if( !len ) - { - ADMP( "^3!decon: ^7no build log found, aborting...\n" ); - return qfalse; + int i; + gentity_t *vic; + + for( i = 0; i < level.maxclients; i++ ) + { + vic = &g_entities[ i ]; + if( !vic->client ) + continue; + if( vic->client->pers.connected != CON_CONNECTED ) + continue; + if( vic->client->pers.teamSelection == TEAM_NONE ) + continue; + if( vic->client->ps.ping == 999 ) + { + G_ChangeTeam( vic, TEAM_NONE ); + AP( va( "print \"^3spec999: ^7%s^7 moved %s^7 to spectators\n\"", + ( ent ) ? ent->client->pers.netname : "console", + vic->client->pers.netname ) ); + } } + return qtrue; +} + +qboolean G_admin_transform( gentity_t *ent ) +{ + int pid; + char name[ MAX_NAME_LENGTH ]; + char modelname[ MAX_NAME_LENGTH ]; + char skin[ MAX_NAME_LENGTH ]; + char err[ MAX_STRING_CHARS ]; + char userinfo[ MAX_INFO_STRING ]; + gentity_t *victim = NULL; + int i; + qboolean found = qfalse; - if( G_SayArgc( ) < 2 + skiparg ) + if (trap_Argc() < 3) { - ADMP( "^3!decon: ^7usage: !decon (^5name|num^7)\n" ); + ADMP("^3transform: ^7usage: transform [name|slot#] [model] \n"); return qfalse; } - G_SayArgv( 1 + skiparg, arg, sizeof( arg ) ); - if( G_ClientNumbersFromString( arg, pids ) != 1 ) + trap_Argv(1, name, sizeof(name)); + trap_Argv(2, modelname, sizeof(modelname)); + + strcpy(skin, "default"); + if (trap_Argc() >= 4) { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!decon: ^7%s\n", err ) ); - return qfalse; + trap_Argv(1, skin, sizeof(skin)); } - builder = g_entities + *pids; - if( builder->client->sess.invisible == qtrue ) + pid = G_ClientNumberFromString(name, err, sizeof(err)); + if (pid == -1) { - ADMP( va( "^3!decon: ^7no connected player by the name or slot #\n" ) ); + ADMP(va("^3transform: ^7%s", err)); return qfalse; } - if( !admin_higher( ent, builder ) ) + victim = &g_entities[ pid ]; + if (victim->client->pers.connected != CON_CONNECTED) { - ADMP( "^3!decon: ^7sorry, but your intended victim has a higher admin" - "level than you\n"); + ADMP("^3transform: ^7sorry, but your intended victim is still connecting\n"); return qfalse; } - for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 ) + for ( i = 0; i < level.playerModelCount; i++ ) { - if( !ptr ) - break; - if( builder && builder != ptr->ent ) - { - // team doesn't match, so skip this ptr and reset prev - prev = ptr; - ptr = ptr->next; - // we don't want to count this one so counteract the decrement by the for - repeat++; - continue; - } - // get the ent's current or last recorded name - if( ptr->ent ) - { - if( ptr->ent->client ) - name = ptr->ent->client->pers.netname; - else - name = ""; // non-client actions - } - else - name = ptr->name; - bname = BG_FindHumanNameForBuildable( ptr->buildable ); - action = ""; - switch( ptr->fate ) + if ( !strcmp(modelname, level.playerModel[i]) ) { - case BF_BUILT: - prev = ptr; - ptr = ptr->next; - repeat++; - continue; - case BF_DESTROYED: - prev = ptr; - ptr = ptr->next; - repeat++; - case BF_DECONNED: - if( !action[0] ) action = "^3deconstruction^7"; - case BF_TEAMKILLED: - if( !action[0] ) action = "^1TEAMKILL^7"; - // if we're not overriding and the replacement can't fit, as before - if( !G_RevertCanFit( ptr ) ) - { - prev = ptr; - ptr = ptr->next; - repeat++; - continue; - } - // else replace it but don't mark it ( it might have been marked before - // but it isn't that important ) - G_SpawnRevertedBuildable( ptr, qfalse ); - break; - default: - // if this happens something has gone wrong - ADMP( "^3!decon: ^7incomplete or corrupted build log entry\n" ); - /* quarantine and dispose of the log, it's dangerous - trap_Cvar_Set( "g_buildLogMaxLength", "0" ); - G_CountBuildLog( ); - */ - return qfalse; + found = qtrue; + break; } - // this is similar to the buildlog stuff - if( BG_FindUniqueTestForBuildable( ptr->buildable ) ) - article = "the"; - else if( strchr( "aeiouAEIOU", *bname ) ) - article = "an"; - else - article = "a"; - AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s", - action, article, bname ) ); - matchlen++; - // remove the reverted entry - // ptr moves on, prev just readjusts ->next unles it is about to be - // freed, in which case it is forced to move on too - tmp = ptr; - if( ptr == level.buildHistory ) - prev = level.buildHistory = ptr = ptr->next; - else - prev->next = ptr = ptr->next; - G_Free( tmp ); } - if( !matchlen ) + if (!found) { - ADMP( "^3!decopn: ^7This user doesn't seem to have deconned anything...\n" ); + ADMP(va("^3transform: ^7no matching model %s\n", modelname)); return qfalse; } - ADMP( va( "^3!decon: ^7reverted %d buildlog events\n", matchlen ) ); - admin_create_ban( ent, - builder->client->pers.netname, - builder->client->pers.guid, - builder->client->pers.ip, G_admin_parse_time( g_deconBanTime.string ), - ( *reason ) ? reason : "^1Decon" ); - if( g_admin.string[ 0 ] ) - admin_writeconfig(); - - trap_SendServerCommand( pids[ 0 ], - va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", - ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", - ( *reason ) ? reason : "^1Decon" ) ); + trap_GetUserinfo(pid, userinfo, sizeof(userinfo)); + AP( va("print \"^3transform: ^7%s^7 has been changed into %s^7 by %s\n\"", + victim->client->pers.netname, modelname, + (ent ? ent->client->pers.netname : "console")) ); - trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s", - ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console", - ( *reason ) ? reason : "^1Decon" ) ); + Info_SetValueForKey( userinfo, "model", modelname ); + Info_SetValueForKey( userinfo, "skin", GetSkin(modelname, skin)); + Info_SetValueForKey( userinfo, "voice", modelname ); + trap_SetUserinfo( pid, userinfo ); + ClientUserinfoChanged( pid, qtrue ); return qtrue; } -qboolean G_admin_setdevmode( gentity_t *ent, int skiparg ) +qboolean G_admin_rename( gentity_t *ent ) { - char str[ 5 ]; + int pid; + char name[ MAX_NAME_LENGTH ]; + char newname[ MAX_NAME_LENGTH ]; + char err[ MAX_STRING_CHARS ]; + char userinfo[ MAX_INFO_STRING ]; + gentity_t *victim = NULL; - if( G_SayArgc() != 2 + skiparg ) + if( trap_Argc() < 3 ) { - ADMP( "^3!setdevmode: ^7usage: !setdevmode [on|off]\n" ); + ADMP( "^3rename: ^7usage: rename [name] [newname]\n" ); return qfalse; } - G_SayArgv( 1 + skiparg, str, sizeof( str ) ); - - if( !Q_stricmp( str, "on" ) ) + trap_Argv( 1, name, sizeof( name ) ); + Q_strncpyz( newname, ConcatArgs( 2 ), sizeof( newname ) ); + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) { - if( g_cheats.integer ) - { - ADMP( "^3!setdevmode: ^7developer mode is already on\n" ); - return qfalse; - } - trap_Cvar_Set( "sv_cheats", "1" ); - trap_Cvar_Update( &g_cheats ); - AP( va( "print \"^3!setdevmode: ^7%s ^7has switched developer mode on\n\"", - ent ? G_admin_adminPrintName( ent ) : "console" ) ); + ADMP( va( "^3rename: ^7%s", err ) ); + return qfalse; } - else if( !Q_stricmp( str, "off" ) ) + victim = &g_entities[ pid ]; + if( !admin_higher( ent, victim ) ) { - if( !g_cheats.integer ) - { - ADMP( "^3!setdevmode: ^7developer mode is already off\n" ); - return qfalse; - } - trap_Cvar_Set( "sv_cheats", "0" ); - trap_Cvar_Update( &g_cheats ); - AP( va( "print \"^3!setdevmode: ^7%s ^7has switched developer mode off\n\"", - ent ? G_admin_adminPrintName( ent ) : "console" ) ); + ADMP( "^3rename: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; } - else + if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) ) { - ADMP( "^3!setdevmode: ^7usage: !setdevmode [on|off]\n" ); + ADMP( va( "^3rename: ^7%s\n", err ) ); return qfalse; } - + if( victim->client->pers.connected != CON_CONNECTED ) + { + ADMP( "^3rename: ^7sorry, but your intended victim is still connecting\n" ); + return qfalse; + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, + victim->client->pers.guid, victim->client->pers.netname ) ); + admin_log( newname ); + trap_GetUserinfo( pid, userinfo, sizeof( userinfo ) ); + AP( va( "print \"^3rename: ^7%s^7 has been renamed to %s^7 by %s\n\"", + victim->client->pers.netname, + newname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + Info_SetValueForKey( userinfo, "name", newname ); + trap_SetUserinfo( pid, userinfo ); + ClientUserinfoChanged( pid, qtrue ); return qtrue; } -qboolean G_admin_hstage( gentity_t *ent, int skiparg ) +qboolean G_admin_restart( gentity_t *ent ) { + char layout[ MAX_CVAR_VALUE_STRING ] = { "" }; + char teampref[ MAX_STRING_CHARS ] = { "" }; + char map[ MAX_CVAR_VALUE_STRING ]; + int i; + gclient_t *cl; - char lvl_chr[ MAX_STRING_CHARS ]; - int minargc; - int lvl; + if( trap_Argc( ) > 1 ) + { + char map[ MAX_QPATH ]; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + trap_Argv( 1, layout, sizeof( layout ) ); + + // Figure out which argument is which + if( Q_stricmp( layout, "keepteams" ) && + Q_stricmp( layout, "keepteamslock" ) && + Q_stricmp( layout, "switchteams" ) && + Q_stricmp( layout, "switchteamslock" ) ) + { + if( G_LayoutExists( map, layout ) ) + { + trap_Cvar_Set( "g_nextLayout", layout ); + } + else + { + ADMP( va( "^3restart: ^7layout '%s' does not exist\n", layout ) ); + return qfalse; + } + } + else + { + layout[ 0 ] = '\0'; + trap_Argv( 1, teampref, sizeof( teampref ) ); + } + } + + if( trap_Argc( ) > 2 ) + trap_Argv( 2, teampref, sizeof( teampref ) ); + + admin_log( layout ); + admin_log( teampref ); + + if( !Q_stricmpn( teampref, "keepteams", 9 ) ) + { + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->pers.teamSelection == TEAM_NONE ) + continue; + cl->sess.restartTeam = cl->pers.teamSelection; + } + } + else if( !Q_stricmpn( teampref, "switchteams", 11 ) ) + { + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; - minargc = 2 + skiparg; + if( cl->pers.connected != CON_CONNECTED ) + continue; - if( G_SayArgc() < minargc ) - { - ADMP( "^3!hstage: ^7hstage: !hstage [#]\n" ); - return qfalse; + if( cl->pers.teamSelection == TEAM_HUMANS ) + cl->sess.restartTeam = TEAM_ALIENS; + else if(cl->pers.teamSelection == TEAM_ALIENS ) + cl->sess.restartTeam = TEAM_HUMANS; + } } - G_SayArgv( 1 + skiparg, lvl_chr, sizeof( lvl_chr ) ); - - lvl = atoi(lvl_chr); + if( !Q_stricmp( teampref, "switchteamslock" ) || + !Q_stricmp( teampref, "keepteamslock" ) ) + trap_Cvar_Set( "g_lockTeamsAtStart", "1" ); - lvl -= 1; - trap_SendConsoleCommand( EXEC_APPEND, va( "g_humanStage %i", lvl ) ); + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + G_MapConfigs( map ); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + AP( va( "print \"^3restart: ^7map restarted by %s %s %s\n\"", + ( ent ) ? ent->client->pers.netname : "console", + ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "", + ( teampref[ 0 ] ) ? va( "^7(with teams option: '%s^7')", teampref ) : "" ) ); return qtrue; - } -qboolean G_admin_astage( gentity_t *ent, int skiparg ) +qboolean G_admin_nextmap( gentity_t *ent ) { + if( level.exited ) + return qfalse; + AP( va( "print \"^3nextmap: ^7%s^7 decided to load the next map\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + level.lastWin = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "Evacuation" ); + LogExit( va( "nextmap was run by %s", + ( ent ) ? ent->client->pers.netname : "console" ) ); + return qtrue; +} - char lvl_chr[ MAX_STRING_CHARS ]; - int minargc; - int lvl; +qboolean G_admin_setnextmap( gentity_t *ent ) +{ + int argc = trap_Argc(); + char map[ MAX_QPATH ]; + char layout[ MAX_QPATH ]; + if( argc < 2 || argc > 3 ) + { + ADMP( "^3setnextmap: ^7usage: setnextmap [map] (layout)\n" ); + return qfalse; + } - minargc = 2 + skiparg; + trap_Argv( 1, map, sizeof( map ) ); - if( G_SayArgc() < minargc ) + if( !G_MapExists( map ) ) { - ADMP( "^3!astage: ^7astage: !astage [#]\n" ); + ADMP( va( "^3setnextmap: ^7map '%s' does not exist\n", map ) ); return qfalse; } - G_SayArgv( 1 + skiparg, lvl_chr, sizeof( lvl_chr ) ); + if( argc > 2 ) + { + trap_Argv( 2, layout, sizeof( layout ) ); + + if( !G_LayoutExists( map, layout ) ) + { + ADMP( va( "^3setnextmap: ^7layout '%s' does not exist for map '%s'\n", layout, map ) ); + return qfalse; + } - lvl = atoi(lvl_chr); + trap_Cvar_Set( "g_nextLayout", layout ); + } + else + trap_Cvar_Set( "g_nextLayout", "" ); - lvl -= 1; - trap_SendConsoleCommand( EXEC_APPEND, va( "g_alienStage %i", lvl ) ); + trap_Cvar_Set( "g_nextMap", map ); + AP( va( "print \"^3setnextmap: ^7%s^7 has set the next map to '%s'%s\n\"", + ( ent ) ? ent->client->pers.netname : "console", map, + argc > 2 ? va( " with layout '%s'", layout ) : "" ) ); return qtrue; - } -qboolean G_admin_bubble( gentity_t *ent, int skiparg ) +static qboolean namelog_matchip( void *namelog, const void *ip ) { - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; - gentity_t *vic; - - if(g_Bubbles.integer) - { - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( "^3!bubble: ^7usage: !bubble [name|slot#]\n" ); - return qfalse; - } - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!bubble: ^7%s\n", err ) ); - return qfalse; - } - vic = &g_entities[ pids[ 0 ] ]; - if(vic->client->sess.invisible == qtrue) - { - ADMP( va( "^3!bubble: ^7no connected player by that name or slot #\n" ) ); - return qfalse; - } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( "^3!bubble: ^7sorry, but your intended victim has a higher admin" - " level than you\n" ); - return qfalse; + int i; + namelog_t *n = (namelog_t *)namelog; + for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) + { + if( G_AddressCompare( &n->ip[ i ], (addr_t *)ip ) || + G_AddressCompare( (addr_t *)ip, &n->ip[ i ] ) ) + return qtrue; } + return qfalse; +} +static qboolean namelog_matchname( void *namelog, const void *name ) +{ + char match[ MAX_NAME_LENGTH ]; + int i; + namelog_t *n = (namelog_t *)namelog; + for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ ) + { + G_SanitiseString( n->name[ i ], match, sizeof( match ) ); + if( strstr( match, (const char *)name ) ) + return qtrue; + } + return qfalse; +} +static void namelog_out( void *namelog, char *str ) +{ + namelog_t *n = (namelog_t *)namelog; + char *p = str; + int l, l2 = MAX_STRING_CHARS, i; + const char *scolor; - - if( vic->client->pers.bubbleTime ) - vic->client->pers.bubbleTime = 0; - else - vic->client->pers.bubbleTime = level.time + 500; - - AP( va( "print \"^3!bubble: ^7bubbles %s for %s^7 by %s\n\"", - ( vic->client->pers.bubbleTime ) ? "enabled" : "disabled", - vic->client->pers.netname, - ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + if( n->slot > -1 ) + { + scolor = S_COLOR_YELLOW; + l = Q_snprintf( p, l2, "%s%-2d", scolor, n->slot ); + p += l; + l2 -= l; } else { - ADMP( "^3!bubble: ^7sorry, but bubbles have been disabled on this server.\n" ); - return qfalse; + *p++ = '-'; + *p++ = ' '; + *p = '\0'; + l2 -= 2; + scolor = S_COLOR_WHITE; } - return qtrue; -} + for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) + { + l = Q_snprintf( p, l2, " %s", n->ip[ i ].str ); + p += l; + l2 -= l; + } -qboolean G_admin_scrim(gentity_t *ent, int skiparg ) -{ - char state[5]; - - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( "^3!scrim: ^7usage: !scrim [on|off]\n" ); - return qfalse; - } - - G_SayArgv( 1 + skiparg, state, sizeof( state ) ); - - if( !Q_stricmp(state, "on") ) - { - if( g_scrimMode.integer != 0 ) - { - ADMP( "^3!scrim: ^7scrim mode is already enabled.\n" ); - return qfalse; - } - AP( va( "print \"^3!scrim: ^7%s ^7turned scrim mode ^2on^7\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - trap_Cvar_Set( "g_scrimMode", "1" ); - } - else if( !Q_stricmp(state, "off") ) - { - if( g_scrimMode.integer == 0 ) - { - ADMP( "^3!scrim: ^7scrim mode is already disabled.\n" ); - return qfalse; - } - AP( va( "print \"^3!scrim: ^7%s ^7turned scrim mode ^1off^7\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - trap_Cvar_Set( "g_scrimMode", "0" ); - - } else { - ADMP( "^3!scrim: ^7usage: !scrim [on|off]\n" ); - return qfalse; - } - - return qtrue; + for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ ) + { + l = Q_snprintf( p, l2, " '" S_COLOR_WHITE "%s%s'%s", n->name[ i ], scolor, + i == n->nameOffset ? "*" : "" ); + p += l; + l2 -= l; + } } - -qboolean G_admin_give(gentity_t *ent, int skiparg) +qboolean G_admin_namelog( gentity_t *ent ) { - char arg_name_raw[MAX_NAME_LENGTH]; - char arg_name[MAX_NAME_LENGTH]; - char arg_amount[30]; - int target_id, amount; - gentity_t *target; - const char *currency; - - if (G_SayArgc() < 3 + skiparg) { - ADMP("^3!give: ^7usage: !give [player] [amount]\n"); - return qfalse; - } - - G_SayArgv(1 + skiparg, arg_name_raw, sizeof(arg_name_raw)); - G_SanitiseString(arg_name_raw, arg_name, sizeof(arg_name)); - - if (is_numeric(arg_name)) { - target_id = atoi(arg_name); - - if (target_id < 0 || target_id >= MAX_CLIENTS) { - ADMP(va("^3!give: ^7invalid client number\n")); - return qfalse; - } - } else { - int pids[ MAX_CLIENTS ]; - - if (G_ClientNumbersFromString(arg_name, pids) != 1) { - char error[MAX_STRING_CHARS]; - - G_MatchOnePlayer(pids, error, sizeof(error)); - ADMP(va("^3!give: ^7%s\n", error)); - return qfalse; - } - - target_id = pids[0]; - } - - - target = g_entities + target_id; - - if (!target->client || - target->client->pers.connected != CON_CONNECTED) { - invalid_target: - ADMP("^3!give: ^7invalid target\n"); - return qfalse; - } - - G_SayArgv(2 + skiparg, arg_amount, sizeof(arg_amount)); - amount = atoi(arg_amount); - - switch (target->client->pers.teamSelection) { - case PTE_ALIENS: - if (amount < -9 || amount > 9) { - too_big: - ADMP("^3!give: ^7amount is too big\n"); - return qfalse; - } - - currency = "evo"; - break; - - case PTE_HUMANS: - if (amount < -2000 || amount > 2000) - goto too_big; - - currency = "credit"; - break; - - default: - goto invalid_target; - } - - G_AddCreditToClient(target->client, amount, qtrue); - AP(va("print \"^3!give: ^7%s^7 was given %i %s%s by ^7%s^7\n\"", - target->client->pers.netname, amount, currency, - (abs(amount) != 1 ? "s" : ""), - ent ? G_admin_adminPrintName(ent) : "console")); - - return qtrue; + char search[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; + addr_t ip; + qboolean ipmatch = qfalse; + int start = MAX_CLIENTS, i; + + if( trap_Argc() == 3 ) + { + trap_Argv( 2, search, sizeof( search ) ); + start = atoi( search ); + } + if( trap_Argc() > 1 ) + { + trap_Argv( 1, search, sizeof( search ) ); + i = 0; + if( trap_Argc() == 2 ) + for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ ); + if( !search[ i ] ) + start = atoi( search ); + else if( !( ipmatch = G_AddressParse( search, &ip ) ) ) + G_SanitiseString( search, s2, sizeof( s2 ) ); + } + + admin_search( ent, "namelog", "recent players", + ipmatch ? namelog_matchip : namelog_matchname, namelog_out, level.namelogs, + ipmatch ? (void *)&ip : s2, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS ); + return qtrue; } -extern mapRotations_t mapRotations; +/* +================== +G_NamelogFromString -qboolean G_admin_setrotation(gentity_t *ent, int skiparg) +This is similar to G_ClientNumberFromString but for namelog instead +Returns NULL if not exactly 1 match +================== +*/ +namelog_t *G_NamelogFromString( gentity_t *ent, char *s ) { - char new_rotation[MAX_NAME_LENGTH]; - int i; - - if (G_SayArgc() < 2 + skiparg) - { - ADMP("^3!setrotation: ^7usage: !setrotation [rotation]\n"); - ADMP("Available rotations:\n"); - goto rotationlist; - } + namelog_t *p, *m = NULL; + int i, found = 0; + char n2[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; - G_SayArgv(1 + skiparg, new_rotation, sizeof(new_rotation)); + if( !s[ 0 ] ) + return NULL; - for( i = 0; i < mapRotations.numRotations; i++ ) + // if a number is provided, it is a clientnum or namelog id + for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); + if( !s[ i ] ) { - if( Q_stricmp( mapRotations.rotations[ i ].name, new_rotation ) == 0 ) + i = atoi( s ); + + if( i >= 0 && i < level.maxclients ) { - G_StartMapRotation(new_rotation, qfalse); - trap_SendServerCommand( -1, va("print \"^3!setrotation: ^7rotation ^3%s ^7was started by %s", - new_rotation, ent ? G_admin_adminPrintName(ent) : "console")); - return qtrue; + if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) + return level.clients[ i ].pers.namelog; + } + else if( i >= MAX_CLIENTS ) + { + for( p = level.namelogs; p; p = p->next ) + { + if( p->id == i ) + break; + } + if( p ) + return p; } + + return NULL; } - ADMP("^3!setrotation: ^7rotation not found. Available rotations:\n"); - goto rotationlist; - rotationlist: + + // check for a name match + G_SanitiseString( s, s2, sizeof( s2 ) ); + + for( p = level.namelogs; p; p = p->next ) { - for( i = 0; i < mapRotations.numRotations; i++ ) + for( i = 0; i < MAX_NAMELOG_NAMES && p->name[ i ][ 0 ]; i++ ) { - ADMP(va(" %s\n", mapRotations.rotations[ i ].name)); + G_SanitiseString( p->name[ i ], n2, sizeof( n2 ) ); + + // if this is an exact match to a current player + if( i == p->nameOffset && p->slot > -1 && !strcmp( s2, n2 ) ) + return p; + + if( strstr( n2, s2 ) ) + m = p; } - ADMP(va("Number of available rotations: ^3%d\n", mapRotations.numRotations)); - } - return qfalse; -} -qboolean G_admin_versions(gentity_t *ent, int skiparg) -{ - int i; + if( m == p ) + found++; + } - ADMBP_begin(); + if( found == 1 ) + return m; - for (i = 0; i < level.maxclients; i++) { - gclient_t *client = level.clients + i; - char userinfo[ MAX_INFO_STRING ], *p; + if( found > 1 ) + admin_search( ent, "namelog", "recent players", namelog_matchname, + namelog_out, level.namelogs, s2, 0, MAX_CLIENTS, -1 ); - if (client->pers.connected == CON_DISCONNECTED) - continue; + return NULL; +} - ADMBP(va("%02i ", i)); +qboolean G_admin_lock( gentity_t *ent ) +{ + char command[ MAX_ADMIN_CMD_LEN ]; + char teamName[ sizeof( "aliens" ) ]; + team_t team; + qboolean lock, fail = qfalse; - trap_GetUserinfo(i, userinfo, sizeof(userinfo)); - p = Info_ValueForKey(userinfo, "version"); + trap_Argv( 0, command, sizeof( command ) ); + if( trap_Argc() < 2 ) + { + ADMP( va( "^3%s: ^7usage: %s [a|h]\n", command, command ) ); + return qfalse; + } + lock = !Q_stricmp( command, "lock" ); + trap_Argv( 1, teamName, sizeof( teamName ) ); + team = G_TeamFromString( teamName ); - if (p[0]) - ADMBP(va("'%s'\n", p)); - else { - p = Info_ValueForKey(userinfo, "cl_voip"); + if( team == TEAM_ALIENS ) + { + if( level.alienTeamLocked == lock ) + fail = qtrue; + else + level.alienTeamLocked = lock; + } + else if( team == TEAM_HUMANS ) + { + if( level.humanTeamLocked == lock ) + fail = qtrue; + else + level.humanTeamLocked = lock; + } + else + { + ADMP( va( "^3%s: ^7invalid team: '%s'\n", command, teamName ) ); + return qfalse; + } - if (p[0]) - ADMBP("probably GPP or newer\n"); - else - ADMBP("probably stock 1.1\n"); - } - } + if( fail ) + { + ADMP( va( "^3%s: ^7the %s team is %s locked\n", + command, BG_TeamName( team ), lock ? "already" : "not currently" ) ); - ADMBP_end(); - return qtrue; -} + return qfalse; + } -static int calc_ff_pct(statsCounters_t *sc) { - if (sc->dmgdone + sc->structdmgdone <= 0) { - if (sc->ffdmgdone <= 0) - return 0; - else - return 100; - } // else { - // return round((float)sc->ffdmgdone / (sc->ffdmgdone + sc->dmgdone + sc->structdmgdone) * 100); - // } -} + admin_log( BG_TeamName( team ) ); + AP( va( "print \"^3%s: ^7the %s team has been %slocked by %s\n\"", + command, BG_TeamName( team ), lock ? "" : "un", + ent ? ent->client->pers.netname : "console" ) ); -qboolean G_admin_showff(gentity_t *ent, int skiparg) -{ - char arg_name_raw[MAX_NAME_LENGTH]; - char arg_name[MAX_NAME_LENGTH]; - int target_id, ffpct; - gentity_t *target; - statsCounters_t *sc; - - if (G_SayArgc() == 1 + skiparg) { - int i; - char team[4]; - gclient_t *client; - - ADMBP_begin(); - ADMBP("^3!showff:^7 friendly fire damage percentage for all connected players\n"); - - for (i = 0; i < level.maxclients; i++) { - client = &level.clients[i]; - - if (client->pers.connected != CON_CONNECTED) - continue; - - if (client->pers.teamSelection == PTE_HUMANS) - Com_sprintf( team, sizeof( team ), "^4H", team); - else if (client->pers.teamSelection == PTE_ALIENS) - Com_sprintf( team, sizeof( team ), "^1A", team); - else - Com_sprintf( team, sizeof( team ), "^3S", team); - - ffpct = calc_ff_pct(&client->pers.statscounters); - ADMBP(va("%2d %s ^1%3d%% ^7%s^7\n", i, team, ffpct, client->pers.netname)); - } - - ADMBP("^7for detailed information, use ^3!showff player|slot^7\n"); - ADMBP_end(); - return qtrue; - } - - G_SayArgv(1 + skiparg, arg_name_raw, sizeof(arg_name_raw)); - G_SanitiseString(arg_name_raw, arg_name, sizeof(arg_name)); - - if (is_numeric(arg_name)) { - target_id = atoi(arg_name); - - if (target_id < 0 || target_id >= MAX_CLIENTS) { - ADMP(va("^3!showff: ^7invalid client number\n")); - return qfalse; - } - } else { - int pids[MAX_CLIENTS]; - - if (G_ClientNumbersFromString(arg_name, pids) != 1) { - char error[MAX_STRING_CHARS]; - - G_MatchOnePlayer(pids, error, sizeof(error)); - ADMP(va("^3!showff: ^7%s\n", error)); - return qfalse; - } - - target_id = pids[0]; - } - - target = g_entities + target_id; - sc = &target->client->pers.statscounters; - ffpct = calc_ff_pct(sc); - - ADMP(va("^3!showff: ^7detailed FF information for %s^7:\n", - target->client->pers.netname)); - ADMP(va("^7damage to: Enemies: ^1%d^7, structures: ^1%d^7, friendlies: ^1%d\n", - sc->dmgdone, sc->structdmgdone, sc->ffdmgdone)); - ADMP(va("dealt ^1%d%%^7 of their total damage to the team\n", ffpct)); - - return qtrue; + return qtrue; } -void G_admin_tklog_cleanup( void ) +qboolean G_admin_builder( gentity_t *ent ) { - int i; + vec3_t forward, right, up; + vec3_t start, end, dist; + trace_t tr; + gentity_t *traceEnt; + buildLog_t *log; + int i; + qboolean buildlog; + char logid[ 20 ] = {""}; - for( i = 0; i < MAX_ADMIN_TKLOGS && g_admin_tklog[ i ]; i++ ) + if( !ent ) { - G_Free( g_admin_tklog[ i ] ); - g_admin_tklog[ i ] = NULL; + ADMP( "^3builder: ^7console can't aim.\n" ); + return qfalse; } - admin_tklog_index = 0; -} + buildlog = G_admin_permission( ent, "buildlog" ); -void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath ) -{ - g_admin_tklog_t *tklog; - int previous; - int count = 1; + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + if( ent->client->pers.teamSelection != TEAM_NONE && + ent->client->sess.spectatorState == SPECTATOR_NOT ) + CalcMuzzlePoint( ent, forward, right, up, start ); + else + VectorCopy( ent->client->ps.origin, start ); + VectorMA( start, 1000, forward, end ); - if( !attacker ) - return; + trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) ) + { + if( !buildlog && + ent->client->pers.teamSelection != TEAM_NONE && + ent->client->pers.teamSelection != traceEnt->buildableTeam ) + { + ADMP( "^3builder: ^7structure not owned by your team\n" ); + return qfalse; + } - previous = admin_tklog_index - 1; - if( previous < 0 ) - previous = MAX_ADMIN_TKLOGS - 1; + if( buildlog ) + { + for( i = 0 ; buildlog && i < level.numBuildLogs; i++ ) + { + log = &level.buildLog[ ( level.buildId - i - 1 ) % MAX_BUILDLOG ]; + if( log->fate != BF_CONSTRUCT || traceEnt->s.modelindex != log->modelindex ) + continue; - if( g_admin_tklog[ previous ] ) - count = g_admin_tklog[ previous ]->id + 1; + VectorSubtract( traceEnt->s.pos.trBase, log->origin, dist ); + if( VectorLengthSquared( dist ) < 2.0f ) + Com_sprintf( logid, sizeof( logid ), ", buildlog #%d", + MAX_CLIENTS + level.buildId - i - 1 ); + } + } - if( g_admin_tklog[ admin_tklog_index ] ) - tklog = g_admin_tklog[ admin_tklog_index ]; + ADMP( va( "^3builder: ^7%s%s%s^7%s\n", + BG_Buildable( traceEnt->s.modelindex )->humanName, + traceEnt->builtBy ? " built by " : "", + traceEnt->builtBy ? + traceEnt->builtBy->name[ traceEnt->builtBy->nameOffset ] : + "", + buildlog ? ( logid[ 0 ] ? logid : ", not in buildlog" ) : "" ) ); + } else - tklog = G_Alloc( sizeof( g_admin_tklog_t ) ); + ADMP( "^3builder: ^7no structure found under crosshair\n" ); - memset( tklog, 0, sizeof( g_admin_tklog_t ) ); - tklog->id = count; - tklog->time = level.time - level.startTime; - Q_strncpyz( tklog->name, attacker->client->pers.netname, sizeof( tklog->name ) ); + return qtrue; +} - if( victim ) +qboolean G_admin_pause( gentity_t *ent ) +{ + if( !level.pausedTime ) { - Q_strncpyz( tklog->victim, victim->client->pers.netname, sizeof( tklog->victim ) ); - tklog->damage = victim->client->tkcredits[ attacker->s.number ]; - tklog->value = victim->client->ps.stats[ STAT_MAX_HEALTH ]; + AP( va( "print \"^3pause: ^7%s^7 paused the game.\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + level.pausedTime = 1; + trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); } else { - Q_strncpyz( tklog->victim, "^3BLEEDING", sizeof( tklog->victim ) ); - tklog->damage = attacker->client->pers.statscounters.spreebleeds; - tklog->value = g_bleedingSpree.integer * 100; - } + // Prevent accidental pause->unpause race conditions by two admins + if( level.pausedTime < 1000 ) + { + ADMP( "^3pause: ^7Unpausing so soon assumed accidental and ignored.\n" ); + return qfalse; + } - tklog->team = attacker->client->ps.stats[ STAT_PTEAM ]; - if( meansOfDeath == MOD_GRENADE ) - tklog->weapon = WP_GRENADE; - else if( tklog->team == PTE_HUMANS ) - tklog->weapon = attacker->s.weapon; - else - tklog->weapon = attacker->client->ps.stats[ STAT_PCLASS ]; + AP( va( "print \"^3pause: ^7%s^7 unpaused the game (Paused for %d sec) \n\"", + ( ent ) ? ent->client->pers.netname : "console", + (int) ( (float) level.pausedTime / 1000.0f ) ) ); + trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); + + level.pausedTime = 0; + } - g_admin_tklog[ admin_tklog_index ] = tklog; - admin_tklog_index++; - if( admin_tklog_index >= MAX_ADMIN_TKLOGS ) - admin_tklog_index = 0; + return qtrue; } -qboolean G_admin_tklog( gentity_t *ent, int skiparg ) +static char *fates[] = { - g_admin_tklog_t *results[ 10 ]; - int result_index = 0; - char *search_name = NULL; - int index; - int skip = 0; - int skipped = 0; - int checked = 0; - char n1[ MAX_NAME_LENGTH ]; - char fmt_name[ 16 ]; - char argbuf[ 32 ]; - char *weaponName; - int name_length = 10; - int max_id = 0; - int i; - qboolean match; - - memset( results, 0, sizeof( results ) ); + "^2built^7", + "^3deconstructed^7", + "^7replaced^7", + "^3destroyed^7", + "^1TEAMKILLED^7", + "^7unpowered^7", + "removed" +}; +qboolean G_admin_buildlog( gentity_t *ent ) +{ + char search[ MAX_NAME_LENGTH ] = {""}; + char s[ MAX_NAME_LENGTH ] = {""}; + char n[ MAX_NAME_LENGTH ]; + char stamp[ 8 ]; + int id = -1; + int printed = 0; + int time; + int start = MAX_CLIENTS + level.buildId - level.numBuildLogs; + int i = 0, j; + buildLog_t *log; + + if( !level.buildId ) + { + ADMP( "^3buildlog: ^7log is empty\n" ); + return qtrue; + } - index = admin_tklog_index; - for( i = 0; i < 10; i++ ) + if( trap_Argc() == 3 ) { - int prev; - - prev = index - 1; - if( prev < 0 ) - prev = MAX_ADMIN_TKLOGS - 1; - if( !g_admin_tklog[ prev ] ) - break; - if( g_admin_tklog[ prev ]->id > max_id ) - max_id = g_admin_tklog[ prev ]->id; - index = prev; + trap_Argv( 2, search, sizeof( search ) ); + start = atoi( search ); } - - if( G_SayArgc() > 1 + skiparg ) + if( trap_Argc() > 1 ) { - G_SayArgv( 1 + skiparg, argbuf, sizeof( argbuf ) ); - if( ( *argbuf >= '0' && *argbuf <= '9' ) || *argbuf == '-' ) + trap_Argv( 1, search, sizeof( search ) ); + for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ ); + if( i && !search[ i ] ) { - int id; - - id = atoi( argbuf ); - if( id < 0 ) - id += ( max_id - 9 ); - else if( id <= max_id - MAX_ADMIN_TKLOGS ) - id = max_id - MAX_ADMIN_TKLOGS + 1; - - if( id + 9 >= max_id ) - id = max_id - 9; - if( id < 1 ) - id = 1; - for( i = 0; i < MAX_ADMIN_TKLOGS; i++ ) + id = atoi( search ); + if( trap_Argc() == 2 && ( id < 0 || id >= MAX_CLIENTS ) ) { - if( g_admin_tklog[ i ]->id == id ) - { - index = i; - break; - } + start = id; + id = -1; + } + else if( id < 0 || id >= MAX_CLIENTS || + level.clients[ id ].pers.connected != CON_CONNECTED ) + { + ADMP( "^3buildlog: ^7invalid client id\n" ); + return qfalse; } } else + G_SanitiseString( search, s, sizeof( s ) ); + } + else + start = MAX( -MAX_ADMIN_LISTITEMS, -level.buildId ); + + if( start < 0 ) + start = MAX( level.buildId - level.numBuildLogs, start + level.buildId ); + else + start -= MAX_CLIENTS; + if( start < level.buildId - level.numBuildLogs || start >= level.buildId ) + { + ADMP( "^3buildlog: ^7invalid build ID\n" ); + return qfalse; + } + + if( ent && ent->client->pers.teamSelection != TEAM_NONE ) + trap_SendServerCommand( -1, + va( "print \"^3buildlog: ^7%s^7 requested a log of recent building activity\n\"", + ent->client->pers.netname ) ); + + ADMBP_begin(); + for( i = start; i < level.buildId && printed < MAX_ADMIN_LISTITEMS; i++ ) + { + log = &level.buildLog[ i % MAX_BUILDLOG ]; + if( id >= 0 && id < MAX_CLIENTS ) { - search_name = argbuf; + if( log->actor != level.clients[ id ].pers.namelog ) + continue; } - - if( G_SayArgc() > 2 + skiparg && ( search_name ) ) + else if( s[ 0 ] ) { - char skipbuf[ 4 ]; - G_SayArgv( 2 + skiparg, skipbuf, sizeof( skipbuf ) ); - skip = atoi( skipbuf ); + if( !log->actor ) + continue; + for( j = 0; j < MAX_NAMELOG_NAMES && log->actor->name[ j ][ 0 ]; j++ ) + { + G_SanitiseString( log->actor->name[ j ], n, sizeof( n ) ); + if( strstr( n, s ) ) + break; + } + if( j >= MAX_NAMELOG_NAMES || !log->actor->name[ j ][ 0 ] ) + continue; } - } + printed++; + time = ( log->time - level.startTime ) / 1000; + Com_sprintf( stamp, sizeof( stamp ), "%3d:%02d", time / 60, time % 60 ); + ADMBP( va( "^2%c^7%-3d %s %s^7%s%s%s %s%s%s\n", + log->actor && log->fate != BF_REPLACE && log->fate != BF_UNPOWER ? + '*' : ' ', + i + MAX_CLIENTS, + log->actor && ( log->fate == BF_REPLACE || log->fate == BF_UNPOWER ) ? + " \\_" : stamp, + BG_Buildable( log->modelindex )->humanName, + log->builtBy && log->fate != BF_CONSTRUCT ? + " (built by " : + "", + log->builtBy && log->fate != BF_CONSTRUCT ? + log->builtBy->name[ log->builtBy->nameOffset ] : + "", + log->builtBy && log->fate != BF_CONSTRUCT ? + "^7)" : + "", + fates[ log->fate ], + log->actor ? " by " : "", + log->actor ? + log->actor->name[ log->actor->nameOffset ] : + "" ) ); + } + ADMBP( va( "^3buildlog: ^7showing %d build logs %d - %d of %d - %d. %s\n", + printed, start + MAX_CLIENTS, i + MAX_CLIENTS - 1, + level.buildId + MAX_CLIENTS - level.numBuildLogs, + level.buildId + MAX_CLIENTS - 1, + i < level.buildId ? va( "run 'buildlog %s%s%d' to see more", + search, search[ 0 ] ? " " : "", i + MAX_CLIENTS ) : "" ) ); + ADMBP_end(); + return qtrue; +} - if( search_name ) - { - g_admin_tklog_t *result_swap[ 10 ]; +qboolean G_admin_revert( gentity_t *ent ) +{ + char arg[ MAX_TOKEN_CHARS ]; + char time[ MAX_DURATION_LENGTH ]; + int id; + buildLog_t *log; - memset( &result_swap, 0, sizeof( result_swap ) ); + if( trap_Argc() != 2 ) + { + ADMP( "^3revert: ^7usage: revert [id]\n" ); + return qfalse; + } - index = admin_tklog_index - 1; - if( index < 0 ) - index = MAX_ADMIN_TKLOGS - 1; + trap_Argv( 1, arg, sizeof( arg ) ); + id = atoi( arg ) - MAX_CLIENTS; + if( id < level.buildId - level.numBuildLogs || id >= level.buildId ) + { + ADMP( "^3revert: ^7invalid id\n" ); + return qfalse; + } - while( g_admin_tklog[ index ] && - checked < MAX_ADMIN_TKLOGS && - result_index < 10 ) - { - match = qfalse; + log = &level.buildLog[ id % MAX_BUILDLOG ]; + if( !log->actor || log->fate == BF_REPLACE || log->fate == BF_UNPOWER ) + { + // fixme: then why list them with an id # in build log ? - rez + ADMP( "^3revert: ^7you can only revert direct player actions, " + "indicated by ^2* ^7in buildlog\n" ); + return qfalse; + } - G_SanitiseString( g_admin_tklog[ index ]->name, n1, sizeof( n1 ) ); - if( strstr( n1, search_name ) ) - match = qtrue; + G_admin_duration( ( level.time - log->time ) / 1000, time, + sizeof( time ) ); + admin_log( arg ); + AP( va( "print \"^3revert: ^7%s^7 reverted %d %s over the past %s\n\"", + ent ? ent->client->pers.netname : "console", + level.buildId - id, + ( level.buildId - id ) > 1 ? "changes" : "change", + time ) ); + G_BuildLogRevert( id ); + return qtrue; +} - if( match && skip > 0 ) - { - match = qfalse; - skip--; - skipped++; - } - if( match ) - { - result_swap[ result_index ] = g_admin_tklog[ index ]; - result_index++; - } +/* +================ + G_admin_print - checked++; - index--; - if( index < 0 ) - index = MAX_ADMIN_TKLOGS - 1; - } - // search runs backwards, turn it around - for( i = 0; i < result_index; i++ ) - results[ i ] = result_swap[ result_index - i - 1 ]; - } + This function facilitates the ADMP define. ADMP() is similar to CP except + that it prints the message to the server console if ent is not defined. +================ +*/ +void G_admin_print( gentity_t *ent, const char *m ) +{ + if( ent ) + trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) ); else { - while( g_admin_tklog[ index ] && result_index < 10 ) + char m2[ MAX_STRING_CHARS ]; + if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) ) { - results[ result_index ] = g_admin_tklog[ index ]; - result_index++; - index++; - if( index >= MAX_ADMIN_TKLOGS ) - index = 0; + G_DecolorString( m, m2, sizeof( m2 ) ); + trap_Print( m2 ); } + else + trap_Print( m ); } +} - for( i = 0; results[ i ] && i < 10; i++ ) - { - int l; +void G_admin_buffer_begin( void ) +{ + g_bfb[ 0 ] = '\0'; +} - G_DecolorString( results[ i ]->name, n1 ); - l = strlen( n1 ); - if( l > name_length ) - name_length = l; - } - ADMBP_begin( ); - for( i = 0; results[ i ] && i < 10; i++ ) +void G_admin_buffer_end( gentity_t *ent ) +{ + ADMP( g_bfb ); +} + +void G_admin_buffer_print( gentity_t *ent, const char *m ) +{ + // 1022 - strlen("print 64 \"\"") - 1 + if( strlen( m ) + strlen( g_bfb ) >= 1009 ) { - int t; + ADMP( g_bfb ); + g_bfb[ 0 ] = '\0'; + } + Q_strcat( g_bfb, sizeof( g_bfb ), m ); +} - t = results[ i ]->time / 1000; - G_DecolorString( results[ i ]->name, n1 ); - Com_sprintf( fmt_name, sizeof( fmt_name ), "%%%ds", - ( name_length + (int)( strlen( results[ i ]->name ) - strlen( n1 ) ) ) ); - Com_sprintf( n1, sizeof( n1 ), fmt_name, results[ i ]->name ); +void G_admin_cleanup( void ) +{ + g_admin_level_t *l; + g_admin_admin_t *a; + g_admin_ban_t *b; + g_admin_command_t *c; + void *n; - if( results[ i ]->team == PTE_HUMANS ) - weaponName = BG_FindNameForWeapon( results[ i ]->weapon ); - else - weaponName = BG_FindNameForClassNum( results[ i ]->weapon ); - - ADMBP( va( "^7%3d %3d:%02d %s^7 %3d / %3d %10s %s^7\n", - results[ i ]->id, - t / 60, t % 60, - n1, - results[ i ]->damage, - results[ i ]->value, - weaponName, - results[ i ]->victim ) ); - } - if( search_name ) - { - ADMBP( va( "^3!tklog:^7 Showing %d matches for '%s^7'.", - result_index, - argbuf ) ); - if( checked < MAX_ADMIN_TKLOGS && g_admin_tklog[ checked ] ) - ADMBP( va( " run '!tklog %s^7 %d' to see more", - argbuf, - skipped + result_index ) ); - ADMBP( "\n" ); - } - else if ( results[ 0 ] ) - { - ADMBP( va( "^3!tklog:^7 Showing %d - %d of %d.\n", - results[ 0 ]->id, - results[ result_index - 1 ]->id, - max_id ) ); + for( l = g_admin_levels; l; l = n ) + { + n = l->next; + BG_Free( l ); } - else + g_admin_levels = NULL; + for( a = g_admin_admins; a; a = n ) { - ADMBP( "^3!tklog:^7 log is empty.\n" ); + n = a->next; + BG_Free( a ); } - ADMBP_end( ); - - return qtrue; + g_admin_admins = NULL; + for( b = g_admin_bans; b; b = n ) + { + n = b->next; + BG_Free( b ); + } + g_admin_bans = NULL; + for( c = g_admin_commands; c; c = n ) + { + n = c->next; + BG_Free( c ); + } + g_admin_commands = NULL; + BG_DefragmentMemory( ); } -qboolean G_admin_sm( gentity_t *ent, int skiparg ) +qboolean G_admin_sm( gentity_t *ent ) { const char *s; char feature[ 16 ]; - if( G_SayArgc() < 2 + skiparg ) + if( trap_Argc() < 2 ) { usage: - ADMP( "^3!sm: ^7usage: !sm ipa \n" ); + ADMP( "^3sm: ^7usage: sm ipa \n" ); return qfalse; } - s = G_SayConcatArgs( 1 + skiparg ); + s = ConcatArgs( 1 ); if( strchr( s, '\n' ) || strchr( s, '\r' ) ) { - ADMP( "^3!sm: ^7invalid character\n" ); + ADMP( "^3sm: ^7invalid character\n" ); return qfalse; } - G_SayArgv( 1 + skiparg, feature, sizeof( feature ) ); + trap_Argv( 1, feature, sizeof( feature ) ); if( !Q_stricmp( feature, "ipa" ) ) { char ipa[ 32 ]; - int parts[ 4 ]; - if( G_SayArgc() > 3 + skiparg ) + if( trap_Argc() > 3 ) { - ADMP( "^3!sm: ^7excessive arguments\n" ); + ADMP( "^3sm: ^7excessive arguments\n" ); goto usage; } - G_SayArgv( 2 + skiparg, ipa, sizeof( ipa ) ); - - // have a well-formed IPv4 address, because Schachtmeister silently drops all invalid requests - - if( sscanf( ipa, "%i.%i.%i.%i", &parts[0], &parts[1], &parts[2], &parts[3] ) != 4 - || parts[0] < 0 || parts[0] > 255 - || parts[1] < 0 || parts[1] > 255 - || parts[2] < 0 || parts[2] > 255 - || parts[3] < 0 || parts[3] > 255 ) - { - ADMP( "^3!sm: ^7invalid IP address\n" ); - return qfalse; - } - - Com_sprintf( ipa, sizeof( ipa ), "%i.%i.%i.%i", parts[0], parts[1], parts[2], parts[3] ); + trap_Argv( 2, ipa, sizeof( ipa ) ); if( rand() % 2 /* FIXME cache hit */ ) { const char *answer = "interesting"; - ADMP( va( "^3!sm: ^7IP address %s is: %s\n", ipa, answer ) ); + ADMP( va( "^3sm: ^7IP address '%s^7' is: %s\n", ipa, answer ) ); return qtrue; } - ADMP( "^3!sm: ^7hmm...\n" ); + ADMP( "^3sm: ^7hmm...\n" ); trap_SendConsoleCommand( EXEC_APPEND, va( "smq ipa \"%s\"\n", ipa ) ); } else goto usage; return qtrue; -} +} \ No newline at end of file diff --git a/src/game/g_admin.h b/src/game/g_admin.h index 25cf2a7..3ab445e 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -1,12 +1,13 @@ /* =========================================================================== -Copyright (C) 2004-2006 Tony J. White +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -15,8 +16,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -31,100 +32,59 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ADMBP_begin() G_admin_buffer_begin() #define ADMBP_end() G_admin_buffer_end(ent) -#define MAX_ADMIN_LEVELS 32 -#define MAX_ADMIN_ADMINS 1024 -#define MAX_ADMIN_BANS 1024 -#define MAX_ADMIN_NAMELOGS 128 -#define MAX_ADMIN_NAMELOG_NAMES 5 -#define MAX_ADMIN_ADMINLOGS 128 -#define MAX_ADMIN_ADMINLOG_ARGS 50 #define MAX_ADMIN_FLAG_LEN 20 #define MAX_ADMIN_FLAGS 1024 -#define MAX_ADMIN_COMMANDS 64 #define MAX_ADMIN_CMD_LEN 20 #define MAX_ADMIN_BAN_REASON 50 -#define MAX_ADMIN_BANSUSPEND_DAYS 14 -#define MAX_ADMIN_TKLOGS 64 /* * IMMUNITY - cannot be vote kicked, vote muted * NOCENSORFLOOD - cannot be censored or flood protected - * TEAMCHANGEFREE - never loses credits for changing teams * SPECALLCHAT - can see team chat as a spectator * FORCETEAMCHANGE - can switch teams any time, regardless of balance * UNACCOUNTABLE - does not need to specify a reason for a kick/ban * NOVOTELIMIT - can call a vote at any time (regardless of a vote being * disabled or voting limitations) * CANPERMBAN - does not need to specify a duration for a ban - * TEAMCHATCMD - can run commands from team chat + * CANIPBAN - allows banning not-necessarily-connected players with CIDR notation * ACTIVITY - inactivity rules do not apply to them * IMMUTABLE - admin commands cannot be used on them - * INCOGNITO - does not show up as an admin in !listplayers - * SEESINCOGNITO - sees registered name of players flagged with INCOGNITO - * ADMINCHAT - receives and can send /a admin messages - * SEESFULLLISTPLAYERS - sees all information in !listplayers - * DBUILDER - permanent designated builder - * STEALTH - uses admin stealth - * SPECIAL - allows some special permissions (unlimited votes etc) - * SPECIALNAME - allows black text in name - * .NOCHAT - mutes a player on connect - * .NOVOTE - disallows voting by a player + * INCOGNITO - does not show up as an admin in /listplayers * ALLFLAGS - all flags (including command flags) apply to this player + * ADMINCHAT - receives and can send /a admin messages */ - - -#define ADMF_IMMUNITY "IMMUNITY" -#define ADMF_NOCENSORFLOOD "NOCENSORFLOOD" -#define ADMF_TEAMCHANGEFREE "TEAMCHANGEFREE" -#define ADMF_SPEC_ALLCHAT "SPECALLCHAT" -#define ADMF_FORCETEAMCHANGE "FORCETEAMCHANGE" -#define ADMF_UNACCOUNTABLE "UNACCOUNTABLE" -#define ADMF_NO_VOTE_LIMIT "NOVOTELIMIT" -#define ADMF_CAN_PERM_BAN "CANPERMBAN" -#define ADMF_TEAMCHAT_CMD "TEAMCHATCMD" -#define ADMF_ACTIVITY "ACTIVITY" - -#define ADMF_IMMUTABLE "IMMUTABLE" -#define ADMF_INCOGNITO "INCOGNITO" -#define ADMF_SEESINCOGNITO "SEESINCOGNITO" -#define ADMF_ADMINCHAT "ADMINCHAT" -#define ADMF_HIGHADMINCHAT "HIGHADMINCHAT" -#define ADMF_SEESFULLLISTPLAYERS "SEESFULLLISTPLAYERS" -#define ADMF_DBUILDER "DBUILDER" -#define ADMF_ADMINSTEALTH "STEALTH" -#define ADMF_ALLFLAGS "ALLFLAGS" - -#define ADMF_BAN_IMMUNITY "BANIMMUNITY" - -#define ADMF_SPECIAL "SPECIAL" -#define ADMF_SPECIALNAME "SPECIALNAME" - -#define ADMF_NOSCRIMRESTRICTION "NOSCRIMRESTRICTION" -#define ADMF_NOAUTOBAHN "NOAUTOBAHN" - -#define ADMF_NO_BUILD ".NOBUILD" -#define ADMF_NO_CHAT ".NOCHAT" -#define ADMF_NO_VOTE ".NOVOTE" -#define ADMF_FAKE_NO_VOTE ".FAKENOVOTE" +#define ADMF_IMMUNITY "IMMUNITY" +#define ADMF_NOCENSORFLOOD "NOCENSORFLOOD" +#define ADMF_SPEC_ALLCHAT "SPECALLCHAT" +#define ADMF_FORCETEAMCHANGE "FORCETEAMCHANGE" +#define ADMF_UNACCOUNTABLE "UNACCOUNTABLE" +#define ADMF_NO_VOTE_LIMIT "NOVOTELIMIT" +#define ADMF_CAN_PERM_BAN "CANPERMBAN" +#define ADMF_CAN_IP_BAN "CANIPBAN" +#define ADMF_ACTIVITY "ACTIVITY" + +#define ADMF_IMMUTABLE "IMMUTABLE" +#define ADMF_INCOGNITO "INCOGNITO" +#define ADMF_ALLFLAGS "ALLFLAGS" +#define ADMF_ADMINCHAT "ADMINCHAT" #define MAX_ADMIN_LISTITEMS 20 #define MAX_ADMIN_SHOWBANS 10 -#define MAX_ADMIN_MAPLOG_LENGTH 5 -// important note: QVM does not seem to allow a single char to be a -// member of a struct at init time. flag has been converted to char* typedef struct { char *keyword; - qboolean ( * handler ) ( gentity_t *ent, int skiparg ); + qboolean ( * handler ) ( gentity_t *ent ); + qboolean silent; char *flag; - char *function; // used for !help - char *syntax; // used for !help + char *function; // used in /adminhelp + char *syntax; // used in /adminhelp } g_admin_cmd_t; typedef struct g_admin_level { + struct g_admin_level *next; int level; char name[ MAX_NAME_LENGTH ]; char flags[ MAX_ADMIN_FLAGS ]; @@ -133,30 +93,48 @@ g_admin_level_t; typedef struct g_admin_admin { + struct g_admin_admin *next; + int level; char guid[ 33 ]; char name[ MAX_NAME_LENGTH ]; - int level; char flags[ MAX_ADMIN_FLAGS ]; - int seen; } g_admin_admin_t; +#define ADDRLEN 16 +/* +addr_ts are passed as "arg" to admin_search for IP address matching +admin_search prints (char *)arg, so the stringified address needs to be first +*/ +typedef struct +{ + char str[ 44 ]; + enum + { + IPv4, + IPv6 + } type; + byte addr[ ADDRLEN ]; + int mask; +} addr_t; + typedef struct g_admin_ban { + struct g_admin_ban *next; char name[ MAX_NAME_LENGTH ]; char guid[ 33 ]; - char ip[ 20 ]; + addr_t ip; char reason[ MAX_ADMIN_BAN_REASON ]; - char made[ 18 ]; // big enough for strftime() %c + char made[ 20 ]; // "YYYY-MM-DD hh:mm:ss" int expires; - int suspend; char banner[ MAX_NAME_LENGTH ]; - int bannerlevel; + int warnCount; } g_admin_ban_t; typedef struct g_admin_command { + struct g_admin_command *next; char command[ MAX_ADMIN_CMD_LEN ]; char exec[ MAX_QPATH ]; char desc[ 50 ]; @@ -164,156 +142,60 @@ typedef struct g_admin_command } g_admin_command_t; -typedef struct -{ - int ratingTime; - int queryTime; - int dispatchTime; - int rating; - char *comment; -} schachtmeisterJudgement_t; - -typedef struct g_admin_namelog -{ - char name[ MAX_ADMIN_NAMELOG_NAMES ][MAX_NAME_LENGTH ]; - char ip[ 16 ]; - char guid[ 33 ]; - int slot; - qboolean banned; - qboolean muted; - int muteExpires; - qboolean denyBuild; - int denyHumanWeapons; - int denyAlienClasses; - int specExpires; - int voteCount; - schachtmeisterJudgement_t smj; -} -g_admin_namelog_t; - -typedef struct g_admin_adminlog -{ - char name[ MAX_NAME_LENGTH ]; - char command[ MAX_ADMIN_CMD_LEN ]; - char args[ MAX_ADMIN_ADMINLOG_ARGS ]; - int id; - int time; - int level; - qboolean success; -} -g_admin_adminlog_t; - -typedef struct g_admin_tklog -{ - char name[ MAX_NAME_LENGTH ]; - char victim[ MAX_NAME_LENGTH ]; - int id; - int time; - int damage; - int value; - int team; - int weapon; -} -g_admin_tklog_t; +void G_admin_register_cmds( void ); +void G_admin_unregister_cmds( void ); +void G_admin_cmdlist( gentity_t *ent ); -qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ); -qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ); -qboolean G_admin_readconfig( gentity_t *ent, int skiparg ); +qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen ); +qboolean G_admin_cmd_check( gentity_t *ent ); +qboolean G_admin_readconfig( gentity_t *ent ); qboolean G_admin_permission( gentity_t *ent, const char *flag ); qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ); -void G_admin_namelog_update( gclient_t *ent, qboolean disconnect ); -void G_admin_maplog_result( char *flag ); -int G_admin_level( gentity_t *ent ); -void G_admin_set_adminname( gentity_t *ent ); -char* G_admin_adminPrintName( gentity_t *ent ); - -qboolean G_admin_seen(gentity_t *ent, int skiparg ); -void G_admin_seen_update( char *guid ); - -// ! command functions -qboolean G_admin_time( gentity_t *ent, int skiparg ); -qboolean G_admin_setlevel( gentity_t *ent, int skiparg ); -qboolean G_admin_flaglist( gentity_t *ent, int skiparg ); -qboolean G_admin_flag( gentity_t *ent, int skiparg ); -qboolean G_admin_kick( gentity_t *ent, int skiparg ); -qboolean G_admin_adjustban( gentity_t *ent, int skiparg ); -qboolean G_admin_subnetban( gentity_t *ent, int skiparg ); -qboolean G_admin_suspendban( gentity_t *ent, int skiparg ); -qboolean G_admin_ban( gentity_t *ent, int skiparg ); -qboolean G_admin_unban( gentity_t *ent, int skiparg ); -qboolean G_admin_putteam( gentity_t *ent, int skiparg ); -qboolean G_admin_adminlog( gentity_t *ent, int skiparg ); -void G_admin_adminlog_cleanup( void ); -void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success ); -qboolean G_admin_listadmins( gentity_t *ent, int skiparg ); -qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ); -qboolean G_admin_listplayers( gentity_t *ent, int skiparg ); -qboolean G_admin_listmaps( gentity_t *ent, int skiparg ); -qboolean G_admin_listrotation( gentity_t *ent, int skiparg ); -qboolean G_admin_map( gentity_t *ent, int skiparg ); -qboolean G_admin_devmap( gentity_t *ent, int skiparg ); -void G_admin_maplog_update( void ); -qboolean G_admin_maplog( gentity_t *ent, int skiparg ); -qboolean G_admin_layoutsave( gentity_t *ent, int skiparg ); -qboolean G_admin_demo( gentity_t *ent, int skiparg ); -qboolean G_admin_mute( gentity_t *ent, int skiparg ); -qboolean G_admin_denybuild( gentity_t *ent, int skiparg ); -qboolean G_admin_denyweapon( gentity_t *ent, int skiparg ); -qboolean G_admin_showbans( gentity_t *ent, int skiparg ); -qboolean G_admin_help( gentity_t *ent, int skiparg ); -qboolean G_admin_admintest( gentity_t *ent, int skiparg ); -qboolean G_admin_allready( gentity_t *ent, int skiparg ); -qboolean G_admin_cancelvote( gentity_t *ent, int skiparg ); -qboolean G_admin_passvote( gentity_t *ent, int skiparg ); -qboolean G_admin_spec999( gentity_t *ent, int skiparg ); -qboolean G_admin_register( gentity_t *ent, int skiparg ); -qboolean G_admin_rename( gentity_t *ent, int skiparg ); -qboolean G_admin_restart( gentity_t *ent, int skiparg ); -qboolean G_admin_nobuild( gentity_t *ent, int skiparg ); -qboolean G_admin_nextmap( gentity_t *ent, int skiparg ); -qboolean G_admin_namelog( gentity_t *ent, int skiparg ); -qboolean G_admin_lock( gentity_t *ent, int skiparg ); -qboolean G_admin_unlock( gentity_t *ent, int skiparg ); -qboolean G_admin_info( gentity_t *ent, int skiparg ); -qboolean G_admin_buildlog( gentity_t *ent, int skiparg ); -qboolean G_admin_revert( gentity_t *ent, int skiparg ); -qboolean G_admin_decon( gentity_t *ent, int skiparg ); -qboolean G_admin_pause( gentity_t *ent, int skiparg ); -qboolean G_admin_L0( gentity_t *ent, int skiparg ); -qboolean G_admin_L1( gentity_t *ent, int skiparg ); -qboolean G_admin_putmespec( gentity_t *ent, int skiparg ); -qboolean G_admin_warn( gentity_t *ent, int skiparg ); -qboolean G_admin_designate( gentity_t *ent, int skiparg ); -qboolean G_admin_cp( gentity_t *ent, int skiparg ); - -qboolean G_admin_slap( gentity_t *ent, int skiparg ); -qboolean G_admin_drop( gentity_t *ent, int skiparg ); -qboolean G_admin_invisible( gentity_t *ent, int skiparg ); -qboolean G_admin_setdevmode( gentity_t *ent, int skiparg ); -qboolean G_admin_hstage( gentity_t *ent, int skiparg ); -qboolean G_admin_astage( gentity_t *ent, int skiparg ); -qboolean G_admin_bubble( gentity_t *ent, int skiparg ); -qboolean G_admin_scrim( gentity_t *ent, int skiparg ); -qboolean G_admin_give( gentity_t *ent, int skiparg ); -qboolean G_admin_setrotation( gentity_t *ent, int skiparg ); -qboolean G_admin_versions( gentity_t *ent, int skiparg ); -qboolean G_admin_showff(gentity_t *ent, int skiparg); -qboolean G_admin_tklog( gentity_t *ent, int skiparg ); -void G_admin_tklog_cleanup( void ); -void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath ); -void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment ); -qboolean G_admin_sm( gentity_t *ent, int skiparg ); -void G_admin_schachtmeisterFrame( void ); -qboolean G_admin_is_restricted(gentity_t *ent, qboolean sendMessage); - -void G_admin_print( gentity_t *ent, char *m ); -void G_admin_buffer_print( gentity_t *ent, char *m ); +g_admin_admin_t *G_admin_admin( const char *guid ); +void G_admin_authlog( gentity_t *ent ); + +// admin command functions +qboolean G_admin_time( gentity_t *ent ); +qboolean G_admin_setlevel( gentity_t *ent ); +qboolean G_admin_kick( gentity_t *ent ); +qboolean G_admin_addlayout( gentity_t *ent ); +qboolean G_admin_setivo( gentity_t *ent ); +qboolean G_admin_adjustban( gentity_t *ent ); +qboolean G_admin_ban( gentity_t *ent ); +qboolean G_admin_unban( gentity_t *ent ); +qboolean G_admin_putteam( gentity_t *ent ); +qboolean G_admin_listadmins( gentity_t *ent ); +qboolean G_admin_listlayouts( gentity_t *ent ); +qboolean G_admin_listplayers( gentity_t *ent ); +qboolean G_admin_changemap( gentity_t *ent ); +qboolean G_admin_mute( gentity_t *ent ); +qboolean G_admin_denybuild( gentity_t *ent ); +qboolean G_admin_showbans( gentity_t *ent ); +qboolean G_admin_adminhelp( gentity_t *ent ); +qboolean G_admin_admintest( gentity_t *ent ); +qboolean G_admin_allready( gentity_t *ent ); +qboolean G_admin_endvote( gentity_t *ent ); +qboolean G_admin_spec999( gentity_t *ent ); +qboolean G_admin_transform( gentity_t *ent ); +qboolean G_admin_rename( gentity_t *ent ); +qboolean G_admin_restart( gentity_t *ent ); +qboolean G_admin_nextmap( gentity_t *ent ); +qboolean G_admin_setnextmap( gentity_t *ent ); +qboolean G_admin_namelog( gentity_t *ent ); +qboolean G_admin_lock( gentity_t *ent ); +qboolean G_admin_pause( gentity_t *ent ); +qboolean G_admin_builder( gentity_t *ent ); +qboolean G_admin_buildlog( gentity_t *ent ); +qboolean G_admin_revert( gentity_t *ent ); +qboolean G_admin_setdevmode( gentity_t *ent ); +qboolean G_admin_sm( gentity_t *ent ); + +void G_admin_print( gentity_t *ent, const char *m ); +void G_admin_buffer_print( gentity_t *ent, const char *m ); void G_admin_buffer_begin( void ); void G_admin_buffer_end( gentity_t *ent ); void G_admin_duration( int secs, char *duration, int dursize ); void G_admin_cleanup( void ); -void G_admin_namelog_cleanup( void ); -void admin_writeconfig( void ); #endif /* ifndef _G_ADMIN_H */ diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index 461c2d0..05135c4 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,16 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "g_local.h" -// from g_combat.c -extern char *modNames[ ]; - /* ================ G_SetBuildableAnim @@ -35,19 +33,17 @@ Triggers an animation client side */ void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ) { - int localAnim = anim; + int localAnim = anim | ( ent->s.legsAnim & ANIM_TOGGLEBIT ); if( force ) localAnim |= ANIM_FORCEBIT; - // don't toggle the togglebit more than once per frame + // don't flip the togglebit more than once per frame if( ent->animTime != level.time ) { - localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ); ent->animTime = level.time; + localAnim ^= ANIM_TOGGLEBIT; } - else - localAnim |= ent->s.legsAnim & ANIM_TOGGLEBIT; ent->s.legsAnim = localAnim; } @@ -71,8 +67,8 @@ G_CheckSpawnPoint Check if a spawn at a specified point is valid =============== */ -gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, - buildable_t spawn, vec3_t spawnOrigin ) +gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, + const vec3_t normal, buildable_t spawn, vec3_t spawnOrigin ) { float displacement; vec3_t mins, maxs; @@ -80,83 +76,40 @@ gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, vec3_t localOrigin; trace_t tr; - BG_FindBBoxForBuildable( spawn, mins, maxs ); + BG_BuildableBoundingBox( spawn, mins, maxs ); if( spawn == BA_A_SPAWN ) { VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX ); VectorSet( cmaxs, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX ); - displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3; + displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3 + 1.0f; VectorMA( origin, displacement, normal, localOrigin ); - - trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); - - if( tr.entityNum != ENTITYNUM_NONE ) - return &g_entities[ tr.entityNum ]; - - trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); - - if( tr.entityNum == ENTITYNUM_NONE ) - { - if( spawnOrigin != NULL ) - VectorCopy( localOrigin, spawnOrigin ); - - return NULL; - } - else - return &g_entities[ tr.entityNum ]; } else if( spawn == BA_H_SPAWN ) { - BG_FindBBoxForClass( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL ); VectorCopy( origin, localOrigin ); localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f; - - trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); - - if( tr.entityNum != ENTITYNUM_NONE ) - return &g_entities[ tr.entityNum ]; - - trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); - - if( tr.entityNum == ENTITYNUM_NONE ) - { - if( spawnOrigin != NULL ) - VectorCopy( localOrigin, spawnOrigin ); - - return NULL; - } - else - return &g_entities[ tr.entityNum ]; } + else + return NULL; - return NULL; -} + trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); -/* -================ -G_NumberOfDependants + if( tr.entityNum != ENTITYNUM_NONE ) + return &g_entities[ tr.entityNum ]; -Return number of entities that depend on this one -================ -*/ -static int G_NumberOfDependants( gentity_t *self ) -{ - int i, n = 0; - gentity_t *ent; + trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, ENTITYNUM_NONE, MASK_PLAYERSOLID ); - for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) - { - if( ent->s.eType != ET_BUILDABLE ) - continue; + if( tr.entityNum != ENTITYNUM_NONE ) + return &g_entities[ tr.entityNum ]; - if( ent->parentNode == self ) - n++; - } + if( spawnOrigin != NULL ) + VectorCopy( localOrigin, spawnOrigin ); - return n; + return NULL; } #define POWER_REFRESH_TIME 2000 @@ -168,191 +121,403 @@ G_FindPower attempt to find power for self, return qtrue if successful ================ */ -static qboolean G_FindPower( gentity_t *self ) +qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned ) { - int i; - gentity_t *ent; + int i, j; + gentity_t *ent, *ent2; gentity_t *closestPower = NULL; int distance = 0; - int minDistance = 10000; + int minDistance = REPEATER_BASESIZE + 1; vec3_t temp_v; - if( self->biteam != BIT_HUMANS ) + if( self->buildableTeam != TEAM_HUMANS ) return qfalse; - //reactor is always powered + // Reactor is always powered if( self->s.modelindex == BA_H_REACTOR ) - return qtrue; + { + self->parentNode = self; - //if this already has power then stop now - if( self->parentNode && self->parentNode->powered ) return qtrue; + } - //reset parent - self->parentNode = NULL; + // Handle repeaters + if( self->s.modelindex == BA_H_REPEATER ) + { + self->parentNode = G_Reactor( ); - //iterate through entities - for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + return self->parentNode != NULL; + } + + // Iterate through entities + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; - //if entity is a power item calculate the distance to it + // If entity is a power item calculate the distance to it if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && - ent->spawned ) + ( searchUnspawned || ent->spawned ) && ent->powered && ent->health > 0 ) { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v ); distance = VectorLength( temp_v ); - if( distance < minDistance && ent->powered && - ( ( ent->s.modelindex == BA_H_REACTOR && - distance <= REACTOR_BASESIZE ) || - ( ent->s.modelindex == BA_H_REPEATER && - distance <= REPEATER_BASESIZE ) ) ) { + // Always prefer a reactor if there is one in range + if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) + { + // Only power as much BP as the reactor can hold + if( self->s.modelindex != BA_NONE ) + { + int buildPoints = g_humanBuildPoints.integer; + + // Scan the buildables in the reactor zone + for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) + { + if( ent2->s.eType != ET_BUILDABLE ) + continue; + + if( ent2 == self ) + continue; + + if( ent2->parentNode == ent ) + { + buildPoints -= BG_Buildable( ent2->s.modelindex )->buildPoints; + } + } + + buildPoints -= level.humanBuildPointQueue; + + buildPoints -= BG_Buildable( self->s.modelindex )->buildPoints; + + if( buildPoints >= 0 ) + { + self->parentNode = ent; + return qtrue; + } + else + { + // a buildable can still be built if it shares BP from two zones + + // TODO: handle combined power zones here + } + } + + // Dummy buildables don't need to look for zones + else + { + self->parentNode = ent; + return qtrue; + } + } + else if( distance < minDistance ) + { + // It's a repeater, so check that enough BP will be available to power + // the buildable but only if self is a real buildable + + if( self->s.modelindex != BA_NONE ) + { + int buildPoints = g_humanRepeaterBuildPoints.integer; + + // Scan the buildables in the repeater zone + for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) + { + if( ent2->s.eType != ET_BUILDABLE ) + continue; + + if( ent2 == self ) + continue; + + if( ent2->parentNode == ent ) + buildPoints -= BG_Buildable( ent2->s.modelindex )->buildPoints; + } + + if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) + buildPoints -= level.buildPointZones[ ent->buildPointZone ].queuedBuildPoints; + + buildPoints -= BG_Buildable( self->s.modelindex )->buildPoints; + + if( buildPoints >= 0 ) + { + closestPower = ent; + minDistance = distance; + } + else + { + // a buildable can still be built if it shares BP from two zones + // TODO: handle combined power zones here + } + } + else + { + // Dummy buildables don't need to look for zones closestPower = ent; minDistance = distance; + } } } } - //if there were no power items nearby give up - if( closestPower ) { - self->parentNode = closestPower; - return qtrue; - } - else - return qfalse; + self->parentNode = closestPower; + return self->parentNode != NULL; } /* ================ -G_IsPowered +G_PowerEntityForPoint -Simple wrapper to G_FindPower to check if a location has power +Simple wrapper to G_FindPower to find the entity providing +power for the specified point ================ */ -qboolean G_IsPowered( vec3_t origin ) +gentity_t *G_PowerEntityForPoint( const vec3_t origin ) { gentity_t dummy; dummy.parentNode = NULL; - dummy.biteam = BIT_HUMANS; + dummy.buildableTeam = TEAM_HUMANS; dummy.s.modelindex = BA_NONE; - VectorCopy( origin, dummy.s.origin ); + VectorCopy( origin, dummy.r.currentOrigin ); - return G_FindPower( &dummy ); + if( G_FindPower( &dummy, qfalse ) ) + return dummy.parentNode; + else + return NULL; } /* ================ -G_FindDCC +G_PowerEntityForEntity -attempt to find a controlling DCC for self, return qtrue if successful +Simple wrapper to G_FindPower to find the entity providing +power for the specified entity ================ */ -static qboolean G_FindDCC( gentity_t *self ) +gentity_t *G_PowerEntityForEntity( gentity_t *ent ) +{ + if( G_FindPower( ent, qfalse ) ) + return ent->parentNode; + return NULL; +} + +/* +================ +G_IsPowered + +Check if a location has power, returning the entity type +that is providing it +================ +*/ +buildable_t G_IsPowered( vec3_t origin ) +{ + gentity_t *ent = G_PowerEntityForPoint( origin ); + + if( ent ) + return ent->s.modelindex; + else + return BA_NONE; +} + + +/* +================== +G_GetBuildPoints + +Get the number of build points from a position +================== +*/ +int G_GetBuildPoints( const vec3_t pos, team_t team ) +{ + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + return 0; + } + else if( team == TEAM_ALIENS ) + { + return level.alienBuildPoints; + } + else if( team == TEAM_HUMANS ) + { + gentity_t *powerPoint = G_PowerEntityForPoint( pos ); + + if( powerPoint && powerPoint->s.modelindex == BA_H_REACTOR ) + return level.humanBuildPoints; + + if( powerPoint && powerPoint->s.modelindex == BA_H_REPEATER && + powerPoint->usesBuildPointZone && level.buildPointZones[ powerPoint->buildPointZone ].active ) + { + return level.buildPointZones[ powerPoint->buildPointZone ].totalBuildPoints - + level.buildPointZones[ powerPoint->buildPointZone ].queuedBuildPoints; + } + + // Return the BP of the main zone by default + return level.humanBuildPoints; + } + + return 0; +} + +/* +================== +G_GetMarkedBuildPoints + +Get the number of marked build points from a position +================== +*/ +int G_GetMarkedBuildPoints( const vec3_t pos, team_t team ) { - int i; gentity_t *ent; - gentity_t *closestDCC = NULL; - int distance = 0; - int minDistance = 10000; - vec3_t temp_v; - qboolean foundDCC = qfalse; + int i; + int sum = 0; - if( self->biteam != BIT_HUMANS ) - return qfalse; + if( G_TimeTilSuddenDeath( ) <= 0 ) + return 0; - //if this already has dcc then stop now - if( self->dccNode && self->dccNode->powered ) - return qtrue; + if( !g_markDeconstruct.integer ) + return 0; - //reset parent - self->dccNode = NULL; + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; - //iterate through entities - for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + if( team == TEAM_HUMANS && + ent->s.modelindex != BA_H_REACTOR && + ent->s.modelindex != BA_H_REPEATER && + ent->parentNode != G_PowerEntityForPoint( pos ) ) + continue; + + if( !ent->inuse ) + continue; + + if( ent->health <= 0 ) + continue; + + if( ent->buildableTeam != team ) + continue; + + if( ent->deconstruct ) + sum += BG_Buildable( ent->s.modelindex )->buildPoints; + } + + return sum; +} + +/* +================== +G_InPowerZone + +See if a buildable is inside of another power zone. +Return pointer to provider if so. +It's different from G_FindPower because FindPower for +providers will find themselves. +(This doesn't check if power zones overlap) +================== +*/ +gentity_t *G_InPowerZone( gentity_t *self ) +{ + int i; + gentity_t *ent; + int distance; + vec3_t temp_v; + + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; - //if entity is a dcc calculate the distance to it - if( ent->s.modelindex == BA_H_DCC && ent->spawned ) + if( ent == self ) + continue; + + if( !ent->spawned ) + continue; + + if( ent->health <= 0 ) + continue; + + // if entity is a power item calculate the distance to it + if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && + ent->spawned && ent->powered ) { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v ); distance = VectorLength( temp_v ); - if( ( !foundDCC || distance < minDistance ) && ent->powered ) - { - closestDCC = ent; - minDistance = distance; - foundDCC = qtrue; - } + + if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) + return ent; + else if( ent->s.modelindex == BA_H_REPEATER && distance <= REPEATER_BASESIZE ) + return ent; } } - //if there was no nearby DCC give up - if( !foundDCC ) - return qfalse; - - self->dccNode = closestDCC; - - return qtrue; + return NULL; } /* ================ -G_IsDCCBuilt +G_FindDCC -simple wrapper to G_FindDCC to check for a dcc +attempt to find a controlling DCC for self, return number found ================ */ -qboolean G_IsDCCBuilt( void ) +int G_FindDCC( gentity_t *self ) { - gentity_t dummy; + int i; + gentity_t *ent; + int distance = 0; + vec3_t temp_v; + int foundDCC = 0; - memset( &dummy, 0, sizeof( gentity_t ) ); + if( self->buildableTeam != TEAM_HUMANS ) + return 0; - dummy.dccNode = NULL; - dummy.biteam = BIT_HUMANS; + //iterate through entities + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + //if entity is a dcc calculate the distance to it + if( ent->s.modelindex == BA_H_DCC && ent->spawned ) + { + VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < DC_RANGE && ent->powered ) + { + foundDCC++; + } + } + } - return G_FindDCC( &dummy ); + return foundDCC; } /* ================ -G_FindOvermind +G_IsDCCBuilt -Attempt to find an overmind for self +See if any powered DCC exists ================ */ -static qboolean G_FindOvermind( gentity_t *self ) +qboolean G_IsDCCBuilt( void ) { int i; gentity_t *ent; - if( self->biteam != BIT_ALIENS ) - return qfalse; + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; - //if this already has overmind then stop now - if( self->overmindNode && self->overmindNode->health > 0 ) - return qtrue; + if( ent->s.modelindex != BA_H_DCC ) + continue; - //reset parent - self->overmindNode = NULL; + if( !ent->spawned ) + continue; - //iterate through entities - for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) - { - if( ent->s.eType != ET_BUILDABLE ) + if( ent->health <= 0 ) continue; - //if entity is an overmind calculate the distance to it - if( ent->s.modelindex == BA_A_OVERMIND && ent->spawned && ent->health > 0 ) - { - self->overmindNode = ent; - return qtrue; - } + return qtrue; } return qfalse; @@ -360,21 +525,46 @@ static qboolean G_FindOvermind( gentity_t *self ) /* ================ -G_IsOvermindBuilt +G_Reactor +G_Overmind + +Since there's only one of these and we quite often want to find them, cache the +results, but check them for validity each time -Simple wrapper to G_FindOvermind to check if a location has an overmind +The code here will break if more than one reactor or overmind is allowed, even +if one of them is dead/unspawned ================ */ -qboolean G_IsOvermindBuilt( void ) +static gentity_t *G_FindBuildable( buildable_t buildable ); + +gentity_t *G_Reactor( void ) { - gentity_t dummy; + static gentity_t *rc; - memset( &dummy, 0, sizeof( gentity_t ) ); + // If cache becomes invalid renew it + if( !rc || rc->s.eType != ET_BUILDABLE || rc->s.modelindex != BA_H_REACTOR ) + rc = G_FindBuildable( BA_H_REACTOR ); + + // If we found it and it's alive, return it + if( rc && rc->spawned && rc->health > 0 ) + return rc; + + return NULL; +} + +gentity_t *G_Overmind( void ) +{ + static gentity_t *om; + + // If cache becomes invalid renew it + if( !om || om->s.eType != ET_BUILDABLE || om->s.modelindex != BA_A_OVERMIND ) + om = G_FindBuildable( BA_A_OVERMIND ); - dummy.overmindNode = NULL; - dummy.biteam = BIT_ALIENS; + // If we found it and it's alive, return it + if( om && om->spawned && om->health > 0 ) + return om; - return G_FindOvermind( &dummy ); + return NULL; } /* @@ -384,7 +574,7 @@ G_FindCreep attempt to find creep for self, return qtrue if successful ================ */ -static qboolean G_FindCreep( gentity_t *self ) +qboolean G_FindCreep( gentity_t *self ) { int i; gentity_t *ent; @@ -394,21 +584,23 @@ static qboolean G_FindCreep( gentity_t *self ) vec3_t temp_v; //don't check for creep if flying through the air - if( self->s.groundEntityNum == -1 ) + if( !self->client && self->s.groundEntityNum == ENTITYNUM_NONE ) return qtrue; - //if self does not have a parentNode or it's parentNode is invalid find a new one - if( ( self->parentNode == NULL ) || !self->parentNode->inuse ) + //if self does not have a parentNode or its parentNode is invalid, then find a new one + if( self->client || self->parentNode == NULL || !self->parentNode->inuse || + self->parentNode->health <= 0 ) { - for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; - if( ( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_A_OVERMIND ) && - ent->spawned ) + if( ( ent->s.modelindex == BA_A_SPAWN || + ent->s.modelindex == BA_A_OVERMIND ) && + ent->spawned && ent->health > 0 ) { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v ); distance = VectorLength( temp_v ); if( distance < minDistance ) { @@ -420,13 +612,17 @@ static qboolean G_FindCreep( gentity_t *self ) if( minDistance <= CREEP_BASESIZE ) { - self->parentNode = closestSpawn; + if( !self->client ) + self->parentNode = closestSpawn; return qtrue; } else return qfalse; } + if( self->client ) + return qfalse; + //if we haven't returned by now then we must already have a valid parent return qtrue; } @@ -446,7 +642,7 @@ static qboolean G_IsCreepHere( vec3_t origin ) dummy.parentNode = NULL; dummy.s.modelindex = BA_NONE; - VectorCopy( origin, dummy.s.origin ); + VectorCopy( origin, dummy.r.currentOrigin ); return G_FindCreep( &dummy ); } @@ -466,12 +662,12 @@ static void G_CreepSlow( gentity_t *self ) int i, num; gentity_t *enemy; buildable_t buildable = self->s.modelindex; - float creepSize = (float)BG_FindCreepSizeForBuildable( buildable ); + float creepSize = (float)BG_Buildable( buildable )->creepSize; VectorSet( range, creepSize, creepSize, creepSize ); - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); + VectorAdd( self->r.currentOrigin, range, maxs ); + VectorSubtract( self->r.currentOrigin, range, mins ); //find humans num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -479,12 +675,11 @@ static void G_CreepSlow( gentity_t *self ) { enemy = &g_entities[ entityList[ i ] ]; - if( enemy->flags & FL_NOTARGET ) - continue; + if( enemy->flags & FL_NOTARGET ) + continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && - enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && - G_Visible( self, enemy ) ) + if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) { enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; enemy->client->lastCreepSlowTime = level.time; @@ -503,41 +698,27 @@ static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *a { } -static void G_BuildableDeathSound( gentity_t *self ) -{ - gentity_t *snd = G_TempEntity( self->r.currentOrigin, EV_GENERAL_SOUND ); - snd->s.eventParm = G_SoundIndex( BG_FindTeamForBuildable( self->s.modelindex ) == PTE_HUMANS ? - "sound/buildables/human/destroyed" : "sound/buildables/alien/construct2" ); -} - -/* -================ -freeBuildable -================ -*/ -static void freeBuildable( gentity_t *self ) -{ - G_FreeEntity( self ); -} - - //================================================================================== /* ================ -A_CreepRecede +AGeneric_CreepRecede -Called when an alien spawn dies +Called when an alien buildable dies ================ */ -void A_CreepRecede( gentity_t *self ) +void AGeneric_CreepRecede( gentity_t *self ) { //if the creep just died begin the recession if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; + G_QueueBuildPoints( self ); + + G_RewardAttackers( self ); + G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) @@ -546,7 +727,7 @@ void A_CreepRecede( gentity_t *self ) self->s.time = -( level.time - (int)( (float)CREEP_SCALEDOWN_TIME * ( 1.0f - ( (float)( level.time - self->buildTime ) / - (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) ); + (float)BG_Buildable( self->s.modelindex )->buildTime ) ) ) ); } //creep is still receeding @@ -556,71 +737,30 @@ void A_CreepRecede( gentity_t *self ) G_FreeEntity( self ); } - - - -//================================================================================== - - - - -/* -================ -ASpawn_Melt - -Called when an alien spawn dies -================ -*/ -void ASpawn_Melt( gentity_t *self ) -{ - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); - - //start creep recession - if( !( self->s.eFlags & EF_DEAD ) ) - { - self->s.eFlags |= EF_DEAD; - G_AddEvent( self, EV_BUILD_DESTROY, 0 ); - - if( self->spawned ) - self->s.time = -level.time; - else - self->s.time = -( level.time - - (int)( (float)CREEP_SCALEDOWN_TIME * - ( 1.0f - ( (float)( level.time - self->buildTime ) / - (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) ); - } - - //not dead yet - if( ( self->timestamp + 10000 ) > level.time ) - self->nextthink = level.time + 500; - else //dead now - G_FreeEntity( self ); -} - /* ================ -ASpawn_Blast +AGeneric_Blast -Called when an alien spawn dies +Called when an Alien buildable explodes after dead state ================ */ -void ASpawn_Blast( gentity_t *self ) +void AGeneric_Blast( gentity_t *self ) { - vec3_t dir; + vec3_t dir; VectorCopy( self->s.origin2, dir ); //do a bit of radius damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); + G_SelectiveRadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath, + TEAM_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); self->timestamp = level.time; - self->think = ASpawn_Melt; - self->nextthink = level.time + 500; //wait .5 seconds before damaging others + self->think = AGeneric_CreepRecede; + self->nextthink = level.time + 500; self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink @@ -628,72 +768,91 @@ void ASpawn_Blast( gentity_t *self ) /* ================ -ASpawn_Die +AGeneric_Die -Called when an alien spawn dies +Called when an Alien buildable is killed and enters a brief dead state prior to +exploding. ================ */ -void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +void AGeneric_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { - buildHistory_t *new; - new = G_Alloc( sizeof( buildHistory_t ) ); - new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; - new->ent = ( attacker && attacker->client ) ? attacker : NULL; - if( new->ent ) - new->name[ 0 ] = 0; - else - Q_strncpyz( new->name, "", 8 ); - new->buildable = self->s.modelindex; - VectorCopy( self->s.pos.trBase, new->origin ); - VectorCopy( self->s.angles, new->angles ); - VectorCopy( self->s.origin2, new->origin2 ); - VectorCopy( self->s.angles2, new->angles2 ); - new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; - new->next = NULL; - G_LogBuild( new ); - - G_BuildableDeathSound( self ); - G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; - self->think = ASpawn_Blast; + self->killedBy = attacker - g_entities; + self->think = AGeneric_Blast; + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + self->powered = qfalse; if( self->spawned ) self->nextthink = level.time + 5000; else self->nextthink = level.time; //blast immediately - self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + G_RemoveRangeMarkerFrom( self ); + G_LogDestruction( self, attacker, mod ); +} + +/* +================ +AGeneric_CreepCheck + +Tests for creep and kills the buildable if there is none +================ +*/ +void AGeneric_CreepCheck( gentity_t *self ) +{ + gentity_t *spawn; - if( attacker && attacker->client ) + spawn = self->parentNode; + if( !G_FindCreep( self ) ) { - if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - if( self->s.modelindex == BA_A_OVERMIND ) - G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue ); - else if( self->s.modelindex == BA_A_SPAWN ) - G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue ); - } + if( spawn ) + G_Damage( self, NULL, g_entities + spawn->killedBy, NULL, NULL, + self->health, 0, MOD_NOCREEP ); else - { - G_TeamCommand( PTE_ALIENS, - va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ) ); - G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ); - } - G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", - attacker->client->ps.clientNum, self->s.modelindex, mod, - attacker->client->pers.netname, - BG_FindNameForBuildable( self->s.modelindex ), - modNames[ mod ] ); + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); + return; } + G_CreepSlow( self ); +} + +/* +================ +AGeneric_Think + +A generic think function for Alien buildables +================ +*/ +void AGeneric_Think( gentity_t *self ) +{ + self->powered = G_Overmind( ) != NULL; + self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; + AGeneric_CreepCheck( self ); +} + +/* +================ +AGeneric_Pain + +A generic pain function for Alien buildables +================ +*/ +void AGeneric_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if( self->health <= 0 ) + return; + + // Alien buildables only have the first pain animation defined + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); } + + + +//================================================================================== + /* ================ ASpawn_Think @@ -708,16 +867,21 @@ void ASpawn_Think( gentity_t *self ) if( self->spawned ) { //only suicide if at rest - if( self->s.groundEntityNum ) + if( self->s.groundEntityNum != ENTITYNUM_NONE ) { - if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, + if( ( ent = G_CheckSpawnPoint( self->s.number, self->r.currentOrigin, self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL ) { // If the thing blocking the spawn is a buildable, kill it. // If it's part of the map, kill self. if( ent->s.eType == ET_BUILDABLE ) { - G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + // don't queue the bp from this + if( ent->builtBy && ent->builtBy->slot >= 0 ) + G_Damage( ent, NULL, g_entities + ent->builtBy->slot, NULL, NULL, 10000, 0, MOD_SUICIDE ); + else + G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); } else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) @@ -725,61 +889,16 @@ void ASpawn_Think( gentity_t *self ) G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } - else if( g_antiSpawnBlock.integer && ent->client && - ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - //spawnblock protection - if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 ) - { - //five seconds of countermeasures and we're still blocked - //time for something more drastic - G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT ); - self->spawnBlockTime += 2000; - //inappropriate MOD but prints an apt obituary - } - else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 ) - //five seconds of blocked by client and... - { - //random direction - vec3_t velocity; - velocity[0] = crandom() * g_antiSpawnBlock.integer; - velocity[1] = crandom() * g_antiSpawnBlock.integer; - velocity[2] = g_antiSpawnBlock.integer; - - VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity ); - trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" ); - } - else if( !self->spawnBlockTime ) - self->spawnBlockTime = level.time; - } - if( ent->s.eType == ET_CORPSE ) - G_FreeEntity( ent ); //quietly remove + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove } - else - self->spawnBlockTime = 0; } } G_CreepSlow( self ); - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); -} - -/* -================ -ASpawn_Pain - -pain function for Alien Spawn -================ -*/ -void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage ) -{ - G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); - - if ( self->s.modelindex == BA_A_OVERMIND && self->health > 0 && - attacker && attacker->client && attacker->client->pers.teamSelection == PTE_ALIENS ) - G_TeamCommand( PTE_ALIENS, va( "print \"Overmind ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", - attacker->client->pers.netname )); + self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; } @@ -805,39 +924,26 @@ Think function for Alien Overmind */ void AOvermind_Think( gentity_t *self ) { - int entityList[ MAX_GENTITIES ]; - vec3_t range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE }; - vec3_t mins, maxs; - int i, num; - gentity_t *enemy; - - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); + int i; if( self->spawned && ( self->health > 0 ) ) { //do some damage - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) + if( G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, MOD_OVERMIND, TEAM_ALIENS ) ) { - enemy = &g_entities[ entityList[ i ] ]; - - if( enemy->flags & FL_NOTARGET ) - continue; - - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - self->timestamp = level.time; - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS ); - G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - } + self->timestamp = level.time; + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); } // just in case an egg finishes building after we tell overmind to stfu if( level.numAlienSpawns > 0 ) level.overmindMuted = qfalse; + // shut up during intermission + if( level.intermissiontime ) + level.overmindMuted = qtrue; + //low on spawns if( !level.overmindMuted && level.numAlienSpawns <= 0 && level.time > self->overmindSpawnsTimer ) @@ -885,14 +991,13 @@ void AOvermind_Think( gentity_t *self ) G_CreepSlow( self ); - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; } - //================================================================================== @@ -903,12 +1008,15 @@ void AOvermind_Think( gentity_t *self ) ================ ABarricade_Pain -pain function for Alien Spawn +Barricade pain animation depends on shrunk state ================ */ void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage ) { - if( rand( ) % 2 ) + if( self->health <= 0 ) + return; + + if( !self->shrunkTime ) G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); else G_SetBuildableAnim( self, BANIM_PAIN2, qfalse ); @@ -916,90 +1024,90 @@ void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage ) /* ================ -ABarricade_Blast +ABarricade_Shrink -Called when an alien spawn dies +Set shrink state for a barricade. When unshrinking, checks to make sure there +is enough room. ================ */ -void ABarricade_Blast( gentity_t *self ) +void ABarricade_Shrink( gentity_t *self, qboolean shrink ) { - vec3_t dir; + if ( !self->spawned || self->health <= 0 ) + shrink = qtrue; + if ( shrink && self->shrunkTime ) + { + int anim; - VectorCopy( self->s.origin2, dir ); + // We need to make sure that the animation has been set to shrunk mode + // because we start out shrunk but with the construct animation when built + self->shrunkTime = level.time; + anim = self->s.torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if ( self->spawned && self->health > 0 && anim != BANIM_DESTROYED ) + { + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); + } + return; + } - //do a bit of radius damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); + if ( !shrink && ( !self->shrunkTime || + level.time < self->shrunkTime + BARRICADE_SHRINKTIMEOUT ) ) + return; - //pretty events and item cleanup - self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed - G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); - self->timestamp = level.time; - self->think = A_CreepRecede; - self->nextthink = level.time + 500; //wait .5 seconds before damaging others + BG_BuildableBoundingBox( BA_A_BARRICADE, self->r.mins, self->r.maxs ); - self->r.contents = 0; //stop collisions... - trap_LinkEntity( self ); //...requires a relink + if ( shrink ) + { + self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); + self->shrunkTime = level.time; + + // shrink animation, the destroy animation is used + if ( self->spawned && self->health > 0 ) + { + G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + } + } + else + { + trace_t tr; + int anim; + + trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, + self->r.currentOrigin, self->s.number, MASK_PLAYERSOLID ); + if ( tr.startsolid || tr.fraction < 1.0f ) + { + self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); + return; + } + self->shrunkTime = 0; + + // unshrink animation, IDLE2 has been hijacked for this + anim = self->s.legsAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if ( self->spawned && self->health > 0 && + anim != BANIM_CONSTRUCT1 && anim != BANIM_CONSTRUCT2 ) + { + G_SetIdleBuildableAnim( self, BG_Buildable( BA_A_BARRICADE )->idleAnim ); + G_SetBuildableAnim( self, BANIM_ATTACK2, qtrue ); + } + } + + // a change in size requires a relink + if ( self->spawned ) + trap_LinkEntity( self ); } /* ================ ABarricade_Die -Called when an alien spawn dies +Called when an alien barricade dies ================ */ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { - buildHistory_t *new; - new = G_Alloc( sizeof( buildHistory_t ) ); - new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; - new->ent = ( attacker && attacker->client ) ? attacker : NULL; - if( new->ent ) - new->name[ 0 ] = 0; - else - Q_strncpyz( new->name, "", 8 ); - new->buildable = self->s.modelindex; - VectorCopy( self->s.pos.trBase, new->origin ); - VectorCopy( self->s.angles, new->angles ); - VectorCopy( self->s.origin2, new->origin2 ); - VectorCopy( self->s.angles2, new->angles2 ); - new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; - new->next = NULL; - G_LogBuild( new ); - - G_BuildableDeathSound( self ); - - G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); - G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); - - self->die = nullDieFunction; - self->think = ABarricade_Blast; - self->s.eFlags &= ~EF_FIRING; //prevent any firing effects - - if( self->spawned ) - self->nextthink = level.time + 5000; - else - self->nextthink = level.time; //blast immediately - - if( attacker && attacker->client ) - { - if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - G_TeamCommand( PTE_ALIENS, - va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ) ); - G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ); - } - G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", - attacker->client->ps.clientNum, self->s.modelindex, mod, - attacker->client->pers.netname, - BG_FindNameForBuildable( self->s.modelindex ), - modNames[ mod ] ); - } + AGeneric_Die( self, inflictor, attacker, damage, mod ); + ABarricade_Shrink( self, qtrue ); } /* @@ -1011,65 +1119,43 @@ Think function for Alien Barricade */ void ABarricade_Think( gentity_t *self ) { + AGeneric_Think( self ); - self->powered = G_IsOvermindBuilt( ); - - //if there is no creep nearby die - if( !G_FindCreep( self ) ) - { - G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - return; - } - - G_CreepSlow( self ); - - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + // Shrink if unpowered + ABarricade_Shrink( self, !self->powered ); } - - - -//================================================================================== - - - - -void AAcidTube_Think( gentity_t *self ); - /* ================ -AAcidTube_Damage +ABarricade_Touch -Damage function for Alien Acid Tube +Barricades shrink when they are come into contact with an Alien that can +pass through ================ */ -void AAcidTube_Damage( gentity_t *self ) + +void ABarricade_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { - if( self->spawned ) - { - if( !( self->s.eFlags & EF_FIRING ) ) - { - self->s.eFlags |= EF_FIRING; - G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); - } + gclient_t *client = other->client; + int client_z, min_z; - if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time ) - self->think = AAcidTube_Damage; - else - { - self->think = AAcidTube_Think; - self->s.eFlags &= ~EF_FIRING; - } + if( !client || client->pers.teamSelection != TEAM_ALIENS ) + return; + + // Client must be high enough to pass over. Note that STEPSIZE (18) is + // hardcoded here because we don't include bg_local.h! + client_z = other->r.currentOrigin[ 2 ] + other->r.mins[ 2 ]; + min_z = self->r.currentOrigin[ 2 ] - 18 + + (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); + if( client_z < min_z ) + return; + ABarricade_Shrink( self, qtrue ); +} + +//================================================================================== - //do some damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); - } - G_CreepSlow( self ); - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); -} /* ================ @@ -1086,21 +1172,14 @@ void AAcidTube_Think( gentity_t *self ) int i, num; gentity_t *enemy; - self->powered = G_IsOvermindBuilt( ); + AGeneric_Think( self ); - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); + VectorAdd( self->r.currentOrigin, range, maxs ); + VectorSubtract( self->r.currentOrigin, range, mins ); - //if there is no creep nearby die - if( !G_FindCreep( self ) ) - { - G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - return; - } - - if( self->spawned && G_FindOvermind( self ) ) + // attack nearby humans + if( self->spawned && self->health > 0 && self->powered ) { - //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { @@ -1109,25 +1188,26 @@ void AAcidTube_Think( gentity_t *self ) if( enemy->flags & FL_NOTARGET ) continue; - if( !G_Visible( self, enemy ) ) + if( !G_Visible( self, enemy, CONTENTS_SOLID ) ) continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - if( level.paused || enemy->client->pers.paused ) - continue; - self->timestamp = level.time; - self->think = AAcidTube_Damage; - self->nextthink = level.time + 100; - G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + // start the attack animation + if( level.time >= self->timestamp + ACIDTUBE_REPEAT_ANIM ) + { + self->timestamp = level.time; + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); + } + + G_SelectiveRadiusDamage( self->s.pos.trBase, self, ACIDTUBE_DAMAGE, + ACIDTUBE_RANGE, self, MOD_ATUBE, TEAM_ALIENS ); + self->nextthink = level.time + ACIDTUBE_REPEAT; return; } } } - - G_CreepSlow( self ); - - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } @@ -1135,8 +1215,50 @@ void AAcidTube_Think( gentity_t *self ) //================================================================================== +/* +================ +AHive_CheckTarget + +Returns true and fires the hive missile if the target is valid +================ +*/ +static qboolean AHive_CheckTarget( gentity_t *self, gentity_t *enemy ) +{ + trace_t trace; + vec3_t tip_origin, dirToTarget; + + // Check if this is a valid target + if( enemy->health <= 0 || !enemy->client || + enemy->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return qfalse; + + if( enemy->flags & FL_NOTARGET ) + return qfalse; + + // Check if the tip of the hive can see the target + VectorMA( self->s.pos.trBase, self->r.maxs[ 2 ], self->s.origin2, + tip_origin ); + if( Distance( tip_origin, enemy->r.currentOrigin ) > HIVE_SENSE_RANGE ) + return qfalse; + + trap_Trace( &trace, tip_origin, NULL, NULL, enemy->s.pos.trBase, + self->s.number, MASK_SHOT ); + if( trace.fraction < 1.0f && trace.entityNum != enemy->s.number ) + return qfalse; + + self->active = qtrue; + self->target_ent = enemy; + self->timestamp = level.time + HIVE_REPEAT; + VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); + VectorNormalize( dirToTarget ); + vectoangles( dirToTarget, self->turretAim ); + // Fire at target + FireWeapon( self ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + return qtrue; +} /* ================ @@ -1147,353 +1269,57 @@ Think function for Alien Hive */ void AHive_Think( gentity_t *self ) { - int entityList[ MAX_GENTITIES ]; - vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE }; - vec3_t mins, maxs; - int i, num; - gentity_t *enemy; - vec3_t dirToTarget; - - self->powered = G_IsOvermindBuilt( ); - - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + int start; - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); - - //if there is no creep nearby die - if( !G_FindCreep( self ) ) - { - G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - return; - } + AGeneric_Think( self ); + // Hive missile hasn't returned in HIVE_REPEAT seconds, forget about it if( self->timestamp < level.time ) - self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it + self->active = qfalse; - if( self->spawned && !self->active && G_FindOvermind( self ) ) + // Find a target to attack + if( self->spawned && !self->active && self->powered ) { - //do some damage - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - enemy = &g_entities[ entityList[ i ] ]; + int i, num, entityList[ MAX_GENTITIES ]; + vec3_t mins, maxs, + range = { HIVE_SENSE_RANGE, HIVE_SENSE_RANGE, HIVE_SENSE_RANGE }; - if( enemy->flags & FL_NOTARGET ) - continue; + VectorAdd( self->r.currentOrigin, range, maxs ); + VectorSubtract( self->r.currentOrigin, range, mins ); - if( enemy->health <= 0 ) - continue; + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - if( !G_Visible( self, enemy ) ) - continue; + if( num == 0 ) + return; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - if( level.paused || enemy->client->pers.paused ) - continue; - self->active = qtrue; - self->target_ent = enemy; - self->timestamp = level.time + HIVE_REPEAT; - - VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); - VectorNormalize( dirToTarget ); - vectoangles( dirToTarget, self->turretAim ); - - //fire at target - FireWeapon( self ); - G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - return; - } - } - } - - G_CreepSlow( self ); -} - - - - -//================================================================================== - - - - -#define HOVEL_TRACE_DEPTH 128.0f - -/* -================ -AHovel_Blocked - -Is this hovel entrance blocked? -================ -*/ -qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit ) -{ - vec3_t forward, normal, origin, start, end, angles, hovelMaxs; - vec3_t mins, maxs; - float displacement; - trace_t tr; - - BG_FindBBoxForBuildable( BA_A_HOVEL, NULL, hovelMaxs ); - BG_FindBBoxForClass( player->client->ps.stats[ STAT_PCLASS ], - mins, maxs, NULL, NULL, NULL ); - - VectorCopy( hovel->s.origin2, normal ); - AngleVectors( hovel->s.angles, forward, NULL, NULL ); - VectorInverse( forward ); - - displacement = VectorMaxComponent( maxs ) + - VectorMaxComponent( hovelMaxs ) + 1.0f; - - VectorMA( hovel->s.origin, displacement, forward, origin ); - - VectorCopy( hovel->s.origin, start ); - VectorCopy( origin, end ); - - // see if there's something between the hovel and its exit - // (eg built right up against a wall) - trap_Trace( &tr, start, NULL, NULL, end, player->s.number, MASK_PLAYERSOLID ); - if( tr.fraction < 1.0f ) - return qtrue; - - vectoangles( forward, angles ); - - VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start ); - - //compute a place up in the air to start the real trace - trap_Trace( &tr, origin, mins, maxs, start, player->s.number, MASK_PLAYERSOLID ); - - VectorMA( origin, ( HOVEL_TRACE_DEPTH * tr.fraction ) - 1.0f, normal, start ); - VectorMA( origin, -HOVEL_TRACE_DEPTH, normal, end ); - - trap_Trace( &tr, start, mins, maxs, end, player->s.number, MASK_PLAYERSOLID ); - - VectorCopy( tr.endpos, origin ); - - trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID ); - - if( provideExit ) - { - G_SetOrigin( player, origin ); - VectorCopy( origin, player->client->ps.origin ); - // nudge - VectorMA( normal, 200.0f, forward, player->client->ps.velocity ); - G_SetClientViewAngle( player, angles ); - } - - if( tr.fraction < 1.0f ) - return qtrue; - else - return qfalse; -} - -/* -================ -APropHovel_Blocked - -Wrapper to test a hovel placement for validity -================ -*/ -static qboolean APropHovel_Blocked( vec3_t origin, vec3_t angles, vec3_t normal, - gentity_t *player ) -{ - gentity_t hovel; - - VectorCopy( origin, hovel.s.origin ); - VectorCopy( angles, hovel.s.angles ); - VectorCopy( normal, hovel.s.origin2 ); - - return AHovel_Blocked( &hovel, player, qfalse ); -} - -/* -================ -AHovel_Use - -Called when an alien uses a hovel -================ -*/ -void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) -{ - vec3_t hovelOrigin, hovelAngles, inverseNormal; - - if( self->spawned && G_FindOvermind( self ) ) - { - if( self->active ) - { - //this hovel is in use - G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_OCCUPIED ); - } - else if( ( ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ) || - ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) && - activator->health > 0 && self->health > 0 ) + start = rand( ) / ( RAND_MAX / num + 1 ); + for( i = start; i < num + start; i++ ) { - if( AHovel_Blocked( self, activator, qfalse ) ) - { - //you can get in, but you can't get out - G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); + if( AHive_CheckTarget( self, g_entities + entityList[ i % num ] ) ) return; - } - - self->active = qtrue; - G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - - //prevent lerping - activator->client->ps.eFlags ^= EF_TELEPORT_BIT; - activator->client->ps.eFlags |= EF_NODRAW; - G_UnlaggedClear( activator ); - - activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING; - activator->client->hovel = self; - self->builder = activator; - - // Cancel pending suicides - activator->suicideTime = 0; - - VectorCopy( self->s.pos.trBase, hovelOrigin ); - VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin ); - - VectorCopy( self->s.origin2, inverseNormal ); - VectorInverse( inverseNormal ); - vectoangles( inverseNormal, hovelAngles ); - - VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin ); - - G_SetOrigin( activator, hovelOrigin ); - VectorCopy( hovelOrigin, activator->client->ps.origin ); - G_SetClientViewAngle( activator, hovelAngles ); } } } - -/* -================ -AHovel_Think - -Think for alien hovel -================ -*/ -void AHovel_Think( gentity_t *self ) -{ - self->powered = G_IsOvermindBuilt( ); - if( self->spawned ) - { - if( self->active ) - G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); - else - G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); - } - - G_CreepSlow( self ); - - self->nextthink = level.time + 200; -} - /* ================ -AHovel_Die +AHive_Pain -Die for alien hovel +pain function for Alien Hive ================ */ -void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +void AHive_Pain( gentity_t *self, gentity_t *attacker, int damage ) { - vec3_t dir; - - buildHistory_t *new; - new = G_Alloc( sizeof( buildHistory_t ) ); - new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; - new->ent = ( attacker && attacker->client ) ? attacker : NULL; - if( new->ent ) - new->name[ 0 ] = 0; - else - Q_strncpyz( new->name, "", 8 ); - new->buildable = self->s.modelindex; - VectorCopy( self->s.pos.trBase, new->origin ); - VectorCopy( self->s.angles, new->angles ); - VectorCopy( self->s.origin2, new->origin2 ); - VectorCopy( self->s.angles2, new->angles2 ); - new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; - new->next = NULL; - G_LogBuild( new ); - - G_BuildableDeathSound( self ); - - VectorCopy( self->s.origin2, dir ); - - //do a bit of radius damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); - - //pretty events and item cleanup - self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed - G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); - self->s.eFlags &= ~EF_FIRING; //prevent any firing effects - self->timestamp = level.time; - self->think = ASpawn_Melt; - self->nextthink = level.time + 500; //wait .5 seconds before damaging others - self->die = nullDieFunction; - - //if the hovel is occupied free the occupant - if( self->active ) - { - gentity_t *builder = self->builder; - vec3_t newOrigin; - vec3_t newAngles; - - VectorCopy( self->s.angles, newAngles ); - newAngles[ ROLL ] = 0; - - VectorCopy( self->s.origin, newOrigin ); - VectorMA( newOrigin, 1.0f, self->s.origin2, newOrigin ); + if( self->spawned && self->powered && !self->active ) + AHive_CheckTarget( self, attacker ); - //prevent lerping - builder->client->ps.eFlags ^= EF_TELEPORT_BIT; - builder->client->ps.eFlags &= ~EF_NODRAW; - G_UnlaggedClear( builder ); - - G_SetOrigin( builder, newOrigin ); - VectorCopy( newOrigin, builder->client->ps.origin ); - G_SetClientViewAngle( builder, newAngles ); - - //client leaves hovel - builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; - } - - self->r.contents = 0; //stop collisions... - trap_LinkEntity( self ); //...requires a relink - - if( attacker && attacker->client ) - { - if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - G_TeamCommand( PTE_ALIENS, - va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ) ); - G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ); - } - G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", - attacker->client->ps.clientNum, self->s.modelindex, mod, - attacker->client->pers.netname, - BG_FindNameForBuildable( self->s.modelindex ), - modNames[ mod ] ); - } + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); } - - - //================================================================================== - - /* ================ ABooster_Touch @@ -1505,20 +1331,20 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { gclient_t *client = other->client; - if( !self->spawned || self->health <= 0 ) - return; - - if( !G_FindOvermind( self ) ) + if( !self->spawned || !self->powered || self->health <= 0 ) return; if( !client ) return; - if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) return; + if( other->flags & FL_NOTARGET ) + return; // notarget cancels even beneficial effects? + client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; - client->lastBoostedTime = level.time; + client->boostedTime = level.time; } @@ -1540,11 +1366,11 @@ void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range ) gentity_t *enemy = self->enemy; vec3_t dirToTarget; vec3_t halfAcceleration, thirdJerk; - float distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex ); + float distanceToTarget = BG_Buildable( self->s.modelindex )->turretRange; int lowMsec = 0; int highMsec = (int)( ( ( ( distanceToTarget * LOCKBLOB_SPEED ) + - ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) / + ( distanceToTarget * BG_Class( enemy->client->ps.stats[ STAT_CLASS ] )->speed ) ) / ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f ); VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration ); @@ -1603,9 +1429,9 @@ qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range ) return qfalse; if( target->flags & FL_NOTARGET ) // is the target cheating? return qfalse; - if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us? + if( target->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) // one of us? return qfalse; - if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive? + if( target->client->sess.spectatorState != SPECTATOR_NOT ) // is the target alive? return qfalse; if( target->health <= 0 ) // is the target still alive? return qfalse; @@ -1638,10 +1464,14 @@ Used by ATrapper_Think to locate enemy gentities void ATrapper_FindEnemy( gentity_t *ent, int range ) { gentity_t *target; + int i; + int start; - //iterate through entities - for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ ) + // iterate through entities + start = rand( ) / ( RAND_MAX / level.num_entities + 1 ); + for( i = start; i < level.num_entities + start; i++ ) { + target = g_entities + ( i % level.num_entities ); //if target is not valid keep searching if( !ATrapper_CheckTarget( ent, target, range ) ) continue; @@ -1664,23 +1494,12 @@ think function for Alien Defense */ void ATrapper_Think( gentity_t *self ) { - int range = BG_FindRangeForBuildable( self->s.modelindex ); - int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); - - self->powered = G_IsOvermindBuilt( ); - - G_CreepSlow( self ); - - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + int range = BG_Buildable( self->s.modelindex )->turretRange; + int firespeed = BG_Buildable( self->s.modelindex )->turretFireSpeed; - //if there is no creep nearby die - if( !G_FindCreep( self ) ) - { - G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - return; - } + AGeneric_Think( self ); - if( self->spawned && G_FindOvermind( self ) ) + if( self->spawned && self->powered ) { //if the current target is not valid find a new one if( !ATrapper_CheckTarget( self, self->enemy, range ) ) @@ -1698,173 +1517,418 @@ void ATrapper_Think( gentity_t *self ) + //================================================================================== + /* ================ -HRepeater_Think +G_SuicideIfNoPower -Think for human power repeater +Destroy human structures that have been unpowered too long ================ */ -void HRepeater_Think( gentity_t *self ) +static qboolean G_SuicideIfNoPower( gentity_t *self ) { - int i; - qboolean reactor = qfalse; - gentity_t *ent; + if( self->buildableTeam != TEAM_HUMANS ) + return qfalse; - if( self->spawned ) + if( !self->powered ) { - //iterate through entities - for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + // if the power hasn't reached this buildable for some time, then destroy the buildable + if( self->count == 0 ) + self->count = level.time; + else if( ( level.time - self->count ) >= HUMAN_BUILDABLE_INACTIVE_TIME ) { - if( ent->s.eType != ET_BUILDABLE ) - continue; - - if( ent->s.modelindex == BA_H_REACTOR && ent->spawned ) - reactor = qtrue; + if( self->parentNode ) + G_Damage( self, NULL, g_entities + self->parentNode->killedBy, + NULL, NULL, self->health, 0, MOD_NOCREEP ); + else + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); + return qtrue; } } - - if( G_NumberOfDependants( self ) == 0 ) - { - //if no dependants for x seconds then disappear - if( self->count < 0 ) - self->count = level.time; - else if( self->count > 0 && ( ( level.time - self->count ) > REPEATER_INACTIVE_TIME ) ) - G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - } else - self->count = -1; - - self->powered = reactor; + self->count = 0; - self->nextthink = level.time + POWER_REFRESH_TIME; + return qfalse; } /* ================ -HRepeater_Use +G_IdlePowerState -Use for human power repeater +Set buildable idle animation to match power state ================ */ -void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +static void G_IdlePowerState( gentity_t *self ) { - if( self->health <= 0 ) - return; + if( self->powered ) + { + if( self->s.torsoAnim == BANIM_IDLE3 ) + G_SetIdleBuildableAnim( self, BG_Buildable( self->s.modelindex )->idleAnim ); + } + else + { + if( self->s.torsoAnim != BANIM_IDLE3 ) + G_SetIdleBuildableAnim( self, BANIM_IDLE3 ); + } +} + - if( !self->spawned ) - return; - if( other ) - G_GiveClientMaxAmmo( other, qtrue ); -} + +//================================================================================== + -#define DCC_ATTACK_PERIOD 10000 /* ================ -HReactor_Think +HSpawn_Disappear -Think function for Human Reactor +Called when a human spawn is destroyed before it is spawned +think function ================ */ -void HReactor_Think( gentity_t *self ) +void HSpawn_Disappear( gentity_t *self ) { - int entityList[ MAX_GENTITIES ]; - vec3_t range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE }; - vec3_t mins, maxs; - int i, num; - gentity_t *enemy, *tent; - - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); - - if( self->spawned && ( self->health > 0 ) ) - { - //do some damage - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - enemy = &g_entities[ entityList[ i ] ]; - - if( enemy->flags & FL_NOTARGET ) - continue; - - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - if( level.paused || enemy->client->pers.paused ) - continue; - self->timestamp = level.time; - G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE, - REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS ); - - tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); - - VectorCopy( self->s.pos.trBase, tent->s.origin2 ); - - tent->s.generic1 = self->s.number; //src - tent->s.clientNum = enemy->s.number; //dest - } - } - - //reactor under attack - if( self->health < self->lastHealth && - level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) - { - level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; - G_BroadcastEvent( EV_DCC_ATTACK, 0 ); - } - - self->lastHealth = self->health; - } + self->timestamp = level.time; + G_QueueBuildPoints( self ); + G_RewardAttackers( self ); - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + G_FreeEntity( self ); } + /* ================ -HReactor_Pain +HSpawn_blast + +Called when a human spawn explodes +think function ================ */ - -void HReactor_Pain( gentity_t *self, gentity_t *attacker, int damage) +void HSpawn_Blast( gentity_t *self ) { - if (self->health <= 0) - return; + vec3_t dir; - if (!attacker || !attacker->client) - return; + // we don't have a valid direction, so just point straight up + dir[ 0 ] = dir[ 1 ] = 0; + dir[ 2 ] = 1; - if (attacker->client->pers.teamSelection != PTE_HUMANS) - return; + self->timestamp = level.time; - G_TeamCommand(PTE_HUMANS, va( "print \"Reactor ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", - attacker->client->pers.netname)); + //do some radius damage + G_RadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath ); + + // begin freeing build points + G_QueueBuildPoints( self ); + G_RewardAttackers( self ); + // turn into an explosion + self->s.eType = ET_EVENTS + EV_HUMAN_BUILDABLE_EXPLOSION; + self->freeAfterEvent = qtrue; + G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); } -//================================================================================== - - /* ================ -HArmoury_Activate +HSpawn_die -Called when a human activates an Armoury +Called when a human spawn dies ================ */ -void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator ) +void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { - if( self->spawned ) - { - //only humans can activate this - if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) - return; + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + + self->die = nullDieFunction; + self->killedBy = attacker - g_entities; + self->powered = qfalse; //free up power + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + + if( self->spawned ) + { + self->think = HSpawn_Blast; + self->nextthink = level.time + HUMAN_DETONATION_DELAY; + } + else + { + self->think = HSpawn_Disappear; + self->nextthink = level.time; //blast immediately + } + + G_RemoveRangeMarkerFrom( self ); + G_LogDestruction( self, attacker, mod ); +} + +/* +================ +HSpawn_Think + +Think for human spawn +================ +*/ +void HSpawn_Think( gentity_t *self ) +{ + gentity_t *ent; + + // set parentNode + self->powered = G_FindPower( self, qfalse ); + + if( G_SuicideIfNoPower( self ) ) + return; + + if( self->spawned ) + { + //only suicide if at rest + if( self->s.groundEntityNum != ENTITYNUM_NONE ) + { + if( ( ent = G_CheckSpawnPoint( self->s.number, self->r.currentOrigin, + self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) + { + // If the thing blocking the spawn is a buildable, kill it. + // If it's part of the map, kill self. + if( ent->s.eType == ET_BUILDABLE ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); + } + else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) + { + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + return; + } + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove + } + } + } + + self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; +} + + + + +//================================================================================== + + + + +/* +================ +HRepeater_Die + +Called when a repeater dies +================ +*/ +static void HRepeater_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + + self->die = nullDieFunction; + self->killedBy = attacker - g_entities; + self->powered = qfalse; //free up power + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + + if( self->spawned ) + { + self->think = HSpawn_Blast; + self->nextthink = level.time + HUMAN_DETONATION_DELAY; + } + else + { + self->think = HSpawn_Disappear; + self->nextthink = level.time; //blast immediately + } + + G_RemoveRangeMarkerFrom( self ); + G_LogDestruction( self, attacker, mod ); + + if( self->usesBuildPointZone ) + { + buildPointZone_t *zone = &level.buildPointZones[self->buildPointZone]; + + zone->active = qfalse; + self->usesBuildPointZone = qfalse; + } +} + +/* +================ +HRepeater_Think + +Think for human power repeater +================ +*/ +void HRepeater_Think( gentity_t *self ) +{ + int i; + gentity_t *powerEnt; + buildPointZone_t *zone; + + self->powered = G_FindPower( self, qfalse ); + + powerEnt = G_InPowerZone( self ); + if( powerEnt != NULL ) + { + // If the repeater is inside of another power zone then suicide + // Attribute death to whoever built the reactor if that's a human, + // which will ensure that it does not queue the BP + if( powerEnt->builtBy && powerEnt->builtBy->slot >= 0 ) + G_Damage( self, NULL, g_entities + powerEnt->builtBy->slot, NULL, NULL, self->health, 0, MOD_SUICIDE ); + else + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + return; + } + + G_IdlePowerState( self ); + + // Initialise the zone once the repeater has spawned + if( self->spawned && ( !self->usesBuildPointZone || !level.buildPointZones[ self->buildPointZone ].active ) ) + { + // See if a free zone exists + for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) + { + zone = &level.buildPointZones[ i ]; + + if( !zone->active ) + { + // Initialise the BP queue with no BP queued + zone->queuedBuildPoints = 0; + zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; + zone->nextQueueTime = level.time; + zone->active = qtrue; + + self->buildPointZone = zone - level.buildPointZones; + self->usesBuildPointZone = qtrue; + + break; + } + } + } + + self->nextthink = level.time + POWER_REFRESH_TIME; +} + +/* +================ +HRepeater_Use + +Use for human power repeater +================ +*/ +void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->health <= 0 || !self->spawned ) + return; + + if( other && other->client ) + G_GiveClientMaxAmmo( other, qtrue ); +} + +/* +================ +HReactor_Think + +Think function for Human Reactor +================ +*/ +void HReactor_Think( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range = { REACTOR_ATTACK_RANGE, + REACTOR_ATTACK_RANGE, + REACTOR_ATTACK_RANGE }; + vec3_t dccrange = { REACTOR_ATTACK_DCC_RANGE, + REACTOR_ATTACK_DCC_RANGE, + REACTOR_ATTACK_DCC_RANGE }; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy, *tent; + + if( self->dcc ) + { + VectorAdd( self->r.currentOrigin, dccrange, maxs ); + VectorSubtract( self->r.currentOrigin, dccrange, mins ); + } + else + { + VectorAdd( self->r.currentOrigin, range, maxs ); + VectorSubtract( self->r.currentOrigin, range, mins ); + } + + if( self->spawned && ( self->health > 0 ) ) + { + qboolean fired = qfalse; + + // Creates a tesla trail for every target + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + enemy = &g_entities[ entityList[ i ] ]; + if( !enemy->client || + enemy->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + continue; + if( enemy->flags & FL_NOTARGET ) + continue; + + tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); + tent->s.misc = self->s.number; //src + tent->s.clientNum = enemy->s.number; //dest + VectorCopy( self->s.pos.trBase, tent->s.origin2 ); + fired = qtrue; + } + + // Actual damage is done by radius + if( fired ) + { + self->timestamp = level.time; + if( self->dcc ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, + REACTOR_ATTACK_DCC_DAMAGE, + REACTOR_ATTACK_DCC_RANGE, self, + MOD_REACTOR, TEAM_HUMANS ); + else + G_SelectiveRadiusDamage( self->s.pos.trBase, self, + REACTOR_ATTACK_DAMAGE, + REACTOR_ATTACK_RANGE, self, + MOD_REACTOR, TEAM_HUMANS ); + } + } + + if( self->dcc ) + self->nextthink = level.time + REACTOR_ATTACK_DCC_REPEAT; + else + self->nextthink = level.time + REACTOR_ATTACK_REPEAT; +} + +//================================================================================== + + + +/* +================ +HArmoury_Activate + +Called when a human activates an Armoury +================ +*/ +void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->spawned ) + { + //only humans can activate this + if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; //if this is powered then call the armoury menu if( self->powered ) @@ -1886,7 +1950,9 @@ void HArmoury_Think( gentity_t *self ) //make sure we have power self->nextthink = level.time + POWER_REFRESH_TIME; - self->powered = G_FindPower( self ); + self->powered = G_FindPower( self, qfalse ); + + G_SuicideIfNoPower( self ); } @@ -1910,7 +1976,9 @@ void HDCC_Think( gentity_t *self ) //make sure we have power self->nextthink = level.time + POWER_REFRESH_TIME; - self->powered = G_FindPower( self ); + self->powered = G_FindPower( self, qfalse ); + + G_SuicideIfNoPower( self ); } @@ -1918,6 +1986,26 @@ void HDCC_Think( gentity_t *self ) //================================================================================== + + + +/* +================ +HMedistat_Die + +Die function for Human Medistation +================ +*/ +void HMedistat_Die( gentity_t *self, gentity_t *inflictor, + gentity_t *attacker, int damage, int mod ) +{ + //clear target's healing flag + if( self->enemy && self->enemy->client ) + self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + + HSpawn_Die( self, inflictor, attacker, damage, mod ); +} + /* ================ HMedistat_Think @@ -1933,15 +2021,22 @@ void HMedistat_Think( gentity_t *self ) gentity_t *player; qboolean occupied = qfalse; - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; + + self->powered = G_FindPower( self, qfalse ); + if( G_SuicideIfNoPower( self ) ) + return; + G_IdlePowerState( self ); + + //clear target's healing flag + if( self->enemy && self->enemy->client ) + self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; //make sure we have power - if( !( self->powered = G_FindPower( self ) ) ) + if( !self->powered ) { if( self->active ) { - G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); - G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); self->active = qfalse; self->enemy = NULL; } @@ -1952,8 +2047,8 @@ void HMedistat_Think( gentity_t *self ) if( self->spawned ) { - VectorAdd( self->s.origin, self->r.maxs, maxs ); - VectorAdd( self->s.origin, self->r.mins, mins ); + VectorAdd( self->r.currentOrigin, self->r.maxs, maxs ); + VectorAdd( self->r.currentOrigin, self->r.mins, mins ); mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ]; maxs[ 2 ] += 60; //player height @@ -1961,19 +2056,27 @@ void HMedistat_Think( gentity_t *self ) //if active use the healing idle if( self->active ) G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); - + //check if a previous occupier is still here num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { player = &g_entities[ entityList[ i ] ]; - if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( player->flags & FL_NOTARGET ) + continue; // notarget cancels even beneficial effects? + + //remove poison from everyone, not just the healed player + if( player->client && player->client->ps.stats[ STAT_STATE ] & SS_POISONED ) + player->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + if( self->enemy == player && player->client && + player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && + PM_Alive( player->client->ps.pm_type ) ) { - if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && - player->client->ps.pm_type != PM_DEAD && - self->enemy == player ) - occupied = qtrue; + occupied = qtrue; + player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; } } @@ -1986,13 +2089,14 @@ void HMedistat_Think( gentity_t *self ) { player = &g_entities[ entityList[ i ] ]; - if( player->flags & FL_NOTARGET ) - continue; // notarget cancels even beneficial effects? + if( player->flags & FL_NOTARGET ) + continue; // notarget cancels even beneficial effects? - if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( player->client && player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && - player->client->ps.pm_type != PM_DEAD ) + if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] || + player->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) && + PM_Alive( player->client->ps.pm_type ) ) { self->enemy = player; @@ -2001,6 +2105,7 @@ void HMedistat_Think( gentity_t *self ) { G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->active = qtrue; + player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; } } else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) @@ -2017,26 +2122,25 @@ void HMedistat_Think( gentity_t *self ) self->active = qfalse; } - else if( self->enemy ) //heal! + else if( self->enemy && self->enemy->client ) //heal! { - if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED ) - self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + if( self->enemy->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) + self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE; - if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) - self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; + if( self->enemy->client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) + self->enemy->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; - self->enemy->health++; + if( self->enemy->health < self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] ) + { + self->enemy->health++; + self->enemy->client->ps.stats[ STAT_HEALTH ] = self->enemy->health; + } //if they're completely healed, give them a medkit - if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] && - !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) - BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); - - // if completely healed, cancel retribution if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] ) { - for( i = 0; i < MAX_CLIENTS; i++ ) - self->enemy->client->tkcredits[ i ] = 0; + if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) + BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); } } } @@ -2050,6 +2154,39 @@ void HMedistat_Think( gentity_t *self ) +/* +================ +HMGTurret_CheckTarget + +Used by HMGTurret_Think to check enemies for validity +================ +*/ +qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, + qboolean los_check ) +{ + trace_t tr; + vec3_t dir, end; + + if( !target || target->health <= 0 || !target->client || + target->client->pers.teamSelection != TEAM_ALIENS ) + return qfalse; + + if( target->flags & FL_NOTARGET ) + return qfalse; + + if( !los_check ) + return qtrue; + + // Accept target if we can line-trace to it + VectorSubtract( target->s.pos.trBase, self->s.pos.trBase, dir ); + VectorNormalize( dir ); + VectorMA( self->s.pos.trBase, MGTURRET_RANGE, dir, end ); + trap_Trace( &tr, self->s.pos.trBase, NULL, NULL, end, + self->s.number, MASK_SHOT ); + return tr.entityNum == target - g_entities; +} + + /* ================ HMGTurret_TrackEnemy @@ -2062,27 +2199,8 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self ) vec3_t dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal; vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; float temp, rotAngle; - float accuracyTolerance, angularSpeed; - - if( self->lev1Grabbed ) - { - //can't turn fast if grabbed - accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE; - angularSpeed = MGTURRET_GRAB_ANGULARSPEED; - } - else if( self->dcced ) - { - accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE; - angularSpeed = MGTURRET_DCC_ANGULARSPEED; - } - else - { - accuracyTolerance = MGTURRET_ACCURACYTOLERANCE; - angularSpeed = MGTURRET_ANGULARSPEED; - } VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); - VectorNormalize( dirToTarget ); CrossProduct( self->s.origin2, refNormal, xNormal ); @@ -2096,10 +2214,10 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self ) angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] ); //if not pointing at our target then move accordingly - if( angularDiff[ PITCH ] < (-accuracyTolerance) ) - self->s.angles2[ PITCH ] += angularSpeed; - else if( angularDiff[ PITCH ] > accuracyTolerance ) - self->s.angles2[ PITCH ] -= angularSpeed; + if( angularDiff[ PITCH ] < 0 && angularDiff[ PITCH ] < (-MGTURRET_ANGULARSPEED) ) + self->s.angles2[ PITCH ] += MGTURRET_ANGULARSPEED; + else if( angularDiff[ PITCH ] > 0 && angularDiff[ PITCH ] > MGTURRET_ANGULARSPEED ) + self->s.angles2[ PITCH ] -= MGTURRET_ANGULARSPEED; else self->s.angles2[ PITCH ] = angleToTarget[ PITCH ]; @@ -2112,10 +2230,10 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self ) self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP; //if not pointing at our target then move accordingly - if( angularDiff[ YAW ] < (-accuracyTolerance) ) - self->s.angles2[ YAW ] += angularSpeed; - else if( angularDiff[ YAW ] > accuracyTolerance ) - self->s.angles2[ YAW ] -= angularSpeed; + if( angularDiff[ YAW ] < 0 && angularDiff[ YAW ] < ( -MGTURRET_ANGULARSPEED ) ) + self->s.angles2[ YAW ] += MGTURRET_ANGULARSPEED; + else if( angularDiff[ YAW ] > 0 && angularDiff[ YAW ] > MGTURRET_ANGULARSPEED ) + self->s.angles2[ YAW ] -= MGTURRET_ANGULARSPEED; else self->s.angles2[ YAW ] = angleToTarget[ YAW ]; @@ -2123,132 +2241,110 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self ) RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle ); vectoangles( dirToTarget, self->turretAim ); - //if pointing at our target return true - if( fabs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance && - fabs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance ) - return qtrue; - - return qfalse; + //fire if target is within accuracy + return ( fabs( angularDiff[ YAW ] ) - MGTURRET_ANGULARSPEED <= + MGTURRET_ACCURACY_TO_FIRE ) && + ( fabs( angularDiff[ PITCH ] ) - MGTURRET_ANGULARSPEED <= + MGTURRET_ACCURACY_TO_FIRE ); } /* ================ -HMGTurret_CheckTarget +HMGTurret_FindEnemy -Used by HMGTurret_Think to check enemies for validity +Used by HMGTurret_Think to locate enemy gentities ================ */ -qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted ) +void HMGTurret_FindEnemy( gentity_t *self ) { - trace_t trace; - gentity_t *traceEnt; - - if( !target ) - return qfalse; - - if( target->flags & FL_NOTARGET ) - return qfalse; - - if( !target->client ) - return qfalse; - - if( level.paused || target->client->pers.paused ) - return qfalse; - - if( target->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) - return qfalse; - - if( target->health <= 0 ) - return qfalse; - - if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE ) - return qfalse; - - //some turret has already selected this target - if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted ) - return qfalse; - - trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT ); + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *target; + int start; - traceEnt = &g_entities[ trace.entityNum ]; + self->enemy = NULL; + + // Look for targets in a box around the turret + VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); + VectorAdd( self->r.currentOrigin, range, maxs ); + VectorSubtract( self->r.currentOrigin, range, mins ); + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - if( !traceEnt->client ) - return qfalse; + if( num == 0 ) + return; - if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) - return qfalse; + start = rand( ) / ( RAND_MAX / num + 1 ); + for( i = start; i < num + start ; i++ ) + { + target = &g_entities[ entityList[ i % num ] ]; + if( !HMGTurret_CheckTarget( self, target, qtrue ) ) + continue; - return qtrue; + self->enemy = target; + return; + } } - /* ================ -HMGTurret_FindEnemy +HMGTurret_State -Used by HMGTurret_Think to locate enemy gentities +Raise or lower MG turret towards desired state ================ */ -void HMGTurret_FindEnemy( gentity_t *self ) +enum { + MGT_STATE_INACTIVE, + MGT_STATE_DROP, + MGT_STATE_RISE, + MGT_STATE_ACTIVE +}; + +static qboolean HMGTurret_State( gentity_t *self, int state ) { - int entityList[ MAX_GENTITIES ]; - vec3_t range; - vec3_t mins, maxs; - int i, num; - gentity_t *target; + float angle; - VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); + if( self->waterlevel == state ) + return qfalse; - //find aliens - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - target = &g_entities[ entityList[ i ] ]; + angle = AngleNormalize180( self->s.angles2[ PITCH ] ); - if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( state == MGT_STATE_INACTIVE ) + { + if( angle < MGTURRET_VERTICALCAP ) { - //if target is not valid keep searching - if( !HMGTurret_CheckTarget( self, target, qfalse ) ) - continue; + if( self->waterlevel != MGT_STATE_DROP ) + { + self->speed = 0.25f; + self->waterlevel = MGT_STATE_DROP; + } + else + self->speed *= 1.25f; - //we found a target - self->enemy = target; - return; + self->s.angles2[ PITCH ] = + MIN( MGTURRET_VERTICALCAP, angle + self->speed ); + return qtrue; } + else + self->waterlevel = MGT_STATE_INACTIVE; } - - if( self->dcced ) + else if( state == MGT_STATE_ACTIVE ) { - //check again, this time ignoring painted targets - for( i = 0; i < num; i++ ) + if( !self->enemy && angle > 0.0f ) { - target = &g_entities[ entityList[ i ] ]; - - if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - //if target is not valid keep searching - if( !HMGTurret_CheckTarget( self, target, qtrue ) ) - continue; - - //we found a target - self->enemy = target; - return; - } + self->waterlevel = MGT_STATE_RISE; + self->s.angles2[ PITCH ] = + MAX( 0.0f, angle - MGTURRET_ANGULARSPEED * 0.5f ); } + else + self->waterlevel = MGT_STATE_ACTIVE; } - //couldn't find a target - self->enemy = NULL; + return qfalse; } -#define MGTURRET_DROOPSCALE 0.5f -#define TURRET_REST_TIME 5000 -#define TURRET_REST_SPEED 3.0f -#define TURRET_REST_TOLERANCE 4.0f - /* ================ HMGTurret_Think @@ -2258,69 +2354,73 @@ Think function for MG turret */ void HMGTurret_Think( gentity_t *self ) { - int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); - - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + self->nextthink = level.time + + BG_Buildable( self->s.modelindex )->nextthink; - //used for client side muzzle flashes + // Turn off client side muzzle flashes self->s.eFlags &= ~EF_FIRING; - //if not powered don't do anything and check again for power next think - if( !( self->powered = G_FindPower( self ) ) ) - { - if( self->spawned ) - { - // unpowered turret barrel falls to bottom of range - float droop; + self->powered = G_FindPower( self, qfalse ); + if( G_SuicideIfNoPower( self ) ) + return; + G_IdlePowerState( self ); - droop = AngleNormalize180( self->s.angles2[ PITCH ] ); - if( droop < MGTURRET_VERTICALCAP ) - { - droop += MGTURRET_DROOPSCALE; - if( droop > MGTURRET_VERTICALCAP ) - droop = MGTURRET_VERTICALCAP; - self->s.angles2[ PITCH ] = droop; - return; - } - } + // If not powered or spawned don't do anything + if( !self->powered ) + { + // if power loss drop turret + if( self->spawned && + HMGTurret_State( self, MGT_STATE_INACTIVE ) ) + return; self->nextthink = level.time + POWER_REFRESH_TIME; return; } - - if( self->spawned ) + if( !self->spawned ) + return; + + // If the current target is not valid find a new enemy + if( !HMGTurret_CheckTarget( self, self->enemy, qtrue ) ) { - //find a dcc for self - self->dcced = G_FindDCC( self ); - - //if the current target is not valid find a new one - if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) ) - { - if( self->enemy ) - self->enemy->targeted = NULL; - - HMGTurret_FindEnemy( self ); - } + self->active = qfalse; + self->turretSpinupTime = -1; + HMGTurret_FindEnemy( self ); + } + // if newly powered raise turret + HMGTurret_State( self, MGT_STATE_ACTIVE ); + if( !self->enemy ) + return; - //if a new target cannot be found don't do anything - if( !self->enemy ) - return; + // Track until we can hit the target + if( !HMGTurret_TrackEnemy( self ) ) + { + self->active = qfalse; + self->turretSpinupTime = -1; + return; + } - self->enemy->targeted = self; + // Update spin state + if( !self->active && self->timestamp < level.time ) + { + self->active = qtrue; - //if we are pointing at our target and we can fire shoot it - if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) ) - { - //fire at target - FireWeapon( self ); + self->turretSpinupTime = level.time + MGTURRET_SPINUP_TIME; + G_AddEvent( self, EV_MGTURRET_SPINUP, 0 ); + } - self->s.eFlags |= EF_FIRING; - G_AddEvent( self, EV_FIRE_WEAPON, 0 ); - G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + // Not firing or haven't spun up yet + if( !self->active || self->turretSpinupTime > level.time ) + return; + + // Fire repeat delay + if( self->timestamp > level.time ) + return; - self->count = level.time + firespeed; - } - } + FireWeapon( self ); + self->s.eFlags |= EF_FIRING; + self->timestamp = level.time + BG_Buildable( self->s.modelindex )->turretFireSpeed; + G_AddEvent( self, EV_FIRE_WEAPON, 0 ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); } @@ -2340,54 +2440,51 @@ Think function for Tesla Generator */ void HTeslaGen_Think( gentity_t *self ) { - int entityList[ MAX_GENTITIES ]; - vec3_t range; - vec3_t mins, maxs; - vec3_t dir; - int i, num; - gentity_t *enemy; + self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + self->powered = G_FindPower( self, qfalse ); + if( G_SuicideIfNoPower( self ) ) + return; + G_IdlePowerState( self ); //if not powered don't do anything and check again for power next think - if( !( self->powered = G_FindPower( self ) ) || !( self->dcced = G_FindDCC( self ) ) ) + if( !self->powered ) { self->s.eFlags &= ~EF_FIRING; self->nextthink = level.time + POWER_REFRESH_TIME; return; } - if( self->spawned && self->count < level.time ) + if( self->spawned && self->timestamp < level.time ) { - //used to mark client side effects + vec3_t origin, range, mins, maxs; + int entityList[ MAX_GENTITIES ], i, num; + + // Communicates firing state to client self->s.eFlags &= ~EF_FIRING; + // Move the muzzle from the entity origin up a bit to fire over turrets + VectorMA( self->r.currentOrigin, self->r.maxs[ 2 ], self->s.origin2, origin ); + VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); - //find aliens + // Attack nearby Aliens num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { - enemy = &g_entities[ entityList[ i ] ]; + self->enemy = &g_entities[ entityList[ i ] ]; - if( enemy->flags & FL_NOTARGET ) + if( self->enemy->flags & FL_NOTARGET ) continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && - enemy->health > 0 && - !level.paused && !enemy->client->pers.paused && - Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE ) - { - VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir ); - VectorNormalize( dir ); - vectoangles( dir, self->turretAim ); - - //fire at target + if( self->enemy->client && self->enemy->health > 0 && + self->enemy->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && + Distance( origin, self->enemy->s.pos.trBase ) <= TESLAGEN_RANGE ) FireWeapon( self ); - } } + self->enemy = NULL; if( self->s.eFlags & EF_FIRING ) { @@ -2396,7 +2493,7 @@ void HTeslaGen_Think( gentity_t *self ) //doesn't really need an anim //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - self->count = level.time + TESLAGEN_REPEAT; + self->timestamp = level.time + TESLAGEN_REPEAT; } } } @@ -2410,227 +2507,124 @@ void HTeslaGen_Think( gentity_t *self ) /* -================ -HSpawn_Disappear - -Called when a human spawn is destroyed before it is spawned -think function -================ +============ +G_QueueValue +============ */ -void HSpawn_Disappear( gentity_t *self ) + +static int G_QueueValue( gentity_t *self ) { - vec3_t dir; + int i; + int damageTotal = 0; + int queuePoints; + double queueFraction = 0; - // we don't have a valid direction, so just point straight up - dir[ 0 ] = dir[ 1 ] = 0; - dir[ 2 ] = 1; + for( i = 0; i < level.maxclients; i++ ) + { + gentity_t *player = g_entities + i; - self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed - self->timestamp = level.time; + damageTotal += self->credits[ i ]; - self->think = freeBuildable; - self->nextthink = level.time + 100; + if( self->buildableTeam != player->client->pers.teamSelection ) + queueFraction += (double) self->credits[ i ]; + } - self->r.contents = 0; //stop collisions... - trap_LinkEntity( self ); //...requires a relink -} + if( damageTotal > 0 ) + queueFraction = queueFraction / (double) damageTotal; + else // all damage was done by nonclients, so queue everything + queueFraction = 1.0; + queuePoints = (int) ( queueFraction * (double) BG_Buildable( self->s.modelindex )->buildPoints ); + return queuePoints; +} /* -================ -HSpawn_blast - -Called when a human spawn explodes -think function -================ +============ +G_QueueBuildPoints +============ */ -void HSpawn_Blast( gentity_t *self ) +void G_QueueBuildPoints( gentity_t *self ) { - vec3_t dir; + gentity_t *powerEntity; + int queuePoints; - // we don't have a valid direction, so just point straight up - dir[ 0 ] = dir[ 1 ] = 0; - dir[ 2 ] = 1; + queuePoints = G_QueueValue( self ); - self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed - G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); - self->timestamp = level.time; + if( !queuePoints ) + return; + + switch( self->buildableTeam ) + { + default: + case TEAM_NONE: + return; - //do some radius damage - G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, 0, self->splashMethodOfDeath ); + case TEAM_ALIENS: + if( !level.alienBuildPointQueue ) + level.alienNextQueueTime = level.time + g_alienBuildQueueTime.integer; - self->think = freeBuildable; - self->nextthink = level.time + 100; + level.alienBuildPointQueue += queuePoints; + break; - self->r.contents = 0; //stop collisions... - trap_LinkEntity( self ); //...requires a relink -} + case TEAM_HUMANS: + powerEntity = G_PowerEntityForEntity( self ); + if( powerEntity ) + { + int nqt; + switch( powerEntity->s.modelindex ) + { + case BA_H_REACTOR: + nqt = G_NextQueueTime( level.humanBuildPointQueue, + g_humanBuildPoints.integer, + g_humanBuildQueueTime.integer ); + if( !level.humanBuildPointQueue || + level.time + nqt < level.humanNextQueueTime ) + level.humanNextQueueTime = level.time + nqt; + + level.humanBuildPointQueue += queuePoints; + break; -/* -================ -HSpawn_die + case BA_H_REPEATER: + if( powerEntity->usesBuildPointZone && + level.buildPointZones[ powerEntity->buildPointZone ].active ) + { + buildPointZone_t *zone = &level.buildPointZones[ powerEntity->buildPointZone ]; -Called when a human spawn dies -================ -*/ -void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) -{ - buildHistory_t *new; - new = G_Alloc( sizeof( buildHistory_t ) ); - new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; - new->ent = ( attacker && attacker->client ) ? attacker : NULL; - if( new->ent ) - new->name[ 0 ] = 0; - else - Q_strncpyz( new->name, "", 8 ); - new->buildable = self->s.modelindex; - VectorCopy( self->s.pos.trBase, new->origin ); - VectorCopy( self->s.angles, new->angles ); - VectorCopy( self->s.origin2, new->origin2 ); - VectorCopy( self->s.angles2, new->angles2 ); - new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ? BF_TEAMKILLED : BF_DESTROYED; - new->next = NULL; - G_LogBuild( new ); - - G_BuildableDeathSound( self ); - - //pretty events and cleanup - G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); - G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + nqt = G_NextQueueTime( zone->queuedBuildPoints, + zone->totalBuildPoints, + g_humanRepeaterBuildQueueTime.integer ); - self->die = nullDieFunction; - self->powered = qfalse; //free up power - //prevent any firing effects and cancel structure protection - self->s.eFlags &= ~( EF_FIRING | EF_DBUILDER ); + if( !zone->queuedBuildPoints || + level.time + nqt < zone->nextQueueTime ) + zone->nextQueueTime = level.time + nqt; - if( self->spawned ) - { - self->think = HSpawn_Blast; - self->nextthink = level.time + HUMAN_DETONATION_DELAY; - } - else - { - self->think = HSpawn_Disappear; - self->nextthink = level.time; //blast immediately - } + zone->queuedBuildPoints += queuePoints; + } + break; - if( attacker && attacker->client ) - { - if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - if( self->s.modelindex == BA_H_REACTOR ) - G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue ); - else if( self->s.modelindex == BA_H_SPAWN ) - G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue ); - } - else - { - G_TeamCommand( PTE_HUMANS, - va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ) ); - G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", - BG_FindHumanNameForBuildable( self->s.modelindex ), - attacker->client->pers.netname ); - } - G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", - attacker->client->ps.clientNum, self->s.modelindex, mod, - attacker->client->pers.netname, - BG_FindNameForBuildable( self->s.modelindex ), - modNames[ mod ] ); + default: + break; + } + } } } /* -================ -HSpawn_Think - -Think for human spawn -================ +============ +G_NextQueueTime +============ */ -void HSpawn_Think( gentity_t *self ) +int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate ) { - gentity_t *ent; + float fractionQueued; - // spawns work without power - self->powered = qtrue; - - if( self->spawned ) - { - //only suicide if at rest - if( self->s.groundEntityNum ) - { - if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, - self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) - { - // If the thing blocking the spawn is a buildable, kill it. - // If it's part of the map, kill self. - if( ent->s.eType == ET_BUILDABLE ) - { - G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); - } - else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) - { - G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); - return; - } - else if( g_antiSpawnBlock.integer && ent->client && - ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - //spawnblock protection - if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 ) - { - //five seconds of countermeasures and we're still blocked - //time for something more drastic - G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT ); - self->spawnBlockTime += 2000; - //inappropriate MOD but prints an apt obituary - } - else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 ) - //five seconds of blocked by client and... - { - //random direction - vec3_t velocity; - velocity[0] = crandom() * g_antiSpawnBlock.integer; - velocity[1] = crandom() * g_antiSpawnBlock.integer; - velocity[2] = g_antiSpawnBlock.integer; - - VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity ); - trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" ); - } - else if( !self->spawnBlockTime ) - self->spawnBlockTime = level.time; - } - - if( ent->s.eType == ET_CORPSE ) - G_FreeEntity( ent ); //quietly remove - } - else - self->spawnBlockTime = 0; - } - - //spawn under attack - if( self->health < self->lastHealth && - level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) - { - level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; - G_BroadcastEvent( EV_DCC_ATTACK, 0 ); - } - - self->lastHealth = self->health; - } - - self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); -} - - - - -//================================================================================== + if( totalBP == 0 ) + return 0; + fractionQueued = queuedBP / (float)totalBP; + return ( 1.0f - fractionQueued ) * queueBaseRate; +} /* ============ @@ -2653,18 +2647,18 @@ void G_BuildableTouchTriggers( gentity_t *ent ) if( ent->health <= 0 ) return; - BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs ); + BG_BuildableBoundingBox( ent->s.modelindex, bmins, bmaxs ); - VectorAdd( ent->s.origin, bmins, mins ); - VectorAdd( ent->s.origin, bmaxs, maxs ); + VectorAdd( ent->r.currentOrigin, bmins, mins ); + VectorAdd( ent->r.currentOrigin, bmaxs, maxs ); VectorSubtract( mins, range, mins ); VectorAdd( maxs, range, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - VectorAdd( ent->s.origin, bmins, mins ); - VectorAdd( ent->s.origin, bmaxs, maxs ); + VectorAdd( ent->r.currentOrigin, bmins, mins ); + VectorAdd( ent->r.currentOrigin, bmaxs, maxs ); for( i = 0; i < num; i++ ) { @@ -2700,66 +2694,85 @@ General think function for buildables */ void G_BuildableThink( gentity_t *ent, int msec ) { - int bHealth = BG_FindHealthForBuildable( ent->s.modelindex ); - int bRegen = BG_FindRegenRateForBuildable( ent->s.modelindex ); - int bTime = BG_FindBuildTimeForBuildable( ent->s.modelindex ); - - //pack health, power and dcc + int maxHealth = BG_Buildable( ent->s.modelindex )->health; + int regenRate = BG_Buildable( ent->s.modelindex )->regenRate; + int buildTime = BG_Buildable( ent->s.modelindex )->buildTime; //toggle spawned flag for buildables - if( !ent->spawned && ent->health > 0 ) + if( !ent->spawned && ent->health > 0 && !level.pausedTime ) { - if( ent->buildTime + bTime < level.time ) + if( ent->buildTime + buildTime < level.time ) + { ent->spawned = qtrue; + if( ent->s.modelindex == BA_A_OVERMIND ) + { + G_TeamCommand( TEAM_ALIENS, "cp \"The Overmind has awakened!\"" ); + } + } } - ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK ); - - if( ent->s.generic1 < 0 ) - ent->s.generic1 = 0; - - if( ent->powered ) - ent->s.generic1 |= B_POWERED_TOGGLEBIT; - - if( ent->dcced ) - ent->s.generic1 |= B_DCCED_TOGGLEBIT; - - if( ent->spawned ) - ent->s.generic1 |= B_SPAWNED_TOGGLEBIT; - - if( ent->deconstruct ) - ent->s.generic1 |= B_MARKED_TOGGLEBIT; - + // Timer actions ent->time1000 += msec; - if( ent->time1000 >= 1000 ) { ent->time1000 -= 1000; - if( !ent->spawned && ent->health > 0 ) - ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) ); - else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth && - bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) - ent->health += bRegen; + if( ent->health > 0 && ent->health < maxHealth ) + { + if( !ent->spawned ) + ent->health += (int)( ceil( (float)maxHealth / (float)( buildTime * 0.001f ) ) ); + else + { + if( ent->buildableTeam == TEAM_ALIENS && regenRate && + ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) + { + ent->health += regenRate; + } + else if( ent->buildableTeam == TEAM_HUMANS && ent->dcc && + ( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME ) < level.time ) + { + ent->health += DC_HEALRATE * ent->dcc; + } + } - if( ent->health > bHealth ) - ent->health = bHealth; + if( ent->health >= maxHealth ) + { + int i; + ent->health = maxHealth; + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + } + } } - if( ent->lev1Grabbed && ent->lev1GrabTime + LEVEL1_GRAB_TIME < level.time ) - ent->lev1Grabbed = qfalse; - if( ent->clientSpawnTime > 0 ) ent->clientSpawnTime -= msec; if( ent->clientSpawnTime < 0 ) ent->clientSpawnTime = 0; - //check if this buildable is touching any triggers + ent->dcc = ( ent->buildableTeam != TEAM_HUMANS ) ? 0 : G_FindDCC( ent ); + + // Set health + ent->s.misc = MAX( ent->health, 0 ); + + // Set flags + ent->s.eFlags &= ~( EF_B_POWERED | EF_B_SPAWNED | EF_B_MARKED ); + if( ent->powered ) + ent->s.eFlags |= EF_B_POWERED; + + if( ent->spawned ) + ent->s.eFlags |= EF_B_SPAWNED; + + if( ent->deconstruct ) + ent->s.eFlags |= EF_B_MARKED; + + // Check if this buildable is touching any triggers G_BuildableTouchTriggers( ent ); - //fall back on normal physics routines - G_Physics( ent, msec ); + // Fall back on normal physics routines + if( msec != 0 ) + G_Physics( ent, msec ); } @@ -2790,7 +2803,7 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) if( ent->s.eType != ET_BUILDABLE ) continue; - if( ent->biteam == BIT_HUMANS && !ent->powered ) + if( ent->buildableTeam == TEAM_HUMANS && !ent->powered ) continue; if( ent->s.modelindex == buildable && ent->spawned ) @@ -2800,20 +2813,29 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) return qfalse; } -static qboolean G_BoundsIntersect(const vec3_t mins, const vec3_t maxs, - const vec3_t mins2, const vec3_t maxs2) +/* +================ +G_FindBuildable + +Finds a buildable of the specified type +================ +*/ +static gentity_t *G_FindBuildable( buildable_t buildable ) { - if ( maxs[0] < mins2[0] || - maxs[1] < mins2[1] || - maxs[2] < mins2[2] || - mins[0] > maxs2[0] || - mins[1] > maxs2[1] || - mins[2] > maxs2[2]) + int i; + gentity_t *ent; + + for( i = MAX_CLIENTS, ent = g_entities + i; + i < level.num_entities; i++, ent++ ) { - return qfalse; + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->s.modelindex == buildable && !( ent->s.eFlags & EF_DEAD ) ) + return ent; } - return qtrue; + return NULL; } /* @@ -2829,15 +2851,15 @@ static qboolean G_BuildablesIntersect( buildable_t a, vec3_t originA, vec3_t minsA, maxsA; vec3_t minsB, maxsB; - BG_FindBBoxForBuildable( a, minsA, maxsA ); + BG_BuildableBoundingBox( a, minsA, maxsA ); VectorAdd( minsA, originA, minsA ); VectorAdd( maxsA, originA, maxsA ); - BG_FindBBoxForBuildable( b, minsB, maxsB ); + BG_BuildableBoundingBox( b, minsB, maxsB ); VectorAdd( minsB, originB, minsB ); VectorAdd( maxsB, originB, maxsB ); - return G_BoundsIntersect( minsA, maxsA, minsB, maxsB ); + return BoundsIntersect( minsA, maxsA, minsB, maxsB ); } /* @@ -2860,7 +2882,6 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b ) BA_A_TRAPPER, BA_A_HIVE, BA_A_BOOSTER, - BA_A_HOVEL, BA_A_SPAWN, BA_A_OVERMIND, @@ -2882,30 +2903,55 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b ) buildableA = *(gentity_t **)a; buildableB = *(gentity_t **)b; - // Prefer the one that collides with the thing we're building aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, - buildableA->s.modelindex, buildableA->s.origin ); + buildableA->s.modelindex, buildableA->r.currentOrigin ); bMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, - buildableB->s.modelindex, buildableB->s.origin ); + buildableB->s.modelindex, buildableB->r.currentOrigin ); if( aMatches && !bMatches ) return -1; - if( !aMatches && bMatches ) + else if( !aMatches && bMatches ) return 1; + // If the only spawn is marked, prefer it last + if( cmpBuildable == BA_A_SPAWN || cmpBuildable == BA_H_SPAWN ) + { + if( ( buildableA->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || + ( buildableA->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) + return 1; + + if( ( buildableB->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || + ( buildableB->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) + return -1; + } + // If one matches the thing we're building, prefer it aMatches = ( buildableA->s.modelindex == cmpBuildable ); bMatches = ( buildableB->s.modelindex == cmpBuildable ); if( aMatches && !bMatches ) return -1; - if( !aMatches && bMatches ) + else if( !aMatches && bMatches ) return 1; - // If they're the same type then pick the one marked earliest + // They're the same type if( buildableA->s.modelindex == buildableB->s.modelindex ) + { + gentity_t *powerEntity = G_PowerEntityForPoint( cmpOrigin ); + + // Prefer the entity that is providing power for this point + aMatches = ( powerEntity == buildableA ); + bMatches = ( powerEntity == buildableB ); + if( aMatches && !bMatches ) + return -1; + else if( !aMatches && bMatches ) + return 1; + + // Pick the one marked earliest return buildableA->deconstructTime - buildableB->deconstructTime; + } - for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ ) + // Resort to preference list + for( i = 0; i < ARRAY_LEN( precedence ); i++ ) { if( buildableA->s.modelindex == precedence[ i ] ) aPrecedence = i; @@ -2917,6 +2963,30 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b ) return aPrecedence - bPrecedence; } +/* +=============== +G_ClearDeconMarks + +Remove decon mark from all buildables +=============== +*/ +void G_ClearDeconMarks( void ) +{ + int i; + gentity_t *ent; + + for( i = MAX_CLIENTS, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + ent->deconstruct = qfalse; + } +} + /* =============== G_FreeMarkedBuildables @@ -2924,12 +2994,20 @@ G_FreeMarkedBuildables Free up build points for a team by deconstructing marked buildables =============== */ -void G_FreeMarkedBuildables( void ) +void G_FreeMarkedBuildables( gentity_t *deconner, char *readable, int rsize, + char *nums, int nsize ) { int i; + int bNum; + int listItems = 0; + int totalListItems = 0; gentity_t *ent; - buildHistory_t *new, *last; - last = level.buildHistory; + int removalCounts[ BA_NUM_BUILDABLES ] = {0}; + + if( readable && rsize ) + readable[ 0 ] = '\0'; + if( nums && nsize ) + nums[ 0 ] = '\0'; if( !g_markDeconstruct.integer ) return; // Not enabled, can't deconstruct anything @@ -2937,24 +3015,43 @@ void G_FreeMarkedBuildables( void ) for( i = 0; i < level.numBuildablesForRemoval; i++ ) { ent = level.markedBuildables[ i ]; + bNum = BG_Buildable( ent->s.modelindex )->number; - new = G_Alloc( sizeof( buildHistory_t ) ); - new->ID = -1; - new->ent = NULL; - Q_strncpyz( new->name, "", 12 ); - new->buildable = ent->s.modelindex; - VectorCopy( ent->s.pos.trBase, new->origin ); - VectorCopy( ent->s.angles, new->angles ); - VectorCopy( ent->s.origin2, new->origin2 ); - VectorCopy( ent->s.angles2, new->angles2 ); - new->fate = BF_DECONNED; - new->next = NULL; - new->marked = NULL; - - last = last->marked = new; + if( removalCounts[ bNum ] == 0 ) + totalListItems++; + G_Damage( ent, NULL, deconner, NULL, NULL, ent->health, 0, MOD_REPLACE ); + + removalCounts[ bNum ]++; + + if( nums ) + Q_strcat( nums, nsize, va( " %d", (int)( ent - g_entities ) ) ); + + G_RemoveRangeMarkerFrom( ent ); G_FreeEntity( ent ); } + + if( !readable ) + return; + + for( i = 0; i < BA_NUM_BUILDABLES; i++ ) + { + if( removalCounts[ i ] ) + { + if( listItems ) + { + if( listItems == ( totalListItems - 1 ) ) + Q_strcat( readable, rsize, va( "%s and ", + ( totalListItems > 2 ) ? "," : "" ) ); + else + Q_strcat( readable, rsize, ", " ); + } + Q_strcat( readable, rsize, va( "%s", BG_Buildable( i )->humanName ) ); + if( removalCounts[ i ] > 1 ) + Q_strcat( readable, rsize, va( " (%dx)", removalCounts[ i ] ) ); + listItems++; + } + } } /* @@ -2962,20 +3059,20 @@ void G_FreeMarkedBuildables( void ) G_SufficientBPAvailable Determine if enough build points can be released for the buildable -and list the buildables that much be destroyed if this is the case +and list the buildables that must be destroyed if this is the case =============== */ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, vec3_t origin ) { - int i; - int numBuildables = 0; - int pointsYielded = 0; - gentity_t *ent; - qboolean unique = BG_FindUniqueTestForBuildable( buildable ); - int remainingBP, remainingSpawns; - int team; - int buildPoints, buildpointsneeded; + int i; + int numBuildables = 0; + int numRequired = 0; + int pointsYielded = 0; + gentity_t *ent; + team_t team = BG_Buildable( buildable )->team; + int buildPoints = BG_Buildable( buildable )->buildPoints; + int remainingBP, remainingSpawns; qboolean collision = qfalse; int collisionCount = 0; qboolean repeaterInRange = qfalse; @@ -2984,29 +3081,35 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, buildable_t spawn; buildable_t core; int spawnCount = 0; + qboolean changed = qtrue; level.numBuildablesForRemoval = 0; - buildPoints = buildpointsneeded = BG_FindBuildPointsForBuildable( buildable ); - team = BG_FindTeamForBuildable( buildable ); - if( team == BIT_ALIENS ) + if( team == TEAM_ALIENS ) { - remainingBP = level.alienBuildPoints; + remainingBP = G_GetBuildPoints( origin, team ); remainingSpawns = level.numAlienSpawns; - bpError = IBE_NOASSERT; + bpError = IBE_NOALIENBP; spawn = BA_A_SPAWN; core = BA_A_OVERMIND; } - else if( team == BIT_HUMANS ) + else if( team == TEAM_HUMANS ) { - remainingBP = level.humanBuildPoints; + if( buildable == BA_H_REACTOR || buildable == BA_H_REPEATER ) + remainingBP = level.humanBuildPoints; + else + remainingBP = G_GetBuildPoints( origin, team ); + remainingSpawns = level.numHumanSpawns; - bpError = IBE_NOPOWER; + bpError = IBE_NOHUMANBP; spawn = BA_H_SPAWN; core = BA_H_REACTOR; } else + { + Com_Error( ERR_FATAL, "team is %d", team ); return IBE_NONE; + } // Simple non-marking case if( !g_markDeconstruct.integer ) @@ -3020,18 +3123,15 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, if( ent->s.eType != ET_BUILDABLE ) continue; - if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin ) ) + if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->r.currentOrigin ) ) return IBE_NOROOM; } return IBE_NONE; } - buildpointsneeded -= remainingBP; - // Set buildPoints to the number extra that are required - if( !g_markDeconstructMode.integer ) - buildPoints -= remainingBP; + buildPoints -= remainingBP; // Build a list of buildable entities for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) @@ -3039,15 +3139,27 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, if( ent->s.eType != ET_BUILDABLE ) continue; - collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin ); + collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->r.currentOrigin ); if( collision ) + { + // Don't allow replacements at all + if( g_markDeconstruct.integer == 1 ) + return IBE_NOROOM; + + // Only allow replacements of the same type + if( g_markDeconstruct.integer == 2 && ent->s.modelindex != buildable ) + return IBE_NOROOM; + + // Any other setting means anything goes + collisionCount++; + } // Check if this is a repeater and it's in range if( buildable == BA_H_REPEATER && buildable == ent->s.modelindex && - Distance( ent->s.origin, origin ) < REPEATER_BASESIZE ) + Distance( ent->r.currentOrigin, origin ) < REPEATER_BASESIZE ) { repeaterInRange = qtrue; repeaterInRangeCount++; @@ -3055,55 +3167,76 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, else repeaterInRange = qfalse; + // Don't allow marked buildables to be replaced in another zone, + // unless the marked buildable isn't in a zone (and thus unpowered) + if( team == TEAM_HUMANS && + buildable != BA_H_REACTOR && + buildable != BA_H_REPEATER && + ent->parentNode != G_PowerEntityForPoint( origin ) ) + continue; + if( !ent->inuse ) continue; if( ent->health <= 0 ) continue; - if( ent->biteam != team ) + if( ent->buildableTeam != team ) continue; - // Don't allow destruction of hovel with granger inside - if( ent->s.modelindex == BA_A_HOVEL && ent->active ) - { - if( buildable == BA_A_HOVEL ) - return IBE_HOVEL; - else - continue; - } - // Explicitly disallow replacement of the core buildable with anything // other than the core buildable if( ent->s.modelindex == core && buildable != core ) continue; + // Don't allow a power source to be replaced by a dependant + if( team == TEAM_HUMANS && + G_PowerEntityForPoint( origin ) == ent && + buildable != BA_H_REPEATER && + buildable != core ) + continue; + + // Don't include unpowered buildables + if( !collision && !ent->powered ) + continue; + if( ent->deconstruct ) { level.markedBuildables[ numBuildables++ ] = ent; + // Buildables that are marked here will always end up at the front of the + // removal list, so just incrementing numBuildablesForRemoval is sufficient if( collision || repeaterInRange ) { + // Collided with something, so we definitely have to remove it or + // it's a repeater that intersects the new repeater's power area, + // so it must be removed + if( collision ) collisionCount--; if( repeaterInRange ) repeaterInRangeCount--; - pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex ); + if( ent->powered ) + pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints; level.numBuildablesForRemoval++; } - else if( unique && ent->s.modelindex == buildable ) + else if( BG_Buildable( ent->s.modelindex )->uniqueTest && + ent->s.modelindex == buildable ) { // If it's a unique buildable, it must be replaced by the same type - pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex ); + if( ent->powered ) + pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints; level.numBuildablesForRemoval++; } } } + numRequired = level.numBuildablesForRemoval; + // We still need build points, but have no candidates for removal - if( buildpointsneeded > 0 && numBuildables == 0 ) + if( buildPoints > 0 && numBuildables == 0 ) return bpError; // Collided with something we can't remove @@ -3112,7 +3245,7 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, // There are one or more repeaters we can't remove if( repeaterInRangeCount > 0 ) - return IBE_RPTWARN2; + return IBE_RPTPOWERHERE; // Sort the list cmpBuildable = buildable; @@ -3125,23 +3258,50 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, level.numBuildablesForRemoval++ ) { ent = level.markedBuildables[ level.numBuildablesForRemoval ]; - pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex ); + if( ent->powered ) + pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints; + } + + // Do another pass to see if we can meet quota with fewer buildables + // than we have now due to mismatches between priority and BP amounts + // by repeatedly testing if we can chop off the first thing that isn't + // required by rules of collision/uniqueness, which are always at the head + while( changed && level.numBuildablesForRemoval > 1 && + level.numBuildablesForRemoval > numRequired ) + { + int pointsUnYielded = 0; + changed = qfalse; + ent = level.markedBuildables[ numRequired ]; + if( ent->powered ) + pointsUnYielded = BG_Buildable( ent->s.modelindex )->buildPoints; + + if( pointsYielded - pointsUnYielded >= buildPoints ) + { + pointsYielded -= pointsUnYielded; + memmove( &level.markedBuildables[ numRequired ], + &level.markedBuildables[ numRequired + 1 ], + ( level.numBuildablesForRemoval - numRequired ) + * sizeof( gentity_t * ) ); + level.numBuildablesForRemoval--; + changed = qtrue; + } } - // Make sure we're not removing the last spawn for( i = 0; i < level.numBuildablesForRemoval; i++ ) { if( level.markedBuildables[ i ]->s.modelindex == spawn ) spawnCount++; } + + // Make sure we're not removing the last spawn if( !g_cheats.integer && remainingSpawns > 0 && ( remainingSpawns - spawnCount ) < 1 ) - return IBE_NORMAL; + return IBE_LASTSPAWN; // Not enough points yielded - if( pointsYielded < buildpointsneeded ) + if( pointsYielded < buildPoints ) return bpError; - - return IBE_NONE; + else + return IBE_NONE; } /* @@ -3190,39 +3350,34 @@ G_CanBuild Checks to see if a buildable can be built ================ */ -itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ) +itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, + vec3_t origin, vec3_t normal, int *groundEntNum ) { vec3_t angles; - vec3_t entity_origin, normal; - vec3_t mins, maxs, nbmins, nbmaxs; - vec3_t nbVect; + vec3_t entity_origin; + vec3_t mins, maxs; trace_t tr1, tr2, tr3; - int i; - itemBuildError_t reason = IBE_NONE; + itemBuildError_t reason = IBE_NONE, tempReason; gentity_t *tempent; float minNormal; qboolean invert; int contents; playerState_t *ps = &ent->client->ps; - int buildPoints; - gentity_t *tmp; - itemBuildError_t tempReason; // Stop all buildables from interacting with traces G_SetBuildableLinkState( qfalse ); - BG_FindBBoxForBuildable( buildable, mins, maxs ); + BG_BuildableBoundingBox( buildable, mins, maxs ); BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 ); - trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, ent->s.number, MASK_PLAYERSOLID ); trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID ); VectorCopy( entity_origin, origin ); - + *groundEntNum = tr1.entityNum; VectorCopy( tr1.plane.normal, normal ); - minNormal = BG_FindMinNormalForBuildable( buildable ); - invert = BG_FindInvertNormalForBuildable( buildable ); + minNormal = BG_Buildable( buildable )->minNormal; + invert = BG_Buildable( buildable )->invertNormal; //can we build at this angle? if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) ) @@ -3232,174 +3387,93 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance reason = IBE_NORMAL; contents = trap_PointContents( entity_origin, -1 ); - buildPoints = BG_FindBuildPointsForBuildable( buildable ); - - //check if we are near a nobuild marker, if so, can't build here... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - tmp = &g_entities[ i ]; - - if( !tmp->noBuild.isNB ) - continue; - - nbVect[0] = tmp->noBuild.Area; - nbVect[1] = tmp->noBuild.Area; - nbVect[2] = tmp->noBuild.Height; - - VectorSubtract( origin, nbVect, nbmins ); - VectorAdd( origin, nbVect, nbmaxs ); - - if( trap_EntityContact( nbmins, nbmaxs, tmp ) ) - reason = IBE_PERMISSION; - - } - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + + if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE ) + reason = tempReason; + + if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { //alien criteria - if( buildable == BA_A_HOVEL ) + // Check there is an Overmind + if( buildable != BA_A_OVERMIND ) { - vec3_t builderMins, builderMaxs; - - //this assumes the adv builder is the biggest thing that'll use the hovel - BG_FindBBoxForClass( PCL_ALIEN_BUILDER0_UPG, builderMins, builderMaxs, NULL, NULL, NULL ); - - if( APropHovel_Blocked( origin, angles, normal, ent ) ) - reason = IBE_HOVELEXIT; + if( !G_Overmind( ) ) + reason = IBE_NOOVERMIND; } //check there is creep near by for building on - if( BG_FindCreepTestForBuildable( buildable ) ) + if( BG_Buildable( buildable )->creepTest ) { if( !G_IsCreepHere( entity_origin ) ) reason = IBE_NOCREEP; } - //check permission to build here - if( tr1.surfaceFlags & SURF_NOALIENBUILD || tr1.surfaceFlags & SURF_NOBUILD || - contents & CONTENTS_NOALIENBUILD || contents & CONTENTS_NOBUILD ) + // Check permission to build here + if( tr1.surfaceFlags & SURF_NOALIENBUILD || contents & CONTENTS_NOALIENBUILD ) reason = IBE_PERMISSION; - - //look for an Overmind - for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) - { - if( tempent->s.eType != ET_BUILDABLE ) - continue; - if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned && - tempent->health > 0 ) - break; - } - - //if none found... - if( i >= level.num_entities && buildable != BA_A_OVERMIND ) - reason = IBE_NOOVERMIND; - - //can we only have one of these? - if( BG_FindUniqueTestForBuildable( buildable ) ) - { - for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) - { - if( tempent->s.eType != ET_BUILDABLE ) - continue; - - if( tempent->s.modelindex == buildable && !tempent->deconstruct ) - { - switch( buildable ) - { - case BA_A_OVERMIND: - reason = IBE_OVERMIND; - break; - - case BA_A_HOVEL: - reason = IBE_HOVEL; - break; - - default: - Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable ); - break; - } - - break; - } - } - } } - else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { //human criteria - if( !G_IsPowered( entity_origin ) ) + + // Check for power + if( G_IsPowered( entity_origin ) == BA_NONE ) { //tell player to build a repeater to provide power if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) - reason = IBE_REPEATER; + reason = IBE_NOPOWERHERE; } //this buildable requires a DCC - if( BG_FindDCCTestForBuildable( buildable ) && !G_IsDCCBuilt( ) ) + if( BG_Buildable( buildable )->dccTest && !G_IsDCCBuilt( ) ) reason = IBE_NODCC; //check that there is a parent reactor when building a repeater if( buildable == BA_H_REPEATER ) { - for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) - { - if( tempent->s.eType != ET_BUILDABLE ) - continue; - - if( tempent->s.modelindex == BA_H_REACTOR ) - break; - } - - if( i >= level.num_entities ) - { - //no reactor present - - //check for other nearby repeaters - for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) - { - if( tempent->s.eType != ET_BUILDABLE ) - continue; - - if( tempent->s.modelindex == BA_H_REPEATER && - Distance( tempent->s.origin, entity_origin ) < REPEATER_BASESIZE ) - { - reason = IBE_RPTWARN2; - break; - } - } - - if( reason == IBE_NONE ) - reason = IBE_RPTWARN; - } - else if( G_IsPowered( entity_origin ) ) - reason = IBE_RPTWARN2; + tempent = G_Reactor( ); + + if( tempent == NULL ) // No reactor + reason = IBE_RPTNOREAC; + else if( g_markDeconstruct.integer && G_IsPowered( entity_origin ) == BA_H_REACTOR ) + reason = IBE_RPTPOWERHERE; + else if( !g_markDeconstruct.integer && G_IsPowered( entity_origin ) ) + reason = IBE_RPTPOWERHERE; } - //check permission to build here - if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD || - contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD ) + // Check permission to build here + if( tr1.surfaceFlags & SURF_NOHUMANBUILD || contents & CONTENTS_NOHUMANBUILD ) reason = IBE_PERMISSION; + } - //can we only build one of these? - if( BG_FindUniqueTestForBuildable( buildable ) ) + // Check permission to build here + if( tr1.surfaceFlags & SURF_NOBUILD || contents & CONTENTS_NOBUILD ) + reason = IBE_PERMISSION; + + // Can we only have one of these? + if( BG_Buildable( buildable )->uniqueTest ) + { + tempent = G_FindBuildable( buildable ); + if( tempent && !tempent->deconstruct ) { - for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + switch( buildable ) { - if( tempent->s.eType != ET_BUILDABLE ) - continue; + case BA_A_OVERMIND: + reason = IBE_ONEOVERMIND; + break; - if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) - { - reason = IBE_REACTOR; + case BA_H_REACTOR: + reason = IBE_ONEREACTOR; + break; + + default: + Com_Error( ERR_FATAL, "No reason for denying build of %d", buildable ); break; - } } } } - if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE ) - reason = tempReason; - // Relink buildables G_SetBuildableLinkState( qtrue ); @@ -3413,8 +3487,8 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance } //this item does not fit here - if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) ) - return IBE_NOROOM; + if( reason == IBE_NONE && ( tr2.fraction < 1.0f || tr3.fraction < 1.0f ) ) + reason = IBE_NOROOM; if( reason != IBE_NONE ) level.numBuildablesForRemoval = 0; @@ -3422,28 +3496,55 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance return reason; } + /* -============== -G_BuildingExists -============== +================ +G_AddRangeMarkerForBuildable +================ */ -qboolean G_BuildingExists( int bclass ) +static void G_AddRangeMarkerForBuildable( gentity_t *self ) { - int i; - gentity_t *tempent; - //look for an Armoury - for (i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + gentity_t *rm; + + switch( self->s.modelindex ) { - if( tempent->s.eType != ET_BUILDABLE ) - continue; - if( tempent->s.modelindex == bclass && tempent->health > 0 ) - { - return qtrue; - } + case BA_A_SPAWN: + case BA_A_OVERMIND: + case BA_A_ACIDTUBE: + case BA_A_TRAPPER: + case BA_A_HIVE: + case BA_H_MGTURRET: + case BA_H_TESLAGEN: + case BA_H_DCC: + case BA_H_REACTOR: + case BA_H_REPEATER: + break; + default: + return; } - return qfalse; + + rm = G_Spawn(); + rm->classname = "buildablerangemarker"; + rm->r.svFlags = SVF_BROADCAST | SVF_CLIENTMASK; + rm->s.eType = ET_RANGE_MARKER; + rm->s.modelindex = self->s.modelindex; + + self->rangeMarker = rm; } +/* +================ +G_RemoveRangeMarkerFrom +================ +*/ +void G_RemoveRangeMarkerFrom( gentity_t *self ) +{ + if( self->rangeMarker ) + { + G_FreeEntity( self->rangeMarker ); + self->rangeMarker = NULL; + } +} /* ================ @@ -3452,142 +3553,105 @@ G_Build Spawns a buildable ================ */ -static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ) +static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, + const vec3_t origin, const vec3_t normal, const vec3_t angles, int groundEntNum ) { gentity_t *built; - buildHistory_t *new; - vec3_t normal; - - // initialise the buildhistory so other functions can use it - if( builder && builder->client ) - { - new = G_Alloc( sizeof( buildHistory_t ) ); - G_LogBuild( new ); - } + char readable[ MAX_STRING_CHARS ]; + char buildnums[ MAX_STRING_CHARS ]; + buildLog_t *log; + + if( builder->client ) + log = G_BuildLogNew( builder, BF_CONSTRUCT ); + else + log = NULL; // Free existing buildables - G_FreeMarkedBuildables( ); + G_FreeMarkedBuildables( builder, readable, sizeof( readable ), + buildnums, sizeof( buildnums ) ); - //spawn the buildable - built = G_Spawn(); + if( builder->client ) + { + // Spawn the buildable + built = G_Spawn(); + } + else + built = builder; built->s.eType = ET_BUILDABLE; + built->killedBy = ENTITYNUM_NONE; + built->classname = BG_Buildable( buildable )->entityName; + built->s.modelindex = buildable; + built->buildableTeam = built->s.modelindex2 = BG_Buildable( buildable )->team; + BG_BuildableBoundingBox( buildable, built->r.mins, built->r.maxs ); - built->classname = BG_FindEntityNameForBuildable( buildable ); - - built->s.modelindex = buildable; //so we can tell what this is on the client side - built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable ); + built->health = 1; - BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs ); + built->splashDamage = BG_Buildable( buildable )->splashDamage; + built->splashRadius = BG_Buildable( buildable )->splashRadius; + built->splashMethodOfDeath = BG_Buildable( buildable )->meansOfDeath; - // detect the buildable's normal vector - if( !builder->client ) - { - // initial base layout created by server - - if( builder->s.origin2[ 0 ] - || builder->s.origin2[ 1 ] - || builder->s.origin2[ 2 ] ) - { - VectorCopy( builder->s.origin2, normal ); - } - else if( BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); - } - else - { - // in-game building by a player - - if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( builder->client->ps.grapplePoint, normal ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); - } - - // when building the initial layout, spawn the entity slightly off its - // target surface so that it can be "dropped" onto it - if( !builder->client ) - VectorMA( origin, 1.0f, normal, origin ); - - built->health = 1; - - built->splashDamage = BG_FindSplashDamageForBuildable( buildable ); - built->splashRadius = BG_FindSplashRadiusForBuildable( buildable ); - built->splashMethodOfDeath = BG_FindMODForBuildable( buildable ); - - built->nextthink = BG_FindNextThinkForBuildable( buildable ); + built->nextthink = BG_Buildable( buildable )->nextthink; built->takedamage = qtrue; built->spawned = qfalse; built->buildTime = built->s.time = level.time; - built->spawnBlockTime = 0; // build instantly in cheat mode if( builder->client && g_cheats.integer ) { - built->health = BG_FindHealthForBuildable( buildable ); + built->health = BG_Buildable( buildable )->health; built->buildTime = built->s.time = - level.time - BG_FindBuildTimeForBuildable( buildable ); + level.time - BG_Buildable( buildable )->buildTime; } //things that vary for each buildable that aren't in the dbase switch( buildable ) { case BA_A_SPAWN: - built->die = ASpawn_Die; + built->die = AGeneric_Die; built->think = ASpawn_Think; - built->pain = ASpawn_Pain; + built->pain = AGeneric_Pain; break; case BA_A_BARRICADE: built->die = ABarricade_Die; built->think = ABarricade_Think; built->pain = ABarricade_Pain; + built->touch = ABarricade_Touch; + built->shrunkTime = 0; + ABarricade_Shrink( built, qtrue ); break; case BA_A_BOOSTER: - built->die = ABarricade_Die; - built->think = ABarricade_Think; - built->pain = ABarricade_Pain; + built->die = AGeneric_Die; + built->think = AGeneric_Think; + built->pain = AGeneric_Pain; built->touch = ABooster_Touch; break; case BA_A_ACIDTUBE: - built->die = ABarricade_Die; + built->die = AGeneric_Die; built->think = AAcidTube_Think; - built->pain = ASpawn_Pain; + built->pain = AGeneric_Pain; break; case BA_A_HIVE: - built->die = ABarricade_Die; + built->die = AGeneric_Die; built->think = AHive_Think; - built->pain = ASpawn_Pain; + built->pain = AHive_Pain; break; case BA_A_TRAPPER: - built->die = ABarricade_Die; + built->die = AGeneric_Die; built->think = ATrapper_Think; - built->pain = ASpawn_Pain; + built->pain = AGeneric_Pain; break; case BA_A_OVERMIND: - built->die = ASpawn_Die; + built->die = AGeneric_Die; built->think = AOvermind_Think; - built->pain = ASpawn_Pain; - break; - - case BA_A_HOVEL: - built->die = AHovel_Die; - built->use = AHovel_Use; - built->think = AHovel_Think; - built->pain = ASpawn_Pain; + built->pain = AGeneric_Pain; break; case BA_H_SPAWN: @@ -3618,7 +3682,7 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori case BA_H_MEDISTAT: built->think = HMedistat_Think; - built->die = HSpawn_Die; + built->die = HMedistat_Die; break; case BA_H_REACTOR: @@ -3626,12 +3690,11 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->die = HSpawn_Die; built->use = HRepeater_Use; built->powered = built->active = qtrue; - built->pain = HReactor_Pain; break; case BA_H_REPEATER: built->think = HRepeater_Think; - built->die = HSpawn_Die; + built->die = HRepeater_Die; built->use = HRepeater_Use; built->count = -1; break; @@ -3641,152 +3704,91 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori break; } - built->s.number = built - g_entities; built->r.contents = CONTENTS_BODY; built->clipmask = MASK_PLAYERSOLID; built->enemy = NULL; - built->s.weapon = BG_FindProjTypeForBuildable( buildable ); + built->s.weapon = BG_Buildable( buildable )->turretProjType; if( builder->client ) - { - built->builtBy = builder->client->ps.clientNum; - - if( builder->client->pers.designatedBuilder ) - { - built->s.eFlags |= EF_DBUILDER; // designated builder protection - } - } + built->builtBy = builder->client->pers.namelog; + else if( builder->builtBy ) + built->builtBy = builder->builtBy; else - built->builtBy = -1; + built->builtBy = NULL; G_SetOrigin( built, origin ); - - // gently nudge the buildable onto the surface :) - VectorScale( normal, -50.0f, built->s.pos.trDelta ); // set turret angles VectorCopy( builder->s.angles2, built->s.angles2 ); - VectorCopy( angles, built->s.angles ); - built->s.angles[ PITCH ] = 0.0f; + VectorCopy( angles, built->s.apos.trBase ); + VectorCopy( angles, built->r.currentAngles ); + built->s.apos.trBase[ PITCH ] = 0.0f; + built->r.currentAngles[ PITCH ] = 0.0f; built->s.angles2[ YAW ] = angles[ YAW ]; - built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable ); - built->s.pos.trTime = level.time; - built->physicsBounce = BG_FindBounceForBuildable( buildable ); - built->s.groundEntityNum = -1; + built->s.angles2[ PITCH ] = MGTURRET_VERTICALCAP; + built->physicsBounce = BG_Buildable( buildable )->bounce; - built->s.generic1 = (int)( ( (float)built->health / - (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK ); + built->s.groundEntityNum = groundEntNum; + if( groundEntNum == ENTITYNUM_NONE ) + { + built->s.pos.trType = BG_Buildable( buildable )->traj; + built->s.pos.trTime = level.time; + // gently nudge the buildable onto the surface :) + VectorScale( normal, -50.0f, built->s.pos.trDelta ); + } - if( built->s.generic1 < 0 ) - built->s.generic1 = 0; + built->s.misc = MAX( built->health, 0 ); - if( BG_FindTeamForBuildable( built->s.modelindex ) == PTE_ALIENS ) + if( BG_Buildable( buildable )->team == TEAM_ALIENS ) { built->powered = qtrue; - built->s.generic1 |= B_POWERED_TOGGLEBIT; + built->s.eFlags |= EF_B_POWERED; } - else if( ( built->powered = G_FindPower( built ) ) ) - built->s.generic1 |= B_POWERED_TOGGLEBIT; - - if( ( built->dcced = G_FindDCC( built ) ) ) - built->s.generic1 |= B_DCCED_TOGGLEBIT; + else if( ( built->powered = G_FindPower( built, qfalse ) ) ) + built->s.eFlags |= EF_B_POWERED; - built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT; + built->s.eFlags &= ~EF_B_SPAWNED; VectorCopy( normal, built->s.origin2 ); G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 ); - G_SetIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) ); + G_SetIdleBuildableAnim( built, BG_Buildable( buildable )->idleAnim ); - if( built->builtBy >= 0 ) + if( built->builtBy ) G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); trap_LinkEntity( built ); - - - if( builder->client ) + + if( builder && builder->client ) { - builder->client->pers.statscounters.structsbuilt++; - if( builder->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.structsbuilt++; - } - else if( builder->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.structsbuilt++; - } + G_TeamCommand( builder->client->ps.stats[ STAT_TEAM ], + va( "print \"%s ^2built^7 by %s%s%s\n\"", + BG_Buildable( built->s.modelindex )->humanName, + builder->client->pers.netname, + ( readable[ 0 ] ) ? "^7, ^3replacing^7 " : "", + readable ) ); + G_LogPrintf( "Construct: %d %d %s%s: %s" S_COLOR_WHITE " is building " + "%s%s%s\n", + (int)( builder - g_entities ), + (int)( built - g_entities ), + BG_Buildable( built->s.modelindex )->name, + buildnums, + builder->client->pers.netname, + BG_Buildable( built->s.modelindex )->humanName, + readable[ 0 ] ? ", replacing " : "", + readable ); } - if( builder->client ) { - G_TeamCommand( builder->client->pers.teamSelection, - va( "print \"%s is ^2being built^7 by %s^7\n\"", - BG_FindHumanNameForBuildable( built->s.modelindex ), - builder->client->pers.netname ) ); - G_LogPrintf("Build: %i %i 0: %s^7 is ^2building^7 %s\n", - builder->client->ps.clientNum, - built->s.modelindex, - builder->client->pers.netname, - BG_FindNameForBuildable( built->s.modelindex ) ); - } + if( log ) + G_BuildLogSet( log, built ); - // ok we're all done building, so what we log here should be the final values - if( builder && builder->client ) // log ingame building only - { - new = level.buildHistory; - new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; - new->ent = builder; - new->name[ 0 ] = 0; - new->buildable = buildable; - VectorCopy( built->s.pos.trBase, new->origin ); - VectorCopy( built->s.angles, new->angles ); - VectorCopy( built->s.origin2, new->origin2 ); - VectorCopy( built->s.angles2, new->angles2 ); - new->fate = BF_BUILT; - } - - if( builder && builder->client ) - built->bdnumb = new->ID; - else - built->bdnumb = -1; + G_AddRangeMarkerForBuildable( built ); return built; } -static void G_SpawnMarker( vec3_t origin ) -{ - gentity_t *nb; - int i; - - // Make the marker... - nb = G_Spawn( ); - nb->s.modelindex = 0; //Coder humor is win - VectorCopy( origin, nb->s.pos.trBase ); - VectorCopy( origin, nb->r.currentOrigin ); - nb->noBuild.isNB = qtrue; - nb->noBuild.Area = level.nbArea; - nb->noBuild.Height = level.nbHeight; - trap_LinkEntity( nb ); - - // Log markers made... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker != NULL ) - continue; - - level.nbMarkers[ i ].Marker = nb; - VectorCopy( origin, level.nbMarkers[ i ].Origin ); - SnapVector( level.nbMarkers[ i ].Origin ); - break; - } - - // End nobuild mode... - level.noBuilding = qfalse; - level.nbArea = 0.0f; - level.nbHeight = 0.0f; -} - /* ================= G_BuildIfValid @@ -3795,27 +3797,19 @@ G_BuildIfValid qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) { float dist; - vec3_t origin; + vec3_t origin, normal; + int groundEntNum; - dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); + dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; - switch( G_CanBuild( ent, buildable, dist, origin ) ) + switch( G_CanBuild( ent, buildable, dist, origin, normal, &groundEntNum ) ) { case IBE_NONE: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - - G_SpawnMarker( origin ); - return qtrue; - } - G_Build( ent, buildable, origin, ent->s.apos.trBase ); + G_Build( ent, buildable, origin, normal, ent->s.apos.trBase, groundEntNum ); return qtrue; - case IBE_NOASSERT: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT ); + case IBE_NOALIENBP: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOBP ); return qfalse; case IBE_NOOVERMIND: @@ -3826,96 +3820,44 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP ); return qfalse; - case IBE_OVERMIND: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND ); - return qfalse; - - case IBE_HOVEL: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL ); - return qfalse; - - case IBE_HOVELEXIT: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT ); + case IBE_ONEOVERMIND: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_ONEOVERMIND ); return qfalse; case IBE_NORMAL: - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL ); - else - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL ); + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL ); return qfalse; case IBE_PERMISSION: - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL ); - else - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL ); + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL ); return qfalse; - case IBE_REACTOR: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR ); + case IBE_ONEREACTOR: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ONEREACTOR ); return qfalse; - case IBE_REPEATER: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER ); + case IBE_NOPOWERHERE: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWERHERE ); return qfalse; case IBE_NOROOM: - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOM ); - else - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOROOM ); + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NOROOM ); return qfalse; - case IBE_NOPOWER: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER ); + case IBE_NOHUMANBP: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOBP); return qfalse; case IBE_NODCC: G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC ); return qfalse; - case IBE_SPWNWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } - G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); - G_Build( ent, buildable, origin, ent->s.apos.trBase ); - return qtrue; - - case IBE_TNODEWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } - G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); - G_Build( ent, buildable, origin, ent->s.apos.trBase ); - return qtrue; - - case IBE_RPTWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } - G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); - G_Build( ent, buildable, origin, ent->s.apos.trBase ); - return qtrue; + case IBE_RPTPOWERHERE: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTPOWERHERE ); + return qfalse; - case IBE_RPTWARN2: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 ); + case IBE_LASTSPAWN: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); return qfalse; default: @@ -3933,33 +3875,41 @@ Traces down to find where an item should rest, instead of letting them free fall from their spawn points ================ */ -static void G_FinishSpawningBuildable( gentity_t *ent ) +static gentity_t *G_FinishSpawningBuildable( gentity_t *ent, qboolean force ) { trace_t tr; - vec3_t dest; + vec3_t normal, dest; gentity_t *built; buildable_t buildable = ent->s.modelindex; - built = G_Build( ent, buildable, ent->s.pos.trBase, ent->s.angles ); - G_FreeEntity( ent ); + if( ent->s.origin2[ 0 ] || ent->s.origin2[ 1 ] || ent->s.origin2[ 2 ] ) + VectorCopy( ent->s.origin2, normal ); + else if( BG_Buildable( buildable )->traj == TR_BUOYANCY ) + VectorSet( normal, 0.0f, 0.0f, -1.0f ); + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); + + built = G_Build( ent, buildable, ent->r.currentOrigin, + normal, ent->r.currentAngles, ENTITYNUM_NONE ); built->takedamage = qtrue; built->spawned = qtrue; //map entities are already spawned - built->health = BG_FindHealthForBuildable( buildable ); - built->s.generic1 |= B_SPAWNED_TOGGLEBIT; + built->health = BG_Buildable( buildable )->health; + built->s.eFlags |= EF_B_SPAWNED; // drop towards normal surface VectorScale( built->s.origin2, -4096.0f, dest ); - VectorAdd( dest, built->s.origin, dest ); + VectorAdd( dest, built->r.currentOrigin, dest ); - trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); + trap_Trace( &tr, built->r.currentOrigin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); - if( tr.startsolid ) + if( tr.startsolid && !force ) { G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", - built->classname, vtos( built->s.origin ) ); + built->classname, vtos( built->r.currentOrigin ) ); + G_RemoveRangeMarkerFrom( built ); G_FreeEntity( built ); - return; + return NULL; } //point items in the correct direction @@ -3971,6 +3921,20 @@ static void G_FinishSpawningBuildable( gentity_t *ent ) G_SetOrigin( built, tr.endpos ); trap_LinkEntity( built ); + return built; +} + +/* +============ +G_SpawnBuildableThink + +Complete spawning a buildable using its placeholder +============ +*/ +static void G_SpawnBuildableThink( gentity_t *ent ) +{ + G_FinishSpawningBuildable( ent, qfalse ); + G_BuildableThink( ent, 0 ); } /* @@ -3990,76 +3954,96 @@ void G_SpawnBuildable( gentity_t *ent, buildable_t buildable ) // some movers spawn on the second frame, so delay item // spawns until the third frame so they can ride trains ent->nextthink = level.time + FRAMETIME * 2; - ent->think = G_FinishSpawningBuildable; + ent->think = G_SpawnBuildableThink; +} + +void G_ParseCSVBuildablePlusList( const char *string, int *buildables, int buildablesSize ) +{ + char buffer[ MAX_STRING_CHARS ]; + int i = 0; + char *p, *q; + qboolean EOS = qfalse; + + Q_strncpyz( buffer, string, MAX_STRING_CHARS ); + + p = q = buffer; + + while( *p != '\0' && i < buildablesSize - 1 ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + if( !Q_stricmp( q, "alien" ) ) + { + buildable_t b; + for( b = BA_A_SPAWN; b <= BA_A_HIVE && i < buildablesSize - 1; ++b ) + buildables[ i++ ] = b; + + if( i < buildablesSize - 1 ) + buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_ALIENS; + } + else if( !Q_stricmp( q, "human" ) ) + { + buildable_t b; + for( b = BA_H_SPAWN; b <= BA_H_REPEATER && i < buildablesSize - 1; ++b ) + buildables[ i++ ] = b; + + if( i < buildablesSize - 1 ) + buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_HUMANS; + } + else if( !Q_stricmp( q, "ivo_spectator" ) ) + buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_NONE; + else if( !Q_stricmp( q, "ivo_alien" ) ) + buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_ALIENS; + else if( !Q_stricmp( q, "ivo_human" ) ) + buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_HUMANS; + else + { + buildables[ i ] = BG_BuildableByName( q )->number; + if( buildables[ i ] == BA_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable or special identifier %s\n", q ); + else + i++; + } + + if( !EOS ) + { + p++; + q = p; + } + else + break; + } + + buildables[ i ] = BA_NONE; } - /* - ============ - G_CheckDBProtection - - Count how many designated builders are in both teams and - if none found in some team, cancel protection for all - structures of that team - ============ - */ - - void G_CheckDBProtection( void ) - { - int alienDBs = 0, humanDBs = 0, i; - gentity_t *ent; - - // count designated builders - for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++) - { - if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) ) - continue; - - if( ent->client->pers.designatedBuilder) - { - if( ent->client->pers.teamSelection == PTE_HUMANS ) - { - humanDBs++; - } - else if( ent->client->pers.teamSelection == PTE_ALIENS ) - { - alienDBs++; - } - } - } - - // both teams have designate builders, we're done - if( alienDBs > 0 && humanDBs > 0 ) - return; - - // cancel protection if needed - for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++) - { - if( ent->s.eType != ET_BUILDABLE) - continue; - - if( ( !alienDBs && ent->biteam == BIT_ALIENS ) || - ( !humanDBs && ent->biteam == BIT_HUMANS ) ) - { - ent->s.eFlags &= ~EF_DBUILDER; - } - } - } - /* ============ G_LayoutSave - ============ */ -void G_LayoutSave( char *name ) +void G_LayoutSave( char *lstr ) { + char *lstrPipePtr; + qboolean bAllowed[ BA_NUM_BUILDABLES + NUM_TEAMS ]; char map[ MAX_QPATH ]; char fileName[ MAX_OSPATH ]; fileHandle_t f; int len; int i; gentity_t *ent; - char *s; + const char *s; trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); if( !map[ 0 ] ) @@ -4067,7 +4051,22 @@ void G_LayoutSave( char *name ) G_Printf( "LayoutSave( ): no map is loaded\n" ); return; } - Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name ); + + if( ( lstrPipePtr = strchr( lstr, '|' ) ) ) + { + int bList[ BA_NUM_BUILDABLES + NUM_TEAMS ]; + *lstrPipePtr = '\0'; + G_ParseCSVBuildablePlusList( lstr, &bList[ 0 ], sizeof( bList ) / sizeof( bList[ 0 ] ) ); + memset( bAllowed, 0, sizeof( bAllowed ) ); + for( i = 0; bList[ i ] != BA_NONE; i++ ) + bAllowed[ bList[ i ] ] = qtrue; + *lstrPipePtr = '|'; + lstr = lstrPipePtr + 1; + } + else + bAllowed[ BA_NONE ] = qtrue; // allow all + + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, lstr ); len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); if( len < 0 ) @@ -4076,22 +4075,48 @@ void G_LayoutSave( char *name ) return; } - G_Printf("layoutsave: saving layout to %s\n", fileName ); + G_Printf( "layoutsave: saving layout to %s\n", fileName ); for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { + const char *name; + ent = &level.gentities[ i ]; - if( ent->s.eType != ET_BUILDABLE ) + if( ent->s.eType == ET_BUILDABLE ) + { + if( !bAllowed[ BA_NONE ] && !bAllowed[ ent->s.modelindex ] ) + continue; + name = BG_Buildable( ent->s.modelindex )->name; + } + else if( ent->count == 1 && !strcmp( ent->classname, "info_player_intermission" ) ) + { + if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_NONE ] ) + continue; + name = "ivo_spectator"; + } + else if( ent->count == 1 && !strcmp( ent->classname, "info_alien_intermission" ) ) + { + if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_ALIENS ] ) + continue; + name = "ivo_alien"; + } + else if( ent->count == 1 && !strcmp( ent->classname, "info_human_intermission" ) ) + { + if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_HUMANS ] ) + continue; + name = "ivo_human"; + } + else continue; - s = va( "%i %f %f %f %f %f %f %f %f %f %f %f %f\n", - ent->s.modelindex, - ent->s.pos.trBase[ 0 ], - ent->s.pos.trBase[ 1 ], - ent->s.pos.trBase[ 2 ], - ent->s.angles[ 0 ], - ent->s.angles[ 1 ], - ent->s.angles[ 2 ], + s = va( "%s %f %f %f %f %f %f %f %f %f %f %f %f\n", + name, + ent->r.currentOrigin[ 0 ], + ent->r.currentOrigin[ 1 ], + ent->r.currentOrigin[ 2 ], + ent->r.currentAngles[ 0 ], + ent->r.currentAngles[ 1 ], + ent->r.currentAngles[ 2 ], ent->s.origin2[ 0 ], ent->s.origin2[ 1 ], ent->s.origin2[ 2 ], @@ -4103,6 +4128,11 @@ void G_LayoutSave( char *name ) trap_FS_FCloseFile( f ); } +/* +============ +G_LayoutList +============ +*/ int G_LayoutList( const char *map, char *list, int len ) { // up to 128 single character layout names could fit in layouts @@ -4112,7 +4142,7 @@ int G_LayoutList( const char *map, char *list, int len ) int count = 0; char *filePtr; - Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " ); + Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " ); numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat", fileList, sizeof( fileList ) ); filePtr = fileList; @@ -4121,7 +4151,7 @@ int G_LayoutList( const char *map, char *list, int len ) fileLen = strlen( filePtr ); listLen = strlen( layouts ); if( fileLen < 5 ) - continue; + continue; // list is full, stop trying to add to it if( ( listLen + fileLen ) >= sizeof( layouts ) ) @@ -4149,27 +4179,31 @@ int G_LayoutList( const char *map, char *list, int len ) ============ G_LayoutSelect -set level.layout based on g_layouts or g_layoutAuto +set level.layout based on g_nextLayout, g_layouts or g_layoutAuto ============ */ void G_LayoutSelect( void ) { - char fileName[ MAX_OSPATH ]; - char layouts[ MAX_CVAR_VALUE_STRING ]; - char layouts2[ MAX_CVAR_VALUE_STRING ]; - char *l; + char layouts[ ( MAX_CVAR_VALUE_STRING - 1 ) * 9 + 1 ]; + char layouts2[ ( MAX_CVAR_VALUE_STRING - 1 ) * 9 + 1 ]; + char *l; char map[ MAX_QPATH ]; char *s; int cnt = 0; int layoutNum; - Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) ); + Q_strncpyz( layouts, g_nextLayout.string, sizeof( layouts ) ); + if( !layouts[ 0 ] ) + { + for( layoutNum = 0; layoutNum < 9; ++layoutNum ) + Q_strcat( layouts, sizeof( layouts ), g_layouts[ layoutNum ].string ); + } trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - // one time use cvar - trap_Cvar_Set( "g_layouts", "" ); - - // pick an included layout at random if no list has been provided + // one time use cvar + trap_Cvar_Set( "g_nextLayout", "" ); + + // pick an included layout at random if no list has been provided if( !layouts[ 0 ] && g_layoutAuto.integer ) { G_LayoutList( map, layouts, sizeof( layouts ) ); @@ -4181,20 +4215,13 @@ void G_LayoutSelect( void ) Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); l = &layouts2[ 0 ]; layouts[ 0 ] = '\0'; - s = COM_ParseExt( &l, qfalse ); - while( *s ) + while( 1 ) { - if( !Q_stricmp( s, "*BUILTIN*" ) ) - { - Q_strcat( layouts, sizeof( layouts ), s ); - Q_strcat( layouts, sizeof( layouts ), " " ); - cnt++; - s = COM_ParseExt( &l, qfalse ); - continue; - } - - Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s ); - if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 ) + s = COM_ParseExt( &l, qfalse ); + if( !*s ) + break; + + if( strchr( s, '+' ) || strchr( s, '|' ) || G_LayoutExists( map, s ) ) { Q_strcat( layouts, sizeof( layouts ), s ); Q_strcat( layouts, sizeof( layouts ), " " ); @@ -4202,7 +4229,6 @@ void G_LayoutSelect( void ) } else G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s ); - s = COM_ParseExt( &l, qfalse ); } if( !cnt ) { @@ -4210,335 +4236,178 @@ void G_LayoutSelect( void ) "found, using map default\n" ); return; } - layoutNum = ( rand( ) % cnt ) + 1; + layoutNum = rand( ) / ( RAND_MAX / cnt + 1 ) + 1; cnt = 0; Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); l = &layouts2[ 0 ]; - s = COM_ParseExt( &l, qfalse ); - while( *s ) + while( 1 ) { + s = COM_ParseExt( &l, qfalse ); + if( !*s ) + break; + Q_strncpyz( level.layout, s, sizeof( level.layout ) ); cnt++; if( cnt >= layoutNum ) break; - s = COM_ParseExt( &l, qfalse ); } - G_Printf("using layout \"%s\" from list ( %s)\n", level.layout, layouts ); -} - -static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, - vec3_t angles, vec3_t origin2, vec3_t angles2 ) -{ - gentity_t *builder; - - builder = G_Spawn( ); - builder->client = 0; - VectorCopy( origin, builder->s.pos.trBase ); - VectorCopy( angles, builder->s.angles ); - VectorCopy( origin2, builder->s.origin2 ); - VectorCopy( angles2, builder->s.angles2 ); - G_SpawnBuildable( builder, buildable ); + G_Printf( "using layout \"%s\" from list (%s)\n", level.layout, layouts ); } /* ============ -G_InstantBuild - -This function is extremely similar to the few functions that place a -buildable on map load. It exists because G_LayoutBuildItem takes a couple -of frames to finish spawning it, so it's not truly instant -Do not call this function immediately after the map loads - that's what -G_LayoutBuildItem is for. +G_LayoutBuildItem ============ */ -gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ) +static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, + vec3_t angles, vec3_t origin2, vec3_t angles2 ) { - gentity_t *builder, *built; - trace_t tr; - vec3_t dest; - + gentity_t *builder; + builder = G_Spawn( ); - builder->client = 0; - VectorCopy( origin, builder->s.pos.trBase ); - VectorCopy( angles, builder->s.angles ); + builder->classname = "builder"; + VectorCopy( origin, builder->r.currentOrigin ); + VectorCopy( angles, builder->r.currentAngles ); VectorCopy( origin2, builder->s.origin2 ); VectorCopy( angles2, builder->s.angles2 ); -//old method didn't quite work out -//builder->s.modelindex = buildable; -//G_FinishSpawningBuildable( builder ); - - built = G_Build( builder, buildable, builder->s.pos.trBase, builder->s.angles ); - G_FreeEntity( builder ); - - built->takedamage = qtrue; - built->spawned = qtrue; //map entities are already spawned - built->health = BG_FindHealthForBuildable( buildable ); - built->s.generic1 |= B_SPAWNED_TOGGLEBIT; - - // drop towards normal surface - VectorScale( built->s.origin2, -4096.0f, dest ); - VectorAdd( dest, built->s.origin, dest ); - - trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); - if( tr.startsolid ) - { - G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", - built->classname, vtos( built->s.origin ) ); - G_FreeEntity( built ); - return NULL; - } - - //point items in the correct direction - VectorCopy( tr.plane.normal, built->s.origin2 ); - - // allow to ride movers - built->s.groundEntityNum = tr.entityNum; - - G_SetOrigin( built, tr.endpos ); - - trap_LinkEntity( built ); - return built; -} - -/* -============ -G_SpawnRevertedBuildable - -Given a buildhistory, try to replace the lost buildable -============ -*/ -void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ) -{ - vec3_t mins, maxs; - int i, j, blockCount, blockers[ MAX_GENTITIES ]; - gentity_t *targ, *built, *toRecontent[ MAX_GENTITIES ]; - - BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); - VectorAdd( bh->origin, mins, mins ); - VectorAdd( bh->origin, maxs, maxs ); - blockCount = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); - for( i = j = 0; i < blockCount; i++ ) - { - targ = g_entities + blockers[ i ]; - if( targ->s.eType == ET_BUILDABLE ) - G_FreeEntity( targ ); - else if( targ->s.eType == ET_PLAYER ) - { - targ->r.contents = 0; // make it intangible - toRecontent[ j++ ] = targ; // and remember it - } - } - level.numBuildablesForRemoval = 0; - built = G_InstantBuild( bh->buildable, bh->origin, bh->angles, bh->origin2, bh->angles2 ); - if( built ) - { - built->r.contents = 0; - built->think = G_CommitRevertedBuildable; - built->nextthink = level.time; - built->deconstruct = mark; - } - for( i = 0; i < j; i++ ) - toRecontent[ i ]->r.contents = CONTENTS_BODY; + G_SpawnBuildable( builder, buildable ); } -/* -============ -G_CommitRevertedBuildable - -Check if there's anyone occupying me, and if not, become solid and operate as -normal. Else, try to get rid of them. -============ -*/ -void G_CommitRevertedBuildable( gentity_t *ent ) +static void G_SpawnIntermissionViewOverride( char *cn, vec3_t origin, vec3_t angles ) { - gentity_t *targ; - int i, n, occupants[ MAX_GENTITIES ]; - vec3_t mins, maxs; - int victims = 0; - - VectorAdd( ent->s.origin, ent->r.mins, mins ); - VectorAdd( ent->s.origin, ent->r.maxs, maxs ); - trap_UnlinkEntity( ent ); - n = trap_EntitiesInBox( mins, maxs, occupants, MAX_GENTITIES ); - trap_LinkEntity( ent ); - - for( i = 0; i < n; i++ ) + gentity_t *spot = G_Find( NULL, FOFS( classname ), cn ); + if( !spot ) { - vec3_t gtfo; - targ = g_entities + occupants[ i ]; - if( targ->client ) - { - VectorSet( gtfo, crandom() * 150, crandom() * 150, random() * 150 ); - VectorAdd( targ->client->ps.velocity, gtfo, targ->client->ps.velocity ); - victims++; - } - } - if( !victims ) - { // we're in the clear! - ent->r.contents = MASK_PLAYERSOLID; - trap_LinkEntity( ent ); // relink - // oh dear, manual think set - switch( ent->s.modelindex ) - { - case BA_A_SPAWN: - ent->think = ASpawn_Think; - break; - case BA_A_BARRICADE: - case BA_A_BOOSTER: - ent->think = ABarricade_Think; - break; - case BA_A_ACIDTUBE: - ent->think = AAcidTube_Think; - break; - case BA_A_HIVE: - ent->think = AHive_Think; - break; - case BA_A_TRAPPER: - ent->think = ATrapper_Think; - break; - case BA_A_OVERMIND: - ent->think = AOvermind_Think; - break; - case BA_A_HOVEL: - ent->think = AHovel_Think; - break; - case BA_H_SPAWN: - ent->think = HSpawn_Think; - break; - case BA_H_MGTURRET: - ent->think = HMGTurret_Think; - break; - case BA_H_TESLAGEN: - ent->think = HTeslaGen_Think; - break; - case BA_H_ARMOURY: - ent->think = HArmoury_Think; - break; - case BA_H_DCC: - ent->think = HDCC_Think; - break; - case BA_H_MEDISTAT: - ent->think = HMedistat_Think; - break; - case BA_H_REACTOR: - ent->think = HReactor_Think; - break; - case BA_H_REPEATER: - ent->think = HRepeater_Think; - break; - } - ent->nextthink = level.time + BG_FindNextThinkForBuildable( ent->s.modelindex ); - // oh if only everything was that simple - return; + spot = G_Spawn(); + spot->classname = cn; } -#define REVERT_THINK_INTERVAL 50 - ent->nextthink = level.time + REVERT_THINK_INTERVAL; -} - -/* -============ -G_RevertCanFit + spot->count = 1; -take a bhist and make sure you're not overwriting anything by placing it -============ -*/ -qboolean G_RevertCanFit( buildHistory_t *bh ) -{ - int i, num, blockers[ MAX_GENTITIES ]; - vec3_t mins, maxs; - gentity_t *targ; - vec3_t dist; - - BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); - VectorAdd( bh->origin, mins, mins ); - VectorAdd( bh->origin, maxs, maxs ); - num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - targ = g_entities + blockers[ i ]; - if( targ->s.eType == ET_BUILDABLE ) - { - VectorSubtract( bh->origin, targ->s.pos.trBase, dist ); - if( targ->s.modelindex == bh->buildable && VectorLength( dist ) < 10 && targ->health <= 0 ) - continue; // it's the same buildable, hasn't blown up yet - else - return qfalse; // can't get rid of this one - } - else - continue; - } - return qtrue; + VectorCopy( origin, spot->r.currentOrigin ); + VectorCopy( angles, spot->r.currentAngles ); } /* ============ G_LayoutLoad - -load the layout .dat file indicated by level.layout and spawn buildables -as if a builder was creating them ============ */ -void G_LayoutLoad( void ) +void G_LayoutLoad( char *lstr ) { + char *lstrPlusPtr, *lstrPipePtr; + qboolean bAllowed[ BA_NUM_BUILDABLES + NUM_TEAMS ]; fileHandle_t f; int len; - char *layout; + char *layout, *layoutHead; char map[ MAX_QPATH ]; - int buildable = BA_NONE; + char buildName[ MAX_TOKEN_CHARS ]; + int buildable; vec3_t origin = { 0.0f, 0.0f, 0.0f }; vec3_t angles = { 0.0f, 0.0f, 0.0f }; vec3_t origin2 = { 0.0f, 0.0f, 0.0f }; vec3_t angles2 = { 0.0f, 0.0f, 0.0f }; char line[ MAX_STRING_CHARS ]; - int i = 0; + int i; - if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) ) + if( !lstr[ 0 ] || !Q_stricmp( lstr, "*BUILTIN*" ) ) return; - + + loadAnotherLayout: + lstrPlusPtr = strchr( lstr, '+' ); + if( lstrPlusPtr ) + *lstrPlusPtr = '\0'; + + if( ( lstrPipePtr = strchr( lstr, '|' ) ) ) + { + int bList[ BA_NUM_BUILDABLES + NUM_TEAMS ]; + *lstrPipePtr = '\0'; + G_ParseCSVBuildablePlusList( lstr, &bList[ 0 ], sizeof( bList ) / sizeof( bList[ 0 ] ) ); + memset( bAllowed, 0, sizeof( bAllowed ) ); + for( i = 0; bList[ i ] != BA_NONE; i++ ) + bAllowed[ bList[ i ] ] = qtrue; + *lstrPipePtr = '|'; + lstr = lstrPipePtr + 1; + } + else + bAllowed[ BA_NONE ] = qtrue; // allow all + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ), + len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, lstr ), &f, FS_READ ); if( len < 0 ) { - G_Printf( "ERROR: layout %s could not be opened\n", level.layout ); + G_Printf( "ERROR: layout %s could not be opened\n", lstr ); return; } - layout = G_Alloc( len + 1 ); + layoutHead = layout = BG_Alloc( len + 1 ); trap_FS_Read( layout, len, f ); - *( layout + len ) = '\0'; + layout[ len ] = '\0'; trap_FS_FCloseFile( f ); + i = 0; while( *layout ) { if( i >= sizeof( line ) - 1 ) { G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", - va( "layouts/%s/%s.dat", map, level.layout ), line ); - return; + va( "layouts/%s/%s.dat", map, lstr ), line ); + break; } line[ i++ ] = *layout; line[ i ] = '\0'; if( *layout == '\n' ) { - i = 0; - sscanf( line, "%d %f %f %f %f %f %f %f %f %f %f %f %f\n", - &buildable, + i = 0; + sscanf( line, "%s %f %f %f %f %f %f %f %f %f %f %f %f\n", + buildName, &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &angles[ 0 ], &angles[ 1 ], &angles[ 2 ], &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ], &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] ); - if( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES ) - G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); + buildable = BG_BuildableByName( buildName )->number; + if( !Q_stricmp( buildName, "ivo_spectator" ) ) + { + if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_NONE ] ) + G_SpawnIntermissionViewOverride( "info_player_intermission", origin, angles ); + } + else if( !Q_stricmp( buildName, "ivo_alien" ) ) + { + if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_ALIENS ] ) + G_SpawnIntermissionViewOverride( "info_alien_intermission", origin, angles ); + } + else if( !Q_stricmp( buildName, "ivo_human" ) ) + { + if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_HUMANS ] ) + G_SpawnIntermissionViewOverride( "info_human_intermission", origin, angles ); + } + else if( buildable <= BA_NONE || buildable >= BA_NUM_BUILDABLES ) + G_Printf( S_COLOR_YELLOW "WARNING: bad buildable name (%s) in layout." + " skipping\n", buildName ); else - G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in " - " layout. skipping\n", buildable ); + { + if( bAllowed[ BA_NONE ] || bAllowed[ buildable ] ) + G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); + } } layout++; } + BG_Free( layoutHead ); + + if( lstrPlusPtr ) + { + *lstrPlusPtr = '+'; + lstr = lstrPlusPtr + 1; + goto loadAnotherLayout; + } } -void G_BaseSelfDestruct( pTeam_t team ) +/* +============ +G_BaseSelfDestruct +============ +*/ +void G_BaseSelfDestruct( team_t team ) { int i; gentity_t *ent; @@ -4550,192 +4419,274 @@ void G_BaseSelfDestruct( pTeam_t team ) continue; if( ent->s.eType != ET_BUILDABLE ) continue; - if( team == PTE_HUMANS && ent->biteam != BIT_HUMANS ) - continue; - if( team == PTE_ALIENS && ent->biteam != BIT_ALIENS ) + if( ent->buildableTeam != team ) continue; G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); } } - int G_LogBuild( buildHistory_t *new ) - { - new->next = level.buildHistory; - level.buildHistory = new; - return G_CountBuildLog(); - } - - int G_CountBuildLog( void ) - { - buildHistory_t *ptr, *mark; - int i = 0, overflow; - for( ptr = level.buildHistory; ptr; ptr = ptr->next, i++ ); - if( i > g_buildLogMaxLength.integer ) - { - for( overflow = i - g_buildLogMaxLength.integer; overflow > 0; overflow-- ) - { - ptr = level.buildHistory; - while( ptr->next ) - { - if( ptr->next->next ) - ptr = ptr->next; - else - { - while( ( mark = ptr->next ) ) - { - ptr->next = ptr->next->marked; - G_Free( mark ); - } - } - } - } - return g_buildLogMaxLength.integer; - } - return i; - } - - char *G_FindBuildLogName( int id ) - { - buildHistory_t *ptr; - - for( ptr = level.buildHistory; ptr && ptr->ID != id; ptr = ptr->next ); - if( ptr ) - { - if( ptr->ent ) - { - if( ptr->ent->client ) - return ptr->ent->client->pers.netname; - } - else if( ptr->name[ 0 ] ) - { - return ptr->name; - } - } - - return ""; - } - /* ============ -G_NobuildLoad - -load the nobuild markers that were previously saved (if there are any). +build log ============ */ -void G_NobuildLoad( void ) +buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate ) { - fileHandle_t f; - int len; - char *nobuild; - char map[ MAX_QPATH ]; - vec3_t origin = { 0.0f, 0.0f, 0.0f }; - char line[ MAX_STRING_CHARS ]; - int i = 0; - gentity_t *nb; - float area; - float height; + buildLog_t *log = &level.buildLog[ level.buildId++ % MAX_BUILDLOG ]; + + if( level.numBuildLogs < MAX_BUILDLOG ) + level.numBuildLogs++; + log->time = level.time; + log->fate = fate; + log->actor = actor && actor->client ? actor->client->pers.namelog : NULL; + return log; +} - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - len = trap_FS_FOpenFile( va( "nobuild/%s.dat", map ), - &f, FS_READ ); - if( len < 0 ) +void G_BuildLogSet( buildLog_t *log, gentity_t *ent ) +{ + log->modelindex = ent->s.modelindex; + log->deconstruct = ent->deconstruct; + log->deconstructTime = ent->deconstructTime; + log->builtBy = ent->builtBy; + VectorCopy( ent->r.currentOrigin, log->origin ); + VectorCopy( ent->r.currentAngles, log->angles ); + VectorCopy( ent->s.origin2, log->origin2 ); + VectorCopy( ent->s.angles2, log->angles2 ); + log->powerSource = ent->parentNode ? ent->parentNode->s.modelindex : BA_NONE; + log->powerValue = G_QueueValue( ent ); +} + +void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ) +{ + G_BuildLogSet( G_BuildLogNew( actor, fate ), buildable ); +} + +void G_BuildLogRevertThink( gentity_t *ent ) +{ + gentity_t *built; + vec3_t mins, maxs; + int blockers[ MAX_GENTITIES ]; + int num; + int victims = 0; + int i; + + if( ent->suicideTime > 0 ) { - // This isn't needed since nobuild is pretty much optional... - //G_Printf( "ERROR: nobuild for %s could not be opened\n", map ); - return; + BG_BuildableBoundingBox( ent->s.modelindex, mins, maxs ); + VectorAdd( ent->s.pos.trBase, mins, mins ); + VectorAdd( ent->s.pos.trBase, maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + gentity_t *targ; + vec3_t push; + + targ = g_entities + blockers[ i ]; + if( targ->client ) + { + float val = ( targ->client->ps.eFlags & EF_WALLCLIMB) ? 300.0 : 150.0; + + VectorSet( push, crandom() * val, crandom() * val, random() * val ); + VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); + victims++; + } + } + + ent->suicideTime--; + + if( victims ) + { + // still a blocker + ent->nextthink = level.time + FRAMETIME; + return; + } } - nobuild = G_Alloc( len + 1 ); - trap_FS_Read( nobuild, len, f ); - *( nobuild + len ) = '\0'; - trap_FS_FCloseFile( f ); - while( *nobuild ) + + built = G_FinishSpawningBuildable( ent, qtrue ); + built->buildTime = built->s.time = 0; + G_KillBox( built ); + + G_LogPrintf( "revert: restore %d %s\n", + (int)( built - g_entities ), BG_Buildable( built->s.modelindex )->name ); + + G_BuildableThink( built, 0 ); +} + +void G_BuildLogRevert( int id ) +{ + buildLog_t *log; + gentity_t *ent; + int i; + vec3_t dist; + + level.numBuildablesForRemoval = 0; + + level.numBuildLogs -= level.buildId - id; + while( level.buildId > id ) { - if( i >= sizeof( line ) - 1 ) + log = &level.buildLog[ --level.buildId % MAX_BUILDLOG ]; + if( log->fate == BF_CONSTRUCT ) { - return; + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + ent = &g_entities[ i ]; + if( ( ( ent->s.eType == ET_BUILDABLE && + ent->health > 0 ) || + ( ent->s.eType == ET_GENERAL && + ent->think == G_BuildLogRevertThink ) ) && + ent->s.modelindex == log->modelindex ) + { + VectorSubtract( ent->s.pos.trBase, log->origin, dist ); + if( VectorLengthSquared( dist ) <= 2.0f ) + { + if( ent->s.eType == ET_BUILDABLE ) + G_LogPrintf( "revert: remove %d %s\n", + (int)( ent - g_entities ), BG_Buildable( ent->s.modelindex )->name ); + G_RemoveRangeMarkerFrom( ent ); + G_FreeEntity( ent ); + break; + } + } + } } - - line[ i++ ] = *nobuild; - line[ i ] = '\0'; - if( *nobuild == '\n' ) + else { - i = 0; - sscanf( line, "%f %f %f %f %f\n", - &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &area, &height ); - - // Make the marker... - nb = G_Spawn( ); - nb->s.modelindex = 0; - VectorCopy( origin, nb->s.pos.trBase ); - VectorCopy( origin, nb->r.currentOrigin ); - nb->noBuild.isNB = qtrue; - nb->noBuild.Area = area; - nb->noBuild.Height = height; - trap_LinkEntity( nb ); - - // Log markers made... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker != NULL ) - continue; - - level.nbMarkers[ i ].Marker = nb; - VectorCopy( origin, level.nbMarkers[ i ].Origin ); - SnapVector( level.nbMarkers[ i ].Origin ); - break; - } - + gentity_t *builder = G_Spawn(); + builder->classname = "builder"; + + VectorCopy( log->origin, builder->r.currentOrigin ); + VectorCopy( log->angles, builder->r.currentAngles ); + VectorCopy( log->origin2, builder->s.origin2 ); + VectorCopy( log->angles2, builder->s.angles2 ); + builder->s.modelindex = log->modelindex; + builder->deconstruct = log->deconstruct; + builder->deconstructTime = log->deconstructTime; + builder->builtBy = log->builtBy; + + builder->think = G_BuildLogRevertThink; + builder->nextthink = level.time + FRAMETIME; + + // Number of thinks before giving up and killing players in the way + builder->suicideTime = 30; + + if( log->fate == BF_DESTROY || log->fate == BF_TEAMKILL ) + { + int value = log->powerValue; + + if( BG_Buildable( log->modelindex )->team == TEAM_ALIENS ) + { + level.alienBuildPointQueue = + MAX( 0, level.alienBuildPointQueue - value ); + } + else + { + if( log->powerSource == BA_H_REACTOR ) + { + level.humanBuildPointQueue = + MAX( 0, level.humanBuildPointQueue - value ); + } + else if( log->powerSource == BA_H_REPEATER ) + { + gentity_t *source; + buildPointZone_t *zone; + + source = G_PowerEntityForPoint( log->origin ); + if( source && source->usesBuildPointZone ) + { + zone = &level.buildPointZones[ source->buildPointZone ]; + zone->queuedBuildPoints = + MAX( 0, zone->queuedBuildPoints - value ); + } + } + } + } } - nobuild++; } } /* -============ -G_NobuildSave -Save all currently placed nobuild markers into the "nobuild" folder -============ +================ +G_UpdateBuildableRangeMarkers +================ */ -void G_NobuildSave( void ) +void G_UpdateBuildableRangeMarkers( void ) { - char map[ MAX_QPATH ]; - char fileName[ MAX_OSPATH ]; - fileHandle_t f; - int len; - int i; - gentity_t *ent; - char *s; + // is the entity 64-bit client-masking extension available? + qboolean maskingExtension = ( trap_Cvar_VariableIntegerValue( "sv_gppExtension" ) >= 1 ); - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - if( !map[ 0 ] ) + gentity_t *e; + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) { - G_Printf( "NobuildSave( ): no map is loaded\n" ); - return; - } - Com_sprintf( fileName, sizeof( fileName ), "nobuild/%s.dat", map ); + buildable_t bType; + team_t bTeam; + int i; - len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); - if( len < 0 ) - { - G_Printf( "nobuildsave: could not open %s\n", fileName ); - return; - } + if( e->s.eType != ET_BUILDABLE || !e->rangeMarker ) + continue; - G_Printf("nobuildsave: saving nobuild to %s\n", fileName ); + bType = e->s.modelindex; + bTeam = BG_Buildable( bType )->team; - for( i = 0; i < MAX_GENTITIES; i++ ) - { - ent = &level.gentities[ i ]; - if( ent->noBuild.isNB != qtrue ) - continue; + e->rangeMarker->s.pos = e->s.pos; + if( bType == BA_A_HIVE || bType == BA_H_TESLAGEN ) + VectorMA( e->s.pos.trBase, e->r.maxs[ 2 ], e->s.origin2, e->rangeMarker->s.pos.trBase ); + else if( bType == BA_A_TRAPPER || bType == BA_H_MGTURRET ) + vectoangles( e->s.origin2, e->rangeMarker->s.apos.trBase ); - s = va( "%f %f %f %f %f\n", - ent->r.currentOrigin[ 0 ], - ent->r.currentOrigin[ 1 ], - ent->r.currentOrigin[ 2 ], - ent->noBuild.Area, - ent->noBuild.Height ); - trap_FS_Write( s, strlen( s ), f ); + e->rangeMarker->r.singleClient = 0; + e->rangeMarker->r.hack.generic1 = 0; + + // remove any previously added NOCLIENT flags from the hack below + e->rangeMarker->r.svFlags &= ~SVF_NOCLIENT; + + for( i = 0; i < level.maxclients; ++i ) + { + gclient_t *client; + team_t team; + qboolean weaponDisplays, wantsToSee; + + client = &level.clients[ i ]; + if( client->pers.connected != CON_CONNECTED ) + continue; + + if( i >= 32 && !maskingExtension ) + { + // resort to not sending range markers at all + if( !trap_Cvar_VariableIntegerValue( "g_rangeMarkerWarningGiven" ) ) + { + trap_SendServerCommand( -1, "print \"" S_COLOR_YELLOW "WARNING: There is no " + "support for entity 64-bit client-masking on this server. Please update " + "your server executable. Until then, range markers will not be displayed " + "while there are clients with client numbers above 31 in the game.\n\"" ); + trap_Cvar_Set( "g_rangeMarkerWarningGiven", "1" ); + } + + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) + { + if( e->s.eType == ET_BUILDABLE && e->rangeMarker ) + e->rangeMarker->r.svFlags |= SVF_NOCLIENT; + } + + return; + } + + team = client->pers.teamSelection; + if( team != TEAM_NONE ) + { + weaponDisplays = ( BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) || + client->ps.weapon == WP_ABUILD || client->ps.weapon == WP_ABUILD2 ); + } + wantsToSee = !!( client->pers.buildableRangeMarkerMask & ( 1 << bType ) ); + + if( ( team == TEAM_NONE || ( team == bTeam && weaponDisplays ) ) && wantsToSee ) + { + if( i >= 32 ) + e->rangeMarker->r.hack.generic1 |= 1 << ( i - 32 ); + else + e->rangeMarker->r.singleClient |= 1 << i; + } + } + + trap_LinkEntity( e->rangeMarker ); } - trap_FS_FCloseFile( f ); } diff --git a/src/game/g_client.c b/src/game/g_client.c index c77d4f7..3481af9 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -79,97 +80,6 @@ void SP_info_human_intermission( gentity_t *ent ) { } -/* -=============== -G_OverflowCredits -=============== -*/ -void G_OverflowCredits( gclient_t *doner, int credits ) -{ - int i; - int maxCredits; - int clientNum; - - if( !g_creditOverflow.integer ) - return; - - if( doner->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - maxCredits = ALIEN_MAX_KILLS; - clientNum = level.lastCreditedAlien; - } - else if( doner->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - maxCredits = HUMAN_MAX_CREDITS; - clientNum = level.lastCreditedHuman; - } - else - { - return; - } - - if( g_creditOverflow.integer == 1 ) - { - // distribute to everyone on team - gentity_t *vic; - - i = 0; - while( credits > 0 && i < level.maxclients ) - { - i++; - clientNum++; - if( clientNum >= level.maxclients ) - clientNum = 0; - - vic = &g_entities[ clientNum ]; - if( vic->client->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] || - vic->client->ps.persistant[ PERS_CREDIT ] >= maxCredits ) - continue; - - if( vic->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - level.lastCreditedAlien = clientNum; - else - level.lastCreditedHuman = clientNum; - - if( vic->client->ps.persistant[ PERS_CREDIT ] + credits > maxCredits ) - { - credits -= maxCredits - vic->client->ps.persistant[ PERS_CREDIT ]; - vic->client->ps.persistant[ PERS_CREDIT ] = maxCredits; - } - else - { - vic->client->ps.persistant[ PERS_CREDIT ] += credits; - return; - } - } - } - else if( g_creditOverflow.integer == 2 ) - { - // distribute by team rank - gclient_t *cl; - - for( i = 0; i < level.numPlayingClients && credits > 0; i++ ) - { - // get the client list sorted by rank - cl = &level.clients[ level.sortedClients[ i ] ]; - if( cl->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] || - cl->ps.persistant[ PERS_CREDIT ] >= maxCredits ) - continue; - - if( cl->ps.persistant[ PERS_CREDIT ] + credits > maxCredits ) - { - credits -= maxCredits - cl->ps.persistant[ PERS_CREDIT ]; - cl->ps.persistant[ PERS_CREDIT ] = maxCredits; - } - else - { - cl->ps.persistant[ PERS_CREDIT ] += credits; - return; - } - } - } -} - /* =============== G_AddCreditToClient @@ -177,60 +87,30 @@ G_AddCreditToClient */ void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ) { + int capAmount; + if( !client ) return; - //if we're already at the max and trying to add credit then stop - if( cap ) - { - if( client->pers.teamSelection == PTE_ALIENS ) - { - if( client->pers.credit >= ALIEN_MAX_KILLS && - credit > 0 ) - { - G_OverflowCredits( client, credit ); - return; - } - } - else if( client->pers.teamSelection == PTE_HUMANS ) - { - if( client->pers.credit >= HUMAN_MAX_CREDITS && - credit > 0 ) - { - G_OverflowCredits( client, credit ); - return; - } - } - } - - client->pers.credit += credit; - - if( cap ) + if( cap && credit > 0 ) { - if( client->pers.teamSelection == PTE_ALIENS ) + capAmount = client->pers.teamSelection == TEAM_ALIENS ? + ALIEN_MAX_CREDITS : HUMAN_MAX_CREDITS; + if( client->pers.credit < capAmount ) { - if( client->pers.credit > ALIEN_MAX_KILLS ) - { - G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - ALIEN_MAX_KILLS ); - client->pers.credit = ALIEN_MAX_KILLS; - } - } - else if( client->pers.teamSelection == PTE_HUMANS ) - { - if( client->pers.credit > HUMAN_MAX_CREDITS ) - { - G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - HUMAN_MAX_CREDITS ); - client->pers.credit = HUMAN_MAX_CREDITS; - } + client->pers.credit += credit; + if( client->pers.credit > capAmount ) + client->pers.credit = capAmount; } } + else + client->pers.credit += credit; if( client->pers.credit < 0 ) client->pers.credit = 0; - // keep PERS_CREDIT in sync if not following - if( client->sess.spectatorState != SPECTATOR_FOLLOW ) - client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; + // Copy to ps so the client can access it + client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; } @@ -255,8 +135,8 @@ qboolean SpotWouldTelefrag( gentity_t *spot ) gentity_t *hit; vec3_t mins, maxs; - VectorAdd( spot->s.origin, playerMins, mins ); - VectorAdd( spot->s.origin, playerMaxs, maxs ); + VectorAdd( spot->r.currentOrigin, playerMins, mins ); + VectorAdd( spot->r.currentOrigin, playerMaxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for( i = 0; i < num; i++ ) @@ -270,75 +150,6 @@ qboolean SpotWouldTelefrag( gentity_t *spot ) return qfalse; } -/* -================ -G_SelectNearestDeathmatchSpawnPoint - -Find the spot that we DON'T want to use -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *G_SelectNearestDeathmatchSpawnPoint( vec3_t from ) -{ - gentity_t *spot; - vec3_t delta; - float dist, nearestDist; - gentity_t *nearestSpot; - - nearestDist = 999999; - nearestSpot = NULL; - spot = NULL; - - while( (spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) - { - VectorSubtract( spot->s.origin, from, delta ); - dist = VectorLength( delta ); - - if( dist < nearestDist ) - { - nearestDist = dist; - nearestSpot = spot; - } - } - - return nearestSpot; -} - - -/* -================ -G_SelectRandomDeathmatchSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *G_SelectRandomDeathmatchSpawnPoint( void ) -{ - gentity_t *spot; - int count; - int selection; - gentity_t *spots[ MAX_SPAWN_POINTS ]; - - count = 0; - spot = NULL; - - while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) - { - if( SpotWouldTelefrag( spot ) ) - continue; - - spots[ count ] = spot; - count++; - } - - if( !count ) // no spots that won't telefrag - return G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); - - selection = rand( ) % count; - return spots[ selection ]; -} - /* =========== @@ -347,7 +158,7 @@ G_SelectRandomFurthestSpawnPoint Chooses a player start, deathmatch start, etc ============ */ -gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +static gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { gentity_t *spot; vec3_t delta; @@ -364,7 +175,7 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, if( SpotWouldTelefrag( spot ) ) continue; - VectorSubtract( spot->s.origin, avoidPoint, delta ); + VectorSubtract( spot->r.currentOrigin, avoidPoint, delta ); dist = VectorLength( delta ); for( i = 0; i < numSpots; i++ ) @@ -406,18 +217,18 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, if( !spot ) G_Error( "Couldn't find a spawn point" ); - VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->r.currentOrigin, origin ); origin[ 2 ] += 9; - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentAngles, angles ); return spot; } // select a random spot from the spawn points furthest away rnd = random( ) * ( numSpots / 2 ); - VectorCopy( list_spot[ rnd ]->s.origin, origin ); + VectorCopy( list_spot[ rnd ]->r.currentOrigin, origin ); origin[ 2 ] += 9; - VectorCopy( list_spot[ rnd ]->s.angles, angles ); + VectorCopy( list_spot[ rnd ]->r.currentAngles, angles ); return list_spot[ rnd ]; } @@ -425,102 +236,45 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, /* ================ -G_SelectAlienSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -gentity_t *G_SelectAlienSpawnPoint( vec3_t preference ) -{ - gentity_t *spot; - int count; - gentity_t *spots[ MAX_SPAWN_POINTS ]; - - if( level.numAlienSpawns <= 0 ) - return NULL; - - count = 0; - spot = NULL; - - while( ( spot = G_Find( spot, FOFS( classname ), - BG_FindEntityNameForBuildable( BA_A_SPAWN ) ) ) != NULL ) - { - if( !spot->spawned ) - continue; - - if( spot->health <= 0 ) - continue; - - if( !spot->s.groundEntityNum ) - continue; - - if( spot->clientSpawnTime > 0 ) - continue; - - if( G_CheckSpawnPoint( spot->s.number, spot->s.origin, - spot->s.origin2, BA_A_SPAWN, NULL ) != NULL ) - continue; - - spots[ count ] = spot; - count++; - } - - if( !count ) - return NULL; - - return G_ClosestEnt( preference, spots, count ); -} - - -/* -================ -G_SelectHumanSpawnPoint +G_SelectSpawnBuildable -go to a random point that doesn't telefrag +find the nearest buildable of the right type that is +spawned/healthy/unblocked etc. ================ */ -gentity_t *G_SelectHumanSpawnPoint( vec3_t preference ) +static gentity_t *G_SelectSpawnBuildable( vec3_t preference, buildable_t buildable ) { - gentity_t *spot; - int count; - gentity_t *spots[ MAX_SPAWN_POINTS ]; - - if( level.numHumanSpawns <= 0 ) - return NULL; + gentity_t *search, *spot; - count = 0; - spot = NULL; + search = spot = NULL; - while( ( spot = G_Find( spot, FOFS( classname ), - BG_FindEntityNameForBuildable( BA_H_SPAWN ) ) ) != NULL ) + while( ( search = G_Find( search, FOFS( classname ), + BG_Buildable( buildable )->entityName ) ) != NULL ) { - if( !spot->spawned ) + if( !search->spawned ) continue; - if( spot->health <= 0 ) + if( search->health <= 0 ) continue; - if( !spot->s.groundEntityNum ) + if( search->s.groundEntityNum == ENTITYNUM_NONE ) continue; - if( spot->clientSpawnTime > 0 ) + if( search->clientSpawnTime > 0 ) continue; - if( G_CheckSpawnPoint( spot->s.number, spot->s.origin, - spot->s.origin2, BA_H_SPAWN, NULL ) != NULL ) + if( G_CheckSpawnPoint( search->s.number, search->r.currentOrigin, + search->s.origin2, buildable, NULL ) != NULL ) continue; - spots[ count ] = spot; - count++; + if( !spot || DistanceSquared( preference, search->r.currentOrigin ) < + DistanceSquared( preference, spot->r.currentOrigin ) ) + spot = search; } - if( !count ) - return NULL; - - return G_ClosestEnt( preference, spots, count ); + return spot; } - /* =========== G_SelectSpawnPoint @@ -541,25 +295,35 @@ G_SelectTremulousSpawnPoint Chooses a player start, deathmatch start, etc ============ */ -gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles ) +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ) { gentity_t *spot = NULL; - if( team == PTE_ALIENS ) - spot = G_SelectAlienSpawnPoint( preference ); - else if( team == PTE_HUMANS ) - spot = G_SelectHumanSpawnPoint( preference ); + if( team == TEAM_ALIENS ) + { + if( level.numAlienSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_A_SPAWN ); + } + else if( team == TEAM_HUMANS ) + { + if( level.numHumanSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_H_SPAWN ); + } //no available spots if( !spot ) return NULL; - if( team == PTE_ALIENS ) - G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin ); - else if( team == PTE_HUMANS ) - G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin ); + if( team == TEAM_ALIENS ) + G_CheckSpawnPoint( spot->s.number, spot->r.currentOrigin, spot->s.origin2, BA_A_SPAWN, origin ); + else if( team == TEAM_HUMANS ) + G_CheckSpawnPoint( spot->s.number, spot->r.currentOrigin, spot->s.origin2, BA_H_SPAWN, origin ); - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentAngles, angles ); angles[ ROLL ] = 0; return spot; @@ -567,44 +331,13 @@ gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t } -/* -=========== -G_SelectInitialSpawnPoint - -Try to find a spawn point marked 'initial', otherwise -use normal spawn selection. -============ -*/ -gentity_t *G_SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) -{ - gentity_t *spot; - - spot = NULL; - while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) - { - if( spot->spawnflags & 1 ) - break; - } - - if( !spot || SpotWouldTelefrag( spot ) ) - { - return G_SelectSpawnPoint( vec3_origin, origin, angles ); - } - - VectorCopy( spot->s.origin, origin ); - origin[ 2 ] += 9; - VectorCopy( spot->s.angles, angles ); - - return spot; -} - /* =========== G_SelectSpectatorSpawnPoint ============ */ -gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) +static gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { FindIntermissionPoint( ); @@ -633,8 +366,8 @@ gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ) if( !spot ) return G_SelectSpectatorSpawnPoint( origin, angles ); - VectorCopy( spot->s.origin, origin ); - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentOrigin, origin ); + VectorCopy( spot->r.currentAngles, angles ); return spot; } @@ -658,8 +391,8 @@ gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ) if( !spot ) return G_SelectSpectatorSpawnPoint( origin, angles ); - VectorCopy( spot->s.origin, origin ); - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentOrigin, origin ); + VectorCopy( spot->r.currentAngles, angles ); return spot; } @@ -681,7 +414,7 @@ BodySink After sitting around for five seconds, fall into the ground and dissapear ============= */ -void BodySink( gentity_t *ent ) +static void BodySink( gentity_t *ent ) { //run on first BodySink call if( !ent->active ) @@ -704,23 +437,6 @@ void BodySink( gentity_t *ent ) } -/* -============= -BodyFree - -After sitting around for a while the body becomes a freebie -============= -*/ -void BodyFree( gentity_t *ent ) -{ - ent->killedBy = -1; - - //if not claimed in the next minute destroy - ent->think = BodySink; - ent->nextthink = level.time + 60000; -} - - /* ============= SpawnCorpse @@ -729,17 +445,11 @@ A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ -void SpawnCorpse( gentity_t *ent ) +static void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; - vec3_t origin, dest; - trace_t tr; - float vDiff; - - // prevent crashing everyone with bad corpsenum bug - if( ent->client->pers.connected != CON_CONNECTED ) - return; + vec3_t origin, mins; VectorCopy( ent->r.currentOrigin, origin ); @@ -752,17 +462,18 @@ void SpawnCorpse( gentity_t *ent ) body = G_Spawn( ); - VectorCopy( ent->s.apos.trBase, body->s.angles ); + VectorCopy( ent->s.apos.trBase, body->s.apos.trBase ); + VectorCopy( ent->s.apos.trBase, body->r.currentAngles ); body->s.eFlags = EF_DEAD; body->s.eType = ET_CORPSE; - body->s.number = body - g_entities; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; - body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ]; + body->clipmask = MASK_DEADSOLID; + body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) body->classname = "humanCorpse"; else body->classname = "alienCorpse"; @@ -815,25 +526,19 @@ void SpawnCorpse( gentity_t *ent ) body->takedamage = qfalse; - body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; - ent->health = 0; + body->health = ent->health; //change body dimensions - BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); - vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], mins, NULL, NULL, body->r.mins, body->r.maxs ); //drop down to match the *model* origins of ent and body - VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); - trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); - VectorCopy( tr.endpos, origin ); + origin[2] += mins[ 2 ] - body->r.mins[ 2 ]; G_SetOrigin( body, origin ); - VectorCopy( origin, body->s.origin ); body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); - VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity( body ); } @@ -846,7 +551,7 @@ G_SetClientViewAngle ================== */ -void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) +void G_SetClientViewAngle( gentity_t *ent, const vec3_t angle ) { int i; @@ -859,8 +564,9 @@ void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ]; } - VectorCopy( angle, ent->s.angles ); - VectorCopy( ent->s.angles, ent->client->ps.viewangles ); + VectorCopy( angle, ent->s.apos.trBase ); + VectorCopy( angle, ent->r.currentAngles ); + VectorCopy( angle, ent->client->ps.viewangles ); } /* @@ -870,52 +576,79 @@ respawn */ void respawn( gentity_t *ent ) { + int i; + SpawnCorpse( ent ); - //TA: Clients can't respawn - they must go thru the class cmd + // Clients can't respawn - they must go through the class cmd ent->client->pers.classSelection = PCL_NONE; ClientSpawn( ent, NULL, NULL, NULL ); -} -/* -================ -TeamCount + // stop any following clients that don't have sticky spec on + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == ent - g_entities ) + { + if( !( level.clients[ i ].pers.stickySpec ) ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + else + G_FollowLockView( &g_entities[ i ] ); + } + } +} -Returns number of players on a team -================ -*/ -team_t TeamCount( int ignoreClientNum, int team ) +static qboolean G_IsEmoticon( const char *s, qboolean *escaped ) { - int i; - int count = 0; + int i, j; + const char *p = s; + char emoticon[ MAX_EMOTICON_NAME_LEN ] = {""}; + qboolean escape = qfalse; - for( i = 0 ; i < level.maxclients ; i++ ) + if( *p != '[' ) + return qfalse; + p++; + if( *p == '[' ) { - if( i == ignoreClientNum ) - continue; - - if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) - continue; - - if( level.clients[ i ].sess.sessionTeam == team ) - count++; + escape = qtrue; + p++; } - - return count; + i = 0; + while( *p && i < ( MAX_EMOTICON_NAME_LEN - 1 ) ) + { + if( *p == ']' ) + { + for( j = 0; j < level.emoticonCount; j++ ) + { + if( !Q_stricmp( emoticon, level.emoticons[ j ].name ) ) + { + *escaped = escape; + return qtrue; + } + } + return qfalse; + } + emoticon[ i++ ] = *p; + emoticon[ i ] = '\0'; + p++; + } + return qfalse; } - /* =========== -ClientCleanName +G_ClientCleanName ============ */ -static void ClientCleanName( const char *in, char *out, int outSize, qboolean special ) +static void G_ClientCleanName( const char *in, char *out, int outSize ) { int len, colorlessLen; - char ch; char *p; int spaces; + qboolean escaped; qboolean invalid = qfalse; //save room for trailing null byte @@ -927,44 +660,48 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp *p = 0; spaces = 0; - while( 1 ) + for( ; *in; in++ ) { - ch = *in++; - if( !ch ) - break; - // don't allow leading spaces - if( !*p && ch == ' ' ) + if( colorlessLen == 0 && *in == ' ' ) continue; // don't allow nonprinting characters or (dead) console keys - if( ch < ' ' || ch > '}' || ch == '`' || ch == '%' ) + if( *in < ' ' || *in > '}' || *in == '`' ) continue; // check colors - if( Q_IsColorString( in - 1 ) ) + if( Q_IsColorString( in ) ) { + in++; + // make sure room in dest for both chars if( len > outSize - 2 ) break; - *out++ = ch; - len += 2; + *out++ = Q_COLOR_ESCAPE; - // solo trailing carat is not a color prefix - if( !*in ) { - *out++ = COLOR_WHITE; - break; - } + *out++ = *in; - *out++ = *in; + len += 2; + continue; + } + else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) + { + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; - in++; + *out++ = '['; + *out++ = '['; + len += 2; + if( escaped ) + in++; continue; } // don't allow too many consecutive spaces - if( ch == ' ' ) + if( *in == ' ' ) { spaces++; if( spaces > 3 ) @@ -976,7 +713,7 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp if( len > outSize - 1 ) break; - *out++ = ch; + *out++ = *in; colorlessLen++; len++; } @@ -984,7 +721,7 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp *out = 0; // don't allow names beginning with "[skipnotify]" because it messes up /ignore-related code - if( !Q_strncmp( p, "[skipnotify]", 12 ) ) + if( !Q_stricmpn( p, "[skipnotify]", 12 ) ) invalid = qtrue; // don't allow comment-beginning strings because it messes up various parsers @@ -1001,37 +738,6 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp } -/* -=================== -G_NextNewbieName - -Generate a unique, known-good name for an UnnamedPlayer -=================== -*/ -char *G_NextNewbieName( gentity_t *ent ) -{ - char newname[ MAX_NAME_LENGTH ]; - char namePrefix[ MAX_NAME_LENGTH - 4 ]; - char err[ MAX_STRING_CHARS ]; - - if( g_newbieNamePrefix.string[ 0 ] ) - Q_strncpyz( namePrefix, g_newbieNamePrefix.string , sizeof( namePrefix ) ); - else - strcpy( namePrefix, "Newbie#" ); - - while( level.numNewbies < 10000 ) - { - strcpy( newname, va( "%s%i", namePrefix, level.numNewbies ) ); - if ( G_admin_name_check( ent, newname, err, sizeof( err ) ) ) - { - return va( "%s", newname ); - } - level.numNewbies++; // Only increments if the last requested name was used. - } - return "UnnamedPlayer"; -} - - /* ====================== G_NonSegModel @@ -1099,24 +805,19 @@ The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ -void ClientUserinfoChanged( int clientNum, qboolean forceName ) +char *ClientUserinfoChanged( int clientNum, qboolean forceName ) { gentity_t *ent; - int teamTask, teamLeader, health; - char *s; - char model[ MAX_QPATH ]; - char buffer[ MAX_QPATH ]; + char *s, *s2; + char model[ MAX_QPATH] = { '\0' }; + char buffer[ MAX_QPATH ] = { '\0' }; char filename[ MAX_QPATH ]; char oldname[ MAX_NAME_LENGTH ]; char newname[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; - qboolean showRenameMsg = qtrue; gclient_t *client; - char c1[ MAX_INFO_STRING ]; - char c2[ MAX_INFO_STRING ]; char userinfo[ MAX_INFO_STRING ]; - pTeam_t team; ent = g_entities + clientNum; client = ent->client; @@ -1130,81 +831,49 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) "disconnect \"illegal or malformed userinfo\n\"" ); trap_DropClient( ent - g_entities, "dropped: illegal or malformed userinfo"); + return "Illegal or malformed userinfo"; } + // If their userinfo overflowed, tremded is in the process of disconnecting them. + // If we send our own disconnect, it won't work, so just return to prevent crashes later + // in this function. This check must come after the Info_Validate call. + else if( !userinfo[ 0 ] ) + return "Empty (overflowed) userinfo"; - - // check for local client - s = Info_ValueForKey( userinfo, "ip" ); - - if( !strcmp( s, "localhost" ) ) - client->pers.localClient = qtrue; - - // check the item prediction - s = Info_ValueForKey( userinfo, "cg_predictItems" ); - - if( !atoi( s ) ) - client->pers.predictItemPickup = qfalse; - else - client->pers.predictItemPickup = qtrue; + // stickyspec toggle + s = Info_ValueForKey( userinfo, "cg_stickySpec" ); + client->pers.stickySpec = atoi( s ) != 0; // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); - - if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) - ClientCleanName( s, newname, sizeof( newname ), qfalse ); - else - ClientCleanName( s, newname, sizeof( newname ), qtrue ); + G_ClientCleanName( s, newname, sizeof( newname ) ); if( strcmp( oldname, newname ) ) { - if( !strlen( oldname ) && client->pers.connected != CON_CONNECTED ) - showRenameMsg = qfalse; - - // in case we need to revert and there's no oldname - if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) - ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qfalse ); - else - ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qtrue ); - - if( g_newbieNumbering.integer ) + if( !forceName && client->pers.namelog->nameChangeTime && + level.time - client->pers.namelog->nameChangeTime <= + g_minNameChangePeriod.value * 1000 ) { - if( !strcmp( newname, "UnnamedPlayer" ) ) - Q_strncpyz( newname, G_NextNewbieName( ent ), sizeof( newname ) ); - if( !strcmp( oldname, "UnnamedPlayer" ) ) - Q_strncpyz( oldname, G_NextNewbieName( ent ), sizeof( oldname ) ); + trap_SendServerCommand( ent - g_entities, va( + "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", + g_minNameChangePeriod.integer ) ); + revertName = qtrue; } - - - if( !forceName ) + else if( !forceName && g_maxNameChanges.integer > 0 && + client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) { - if( G_IsMuted( client ) ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You cannot change your name while you are muted\n\"" ); - revertName = qtrue; - } - else if( client->pers.nameChangeTime && - ( level.time - client->pers.nameChangeTime ) - <= ( g_minNameChangePeriod.value * 1000 ) ) - { - trap_SendServerCommand( ent - g_entities, va( - "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", - g_minNameChangePeriod.integer ) ); - revertName = qtrue; - } - else if( g_maxNameChanges.integer > 0 - && client->pers.nameChanges >= g_maxNameChanges.integer - && !G_admin_permission( ent, ADMF_SPECIAL ) ) - { - trap_SendServerCommand( ent - g_entities, va( - "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", - g_maxNameChanges.integer ) ); - revertName = qtrue; - } + trap_SendServerCommand( ent - g_entities, va( + "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", + g_maxNameChanges.integer ) ); + revertName = qtrue; } - - if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) + else if( !forceName && client->pers.namelog->muted ) + { + trap_SendServerCommand( ent - g_entities, + "print \"You cannot change your name while you are muted\n\"" ); + revertName = qtrue; + } + else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); revertName = qtrue; @@ -1212,110 +881,96 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) if( revertName ) { - Q_strncpyz( client->pers.netname, oldname, + Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", sizeof( client->pers.netname ) ); Info_SetValueForKey( userinfo, "name", oldname ); trap_SetUserinfo( clientNum, userinfo ); } else { - Q_strncpyz( client->pers.netname, newname, - sizeof( client->pers.netname ) ); - Info_SetValueForKey( userinfo, "name", newname ); - trap_SetUserinfo( clientNum, userinfo ); - if( client->pers.connected == CON_CONNECTED ) + G_CensorString( client->pers.netname, newname, + sizeof( client->pers.netname ), ent ); + if( !forceName && client->pers.connected == CON_CONNECTED ) + { + client->pers.namelog->nameChangeTime = level.time; + client->pers.namelog->nameChanges++; + } + if( *oldname ) { - client->pers.nameChangeTime = level.time; - client->pers.nameChanges++; + G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + oldname, client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); } } + G_namelog_update_name( client ); } - if( client->sess.sessionTeam == TEAM_SPECTATOR ) + if ( client->pers.teamSelection == TEAM_HUMANS ) { - if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) - Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) ); - } + int i; + qboolean found = qfalse; - if( client->pers.connected >= CON_CONNECTING && showRenameMsg ) - { - if( strcmp( oldname, client->pers.netname ) ) - { - //dont show if players invisible - if( client->sess.invisible != qtrue ) - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE - " renamed to %s^7\n\"", oldname, client->pers.netname ) ); - if( g_decolourLogfiles.integer) - { - char decoloured[ MAX_STRING_CHARS ] = ""; - if( g_decolourLogfiles.integer == 1 ) - { - Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\" -> \"%s^7\")", oldname, client->pers.netname ); - G_DecolorString( decoloured, decoloured ); - G_LogPrintfColoured( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); - } - else - { - G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); - } + s = Info_ValueForKey(userinfo, "model"); - } - else + for ( i = 0; i < level.playerModelCount; i++ ) + { + if ( !strcmp(s, level.playerModel[i]) ) { - G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"\n", clientNum, - client->pers.ip, client->pers.guid, oldname, client->pers.netname ); + found = qtrue; + break; } - G_admin_namelog_update( client, qfalse ); } - } - // set max health - health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - client->pers.maxHealth = health; + if ( !found ) + s = NULL; + else if ( !g_cheats.integer + && !forceName + && !G_admin_permission( ent, va("MODEL%s", s) ) ) + s = NULL; - if( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) - client->pers.maxHealth = 100; - - //hack to force a client update if the config string does not change between spawning - if( client->pers.classSelection == PCL_NONE ) - client->pers.maxHealth = 0; - - // set model - if( client->ps.stats[ STAT_PCLASS ] == PCL_HUMAN && BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) + if (s) + { + s2 = Info_ValueForKey(userinfo, "skin"); + s2 = GetSkin(s, s2); + } + } + else { - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), - BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); + s = NULL; } - else if( client->pers.classSelection == PCL_NONE ) + + if( client->pers.classSelection == PCL_NONE ) { //This looks hacky and frankly it is. The clientInfo string needs to hold different //model details to that of the spawning class or the info change will not be //registered and an axis appears instead of the player model. There is zero chance //the player can spawn with the battlesuit, hence this choice. - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), - BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, + BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); } else { - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( client->pers.classSelection ), - BG_FindSkinNameForClass( client->pers.classSelection ) ); - } - Q_strncpyz( model, buffer, sizeof( model ) ); + if ( !(client->pers.classSelection == PCL_HUMAN_BSUIT) && s ) + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", s, s2 ); + } + else + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, + BG_ClassConfig( client->pers.classSelection )->skinName ); + } - //don't bother setting model type if spectating - if( client->pers.classSelection != PCL_NONE ) - { //model segmentation Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", - BG_FindModelNameForClass( client->pers.classSelection ) ); + BG_ClassConfig( client->pers.classSelection )->modelName ); if( G_NonSegModel( filename ) ) client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; else client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; } + Q_strncpyz( model, buffer, sizeof( model ) ); // wallwalk follow s = Info_ValueForKey( userinfo, "cg_wwFollow" ); @@ -1333,13 +988,44 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; + // always sprint + s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; + + // fly speed + s = Info_ValueForKey( userinfo, "cg_flySpeed" ); + + if( *s ) + client->pers.flySpeed = atoi( s ); + else + client->pers.flySpeed = BG_Class( PCL_NONE )->speed; + + // disable blueprint errors + s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); + + if( atoi( s ) ) + client->pers.disableBlueprintErrors = qtrue; + else + client->pers.disableBlueprintErrors = qfalse; + + client->pers.buildableRangeMarkerMask = + atoi( Info_ValueForKey( userinfo, "cg_buildableRangeMarkerMask" ) ); + // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); - if( ! *s || atoi( s ) != 0 ) - client->pers.teamInfo = qtrue; + if( atoi( s ) != 0 ) + { + // teamoverlay was enabled so we need an update + if( client->pers.teamInfo == 0 ) + client->pers.teamInfo = 1; + } else - client->pers.teamInfo = qfalse; + client->pers.teamInfo = 0; s = Info_ValueForKey( userinfo, "cg_unlagged" ); if( !s[0] || atoi( s ) != 0 ) @@ -1347,67 +1033,26 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) else client->pers.useUnlagged = qfalse; - // team task (0 = none, 1 = offence, 2 = defence) - teamTask = atoi( Info_ValueForKey( userinfo, "teamtask" ) ); - // team Leader (1 = leader, 0 is normal player) - teamLeader = client->sess.teamLeader; - - // colors - strcpy( c1, Info_ValueForKey( userinfo, "color1" ) ); - strcpy( c2, Info_ValueForKey( userinfo, "color2" ) ); - - team = client->pers.teamSelection; + Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), + sizeof( client->pers.voice ) ); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds - if ( client->sess.invisible != qtrue ) - { - Com_sprintf( userinfo, sizeof( userinfo ), - "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\" - "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\" - "tl\\%d\\ig\\%16s", - client->pers.netname, team, model, model, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, - teamLeader, BG_ClientListString( &client->sess.ignoreList ) ); - - trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); - } else { - trap_SetConfigstring( CS_PLAYERS + clientNum, "" ); - } + + Com_sprintf( userinfo, sizeof( userinfo ), + "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", + client->pers.netname, client->pers.teamSelection, model, + Com_ClientListString( &client->sess.ignoreList ), + client->pers.voice ); + + trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); + /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ -} -/* -=========== -LogAutobahn -=========== -*/ -void G_LogAutobahn(gentity_t *ent, const char *userinfo, int rating, - qboolean onConnect) -{ - char ip_buffer[20]; - const char *ip, *name, *verb; - - verb = (onConnect ? "refused" : "dropped"); - - if (userinfo) { - Q_strncpyz(ip_buffer, Info_ValueForKey(userinfo, "ip"), - sizeof(ip_buffer)); - ip = ip_buffer; - name = Info_ValueForKey(userinfo, "name"); - } else { - ip = ent->client->pers.ip; - name = ent->client->pers.netname; - } - - G_LogPrintf("Autobahn: %s %i %s %+i \"%s^7\"\n", verb, ent - g_entities, - ip, rating, name); - - if (g_adminAutobahnNotify.integer) - G_AdminsPrintf("Autobahn %s '%s^7' with rating %+i, connecting from %s.\n", - verb, name, rating, ip); + return NULL; } + /* =========== ClientConnect @@ -1428,52 +1073,59 @@ to the server machine, but qfalse on map changes and tournement restarts. ============ */ -char *ClientConnect( int clientNum, qboolean firstTime ) +const char *ClientConnect( int clientNum, qboolean firstTime ) { char *value; + char *userInfoError; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; gentity_t *ent; - char guid[ 33 ]; - char ip[ 16 ] = {""}; char reason[ MAX_STRING_CHARS ] = {""}; int i; ent = &g_entities[ clientNum ]; + client = &level.clients[ clientNum ]; + + // ignore if client already connected + if( client->pers.connected != CON_DISCONNECTED ) + return NULL; + + ent->client = client; + memset( client, 0, sizeof( *client ) ); trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); value = Info_ValueForKey( userinfo, "cl_guid" ); - Q_strncpyz( guid, value, sizeof( guid ) ); - - // check for admin ban - if( G_admin_ban_check( userinfo, reason, sizeof( reason ) ) ) - { - return va( "%s", reason ); - } + Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); - // IP filtering - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 - // recommanding PB based IP / GUID banning, the builtin system is pretty limited - // check to see if they are on the banned IP list value = Info_ValueForKey( userinfo, "ip" ); - i = 0; - while( *value && i < sizeof( ip ) - 2 ) + // check for local client + if( !strcmp( value, "localhost" ) ) + client->pers.localClient = qtrue; + G_AddressParse( value, &client->pers.ip ); + + client->pers.admin = G_admin_admin( client->pers.guid ); + + client->pers.alternateProtocol = trap_Cvar_VariableIntegerValue( va( "sv_clAltProto%i", clientNum ) ); + + if( client->pers.alternateProtocol == 2 && client->pers.guid[ 0 ] == '\0' ) { - if( *value != '.' && ( *value < '0' || *value > '9' ) ) - break; - ip[ i++ ] = *value; - value++; + size_t len = strlen( client->pers.ip.str ); + if( len == 0 ) + len = 1; + for( i = 0; i < sizeof( client->pers.guid ) - 1; ++i ) + { + int j = client->pers.ip.str[ i % len ] + rand() / ( RAND_MAX / 16 + 1 ); + client->pers.guid[ i ] = "0123456789ABCDEF"[ j % 16 ]; + } + client->pers.guid[ sizeof( client->pers.guid ) - 1 ] = '\0'; + client->pers.guidless = qtrue; } - ip[ i ] = '\0'; - if( G_FilterPacket( value ) ) - return "You are banned from this server."; - if( strlen( ip ) < 7 && strcmp( Info_ValueForKey( userinfo, "ip" ), "localhost" ) ) + // check for admin ban + if( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) { - G_AdminsPrintf( "Connect from client with invalid IP: '%s' NAME: '%s^7'\n", - ip, Info_ValueForKey( userinfo, "name" ) ); - return "Invalid client data"; + return va( "%s", reason ); } // check for a password @@ -1483,66 +1135,12 @@ char *ClientConnect( int clientNum, qboolean firstTime ) strcmp( g_password.string, value ) != 0 ) return "Invalid password"; - schachtmeisterJudgement_t *smj = NULL; - - if (!(G_admin_permission_guid(guid, ADMF_NOAUTOBAHN) - || G_admin_permission_guid(guid, ADMF_IMMUNITY))) - { - extern g_admin_namelog_t *g_admin_namelog[128]; - for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; ++i) - { - if (!Q_stricmp(g_admin_namelog[i]->ip, ip) - || !Q_stricmp(g_admin_namelog[i]->guid, guid)) - { - schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; - if (j->ratingTime) - { - if (j->rating >= g_schachtmeisterClearThreshold.integer) - break; - else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) - { - G_LogAutobahn( ent, userinfo, j->rating, qtrue ); - return g_schachtmeisterAutobahnMessage.string; - } - smj = j; - } - break; - } - } - } - - // they can connect - ent->client = level.clients + clientNum; - client = ent->client; - - memset( client, 0, sizeof(*client) ); - // add guid to session so we don't have to keep parsing userinfo everywhere - if( !guid[ 0 ] ) - { - Q_strncpyz( client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - sizeof( client->pers.guid ) ); - } - else - { - Q_strncpyz( client->pers.guid, guid, sizeof( client->pers.guid ) ); - } + for( i = 0; i < sizeof( client->pers.guid ) - 1 && + isxdigit( client->pers.guid[ i ] ); i++ ); - Q_strncpyz( client->pers.ip, ip, sizeof( client->pers.ip ) ); - client->pers.adminLevel = G_admin_level( ent ); - - // do autoghost now so that there won't be any name conflicts later on - if ( g_autoGhost.integer && client->pers.guid[ 0 ] != 'X' ) - { - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - if ( i != ent - g_entities && g_entities[i].client && g_entities[i].client->pers.connected != CON_DISCONNECTED && !Q_stricmp( g_entities[i].client->pers.guid, client->pers.guid ) ) - { - trap_SendServerCommand( i, "disconnect \"You may not be connected to this server multiple times\"" ); - trap_DropClient( i, "disconnected" ); - } - } - } + if( i < sizeof( client->pers.guid ) - 1 ) + return "Invalid GUID"; client->pers.connected = CON_CONNECTING; @@ -1552,81 +1150,36 @@ char *ClientConnect( int clientNum, qboolean firstTime ) G_ReadSessionData( client ); - if( firstTime ) - client->pers.firstConnect = qtrue; - else - client->pers.firstConnect = qfalse; - // get and distribute relevent paramters - ClientUserinfoChanged( clientNum, qfalse ); - - G_admin_set_adminname( ent ); - - if( g_decolourLogfiles.integer ) - { - char decoloured[ MAX_STRING_CHARS ] = ""; - if( g_decolourLogfiles.integer == 1 ) - { - Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\")", client->pers.netname ); - G_DecolorString( decoloured, decoloured ); - G_LogPrintfColoured( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, client->pers.netname, decoloured ); - } - else - { - G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, client->pers.netname, decoloured ); - } - } - else - { - G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"\n", clientNum, - client->pers.ip, client->pers.guid, client->pers.netname ); - } - - if( client->pers.adminLevel ) - { - G_LogPrintf( "ClientAuth: %i [%s] \"%s^7\" authenticated to admin level %i using GUID %s (^7%s)\n", clientNum, client->pers.ip, client->pers.netname, client->pers.adminLevel, client->pers.guid, client->pers.adminName ); - } + G_namelog_connect( client ); + userInfoError = ClientUserinfoChanged( clientNum, qfalse ); + if( userInfoError != NULL ) + return userInfoError; + + G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); // don't do the "xxx connected" messages if they were caried over from previous level - if( client->sess.invisible != qtrue ) - { - if( firstTime ) - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) ); + if( firstTime ) + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", + client->pers.netname ) ); - // count current clients and rank for scoreboard - CalculateRanks( ); - G_admin_namelog_update( client, qfalse ); - } + if( client->pers.admin ) + G_admin_authlog( ent ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + // if this is after !restart keepteams or !restart switchteams, apply said selection - if ( client->sess.restartTeam != PTE_NONE ) { + if ( client->sess.restartTeam != TEAM_NONE ) + { G_ChangeTeam( ent, client->sess.restartTeam ); - client->sess.restartTeam = PTE_NONE; + client->sess.restartTeam = TEAM_NONE; } - if( !( G_admin_permission( ent, ADMF_NOAUTOBAHN ) || - G_admin_permission( ent, ADMF_IMMUNITY ) ) ) - { - extern g_admin_namelog_t *g_admin_namelog[ 128 ]; - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - if( !Q_stricmp( ip, g_admin_namelog[ i ]->ip ) || !Q_stricmp( guid, g_admin_namelog[ i ]->guid ) ) - { - schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; - if( j->ratingTime ) - { - if( j->rating >= g_schachtmeisterClearThreshold.integer ) - break; - else if( j->rating <= g_schachtmeisterAutobahnThreshold.integer ) - return g_schachtmeisterAutobahnMessage.string; - G_AdminsPrintf( "%s^7 (#%d) has rating %d\n", ent->client->pers.netname, ent - g_entities, j->rating ); - } - break; - } - } - } return NULL; } @@ -1635,9 +1188,9 @@ char *ClientConnect( int clientNum, qboolean firstTime ) =========== ClientBegin -called when a client has finished connecting, and is ready -to be placed into the level. This will happen every level load, -and on transition between teams, but doesn't happen on respawns +Called when a client has finished connecting, and is ready +to be placed into the level. This will happen on every +level load and level restart, but doesn't happen on respawns. ============ */ void ClientBegin( int clientNum ) @@ -1650,6 +1203,10 @@ void ClientBegin( int clientNum ) client = level.clients + clientNum; + // ignore if client already entered the game + if( client->pers.connected != CON_CONNECTING ) + return; + if( ent->r.linked ) trap_UnlinkEntity( ent ); @@ -1660,8 +1217,6 @@ void ClientBegin( int clientNum ) client->pers.connected = CON_CONNECTED; client->pers.enterTime = level.time; - client->pers.teamState.state = TEAM_BEGIN; - client->pers.classSelection = PCL_NONE; // save eflags around this, because changing teams will // cause this to happen with a valid entity, and we @@ -1674,44 +1229,19 @@ void ClientBegin( int clientNum ) client->ps.eFlags = flags; // locate ent at a spawn point - ClientSpawn( ent, NULL, NULL, NULL ); - // Ignore invisible players for this section: - if ( client->sess.invisible != qtrue ) - { - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - - // auto denybuild - if( G_admin_permission( ent, ADMF_NO_BUILD ) ) - client->pers.denyBuild = qtrue; - - // auto mute flag - if( G_admin_permission( ent, ADMF_NO_CHAT ) ) - client->pers.muted = qtrue; + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - // name can change between ClientConnect() and ClientBegin() - G_admin_namelog_update( client, qfalse ); + G_namelog_restore( client ); - if( g_scrimMode.integer == 1 ) - { - ADMP( "^5Scrim mode is enabled. Teams are locked and you can only use spectator chat.\n" ); - } - - // request the clients PTR code - trap_SendServerCommand( ent - g_entities, "ptrcrequest" ); - } G_LogPrintf( "ClientBegin: %i\n", clientNum ); - if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) && - g_outdatedClientMessage.string[0] ) - { - trap_SendServerCommand( client->ps.clientNum, va( - "print \"%s\n\"", g_outdatedClientMessage.string ) ); - } - // count current clients and rank for scoreboard CalculateRanks( ); + + // send the client a list of commands that can be used + G_ListCommands( ent ); } /* @@ -1723,7 +1253,7 @@ after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ -void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) +void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; @@ -1731,8 +1261,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles int i; clientPersistant_t saved; clientSession_t savedSess; + qboolean savedNoclip, savedCliprcontents; int persistant[ MAX_PERSISTANT ]; - gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; @@ -1742,75 +1272,65 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles int maxAmmo, maxClips; weapon_t weapon; - index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; - //TA: only start client if chosen a class and joined a team - if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE ) + //if client is dead and following teammate, stop following before spawning + if( client->sess.spectatorClient != -1 ) { - client->sess.sessionTeam = TEAM_SPECTATOR; + client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } + + // only start client if chosen a class and joined a team + if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) + client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) - { - client->sess.sessionTeam = TEAM_SPECTATOR; client->sess.spectatorState = SPECTATOR_LOCKED; - } - - //if client is dead and following teammate, stop following before spawning - if(ent->client->sess.spectatorClient!=-1) - { - ent->client->sess.spectatorClient = -1; - ent->client->sess.spectatorState = SPECTATOR_FREE; - } - - if( origin != NULL ) - VectorCopy( origin, spawn_origin ); - if( angles != NULL ) - VectorCopy( angles, spawn_angles ); + // if client is dead and following teammate, stop following before spawning + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client - if( client->sess.sessionTeam == TEAM_SPECTATOR ) + if( client->sess.spectatorState != SPECTATOR_NOT ) { - if( teamLocal == PTE_NONE ) - spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); - else if( teamLocal == PTE_ALIENS ) - spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( teamLocal == PTE_HUMANS ) - spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + if( teamLocal == TEAM_ALIENS ) + spawn = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == TEAM_HUMANS ) + spawn = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + else + spawn = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); } else { - if( spawn == NULL ) + if( origin == NULL || angles == NULL ) { - G_Error( "ClientSpawn: spawn is NULL\n" ); + G_Error( "ClientSpawn: origin or angles is NULL" ); return; } - spawnPoint = spawn; + VectorCopy( origin, spawn_origin ); + VectorCopy( angles, spawn_angles ); - if( ent != spawn ) + if( spawn != NULL && spawn != ent ) { //start spawn animation on spawnPoint - G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); + G_SetBuildableAnim( spawn, BANIM_SPAWN1, qtrue ); - if( spawnPoint->biteam == PTE_ALIENS ) - spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; - else if( spawnPoint->biteam == PTE_HUMANS ) - spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; + if( spawn->buildableTeam == TEAM_ALIENS ) + spawn->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; + else if( spawn->buildableTeam == TEAM_HUMANS ) + spawn->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } - client->pers.teamState.state = TEAM_ACTIVE; // toggle the teleport bit so the client knows to not lerp - flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED ); - flags ^= EF_TELEPORT_BIT; + flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data @@ -1818,6 +1338,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; + savedNoclip = client->noclip; + savedCliprcontents = client->cliprcontents; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; @@ -1828,6 +1350,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; + client->noclip = savedNoclip; + client->cliprcontents = savedCliprcontents; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) @@ -1837,11 +1361,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; - client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam; - - // restore really persistant things - client->ps.persistant[ PERS_SCORE ] = client->pers.score; - client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; + client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; @@ -1853,52 +1373,58 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; - ent->inuse = qtrue; ent->classname = "player"; - ent->r.contents = CONTENTS_BODY; - ent->clipmask = MASK_PLAYERSOLID; + if( client->noclip ) + client->cliprcontents = CONTENTS_BODY; + else + ent->r.contents = CONTENTS_BODY; + if( client->pers.teamSelection == TEAM_NONE ) + ent->clipmask = MASK_DEADSOLID; + else + ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; - ent->flags = 0; + ent->flags &= FL_GODMODE | FL_NOTARGET; - //TA: calculate each client's acceleration + // calculate each client's acceleration ent->evaluateAcceleration = qtrue; - client->ps.stats[ STAT_WEAPONS ] = 0; - client->ps.stats[ STAT_WEAPONS2 ] = 0; - client->ps.stats[ STAT_SLOTS ] = 0; + client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; - BG_FindBBoxForClass( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); - if( client->sess.sessionTeam != TEAM_SPECTATOR ) - client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = - BG_FindHealthForClass( ent->client->pers.classSelection ); + if( client->sess.spectatorState == SPECTATOR_NOT ) + client->ps.stats[ STAT_MAX_HEALTH ] = + BG_Class( ent->client->pers.classSelection )->health; else - client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = 100; + client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { - BG_AddWeaponToInventory( WP_BLASTER, client->ps.stats ); BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } - else if( client->sess.sessionTeam != TEAM_SPECTATOR ) - weapon = BG_FindStartWeaponForClass( ent->client->pers.classSelection ); + else if( client->sess.spectatorState == SPECTATOR_NOT ) + weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; - BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); - BG_AddWeaponToInventory( weapon, client->ps.stats ); + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; + client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; - ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection; - ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; + // We just spawned, not changing weapons + client->ps.persistant[ PERS_NEWWEAPON ] = 0; + + ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; @@ -1911,18 +1437,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; - client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; + client->ps.stats[ STAT_HEALTH ] = ent->health; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; - client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; - - if( mod_jetpackFuel.value >= 10.0f ) { - client->jetpackfuel = mod_jetpackFuel.value; - } + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); @@ -1931,10 +1453,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles #define F_VEL 50.0f //give aliens some spawn velocity - if( client->sess.sessionTeam != TEAM_SPECTATOR && - client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - if( ent == spawn ) + if( spawn == NULL ) + { + G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); + } + else if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); @@ -1944,13 +1470,13 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); - if( spawnPoint->s.origin2[ 2 ] > 0.0f ) + if( spawn->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); - VectorAdd( spawnPoint->s.origin2, forward, dir ); + VectorAdd( spawn->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); @@ -1959,11 +1485,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } - else if( client->sess.sessionTeam != TEAM_SPECTATOR && - client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - spawn_angles[ YAW ] += 180.0f; - AngleNormalize360( spawn_angles[ YAW ] ); + if( spawn != NULL ) + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + } } // the respawned flag will be cleared after the attack and jump keys come up @@ -1972,13 +1501,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); - if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) ) + if( client->sess.spectatorState == SPECTATOR_NOT ) { - /*G_KillBox( ent );*/ //blame this if a newly spawned client gets stuck in another trap_LinkEntity( ent ); // force the base weapon up - client->ps.weapon = WP_NONE; + if( client->pers.teamSelection == TEAM_HUMANS ) + G_ForceWeaponChange( ent, weapon ); + client->ps.weaponstate = WEAPON_READY; } @@ -1987,8 +1517,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles client->ps.pm_time = 100; client->respawnTime = level.time; - if( g_gradualFreeFunds.integer < 2 ) - client->pers.lastFreekillTime = level.time; + ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; @@ -2002,21 +1531,10 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles else { // fire the targets of the spawn point - if( !spawn ) - G_UseTargets( spawnPoint, ent ); + if( spawn != NULL && spawn != ent ) + G_UseTargets( spawn, ent ); - // select the highest weapon number available, after any - // spawn given items have fired - client->ps.weapon = 1; - - for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) - { - if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) - { - client->ps.weapon = i; - break; - } - } + client->ps.weapon = client->ps.stats[ STAT_WEAPON ]; } // run a client frame to drop exactly to the floor, @@ -2025,15 +1543,16 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); + VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // positively link the client, even if the command times are weird - if( client->sess.sessionTeam != TEAM_SPECTATOR ) + if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } - //TA: must do this here so the number of active clients is calculated + // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else @@ -2041,6 +1560,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + client->pers.infoChangeTime = level.time; } @@ -2061,55 +1582,40 @@ void ClientDisconnect( int clientNum ) gentity_t *ent; gentity_t *tent; int i; - buildHistory_t *ptr; ent = g_entities + clientNum; - if( !ent->client ) + if( !ent->client || ent->client->pers.connected == CON_DISCONNECTED ) return; - // look through the bhist and readjust it if the referenced ent has left - for( ptr = level.buildHistory; ptr; ptr = ptr->next ) - { - if( ptr->ent == ent ) - { - ptr->ent = NULL; - Q_strncpyz( ptr->name, ent->client->pers.netname, MAX_NETNAME ); - } - } - - if ( ent->client->sess.invisible != qtrue ) - G_admin_namelog_update( ent->client, qtrue ); G_LeaveTeam( ent ); + G_namelog_disconnect( ent->client ); + G_Vote( ent, TEAM_NONE, qfalse ); // stop any following clients for( i = 0; i < level.maxclients; i++ ) { // remove any /ignore settings for this clientNum - BG_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); + Com_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); } // send effect if they were completely connected if( ent->client->pers.connected == CON_CONNECTED && - ent->client->sess.sessionTeam != TEAM_SPECTATOR ) + ent->client->sess.spectatorState == SPECTATOR_NOT ) { tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = ent->s.clientNum; } - if( ent->client->pers.connection ) - ent->client->pers.connection->clientNum = -1; - - G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s\"\n", clientNum, - ent->client->pers.ip, ent->client->pers.guid, ent->client->pers.netname ); + G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, + ent->client->pers.ip.str, ent->client->pers.guid, ent->client->pers.netname ); trap_UnlinkEntity( ent ); - ent->s.modelindex = 0; ent->inuse = qfalse; ent->classname = "disconnected"; ent->client->pers.connected = CON_DISCONNECTED; - ent->client->ps.persistant[ PERS_TEAM ] = TEAM_FREE; - ent->client->sess.sessionTeam = TEAM_FREE; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_NOT; trap_SetConfigstring( CS_PLAYERS + clientNum, ""); diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index c380b45..4dccf6a 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,59 +17,41 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "g_local.h" +static qboolean G_RoomForClassChange( gentity_t*, class_t, vec3_t ); + /* ================== G_SanitiseString -Remove case and control characters from a player name +Remove color codes and non-alphanumeric characters from a string ================== */ void G_SanitiseString( char *in, char *out, int len ) { - qboolean skip = qtrue; - int spaces = 0; + len--; while( *in && len > 0 ) { - // strip leading white space - if( *in == ' ' ) - { - if( skip ) - { - in++; - continue; - } - spaces++; - } - else - { - spaces = 0; - skip = qfalse; - } - if( Q_IsColorString( in ) ) { in += 2; // skip color code continue; } - if( *in < 32 ) + if( isalnum( *in ) ) { - in++; - continue; + *out++ = tolower( *in ); + len--; } - - *out++ = tolower( *in++ ); - len--; + in++; } - out -= spaces; *out = 0; } @@ -77,36 +60,69 @@ void G_SanitiseString( char *in, char *out, int len ) G_ClientNumberFromString Returns a player number for either a number or name string -Returns -1 if invalid +Returns -1 and optionally sets err if invalid or not exactly 1 match +err will have a trailing \n if set ================== */ -int G_ClientNumberFromString( gentity_t *to, char *s ) +int G_ClientNumberFromString( char *s, char *err, int len ) { gclient_t *cl; - int idnum; - char s2[ MAX_STRING_CHARS ]; - char n2[ MAX_STRING_CHARS ]; + int i, found = 0, m = -1; + char s2[ MAX_NAME_LENGTH ]; + char n2[ MAX_NAME_LENGTH ]; + char *p = err; + int l, l2 = len; + + if( !s[ 0 ] ) + { + if( p ) + Q_strncpyz( p, "no player name or slot # provided\n", len ); + + return -1; + } // numeric values are just slot numbers - if( s[ 0 ] >= '0' && s[ 0 ] <= '9' ) + for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); + if( !s[ i ] ) { - idnum = atoi( s ); + i = atoi( s ); - if( idnum < 0 || idnum >= level.maxclients ) + if( i < 0 || i >= level.maxclients ) return -1; - cl = &level.clients[ idnum ]; + cl = &level.clients[ i ]; if( cl->pers.connected == CON_DISCONNECTED ) + { + if( p ) + Q_strncpyz( p, "no player connected in that slot #\n", len ); + return -1; + } - return idnum; + return i; } - // check for a name match G_SanitiseString( s, s2, sizeof( s2 ) ); + if( !s2[ 0 ] ) + { + if( p ) + Q_strncpyz( p, "no player name provided\n", len ); + + return -1; + } + + if( p ) + { + Q_strncpyz( p, "more than one player name matches. " + "be more specific or use the slot #:\n", l2 ); + l = strlen( p ); + p += l; + l2 -= l; + } - for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ ) + // check for a name match + for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) { if( cl->pers.connected == CON_DISCONNECTED ) continue; @@ -114,54 +130,29 @@ int G_ClientNumberFromString( gentity_t *to, char *s ) G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) ); if( !strcmp( n2, s2 ) ) - return idnum; - } + return i; - return -1; -} - - -/* -================== -G_MatchOnePlayer - -This is a companion function to G_ClientNumbersFromString() - -returns qtrue if the int array plist only has one client id, false otherwise -In the case of false, err will be populated with an error message. -================== -*/ -qboolean G_MatchOnePlayer( int *plist, char *err, int len ) -{ - gclient_t *cl; - int *p; - char line[ MAX_NAME_LENGTH + 10 ] = {""}; - - err[ 0 ] = '\0'; - if( plist[ 0 ] == -1 ) - { - Q_strcat( err, len, "no connected player by that name or slot #" ); - return qfalse; - } - if( plist[ 1 ] != -1 ) - { - Q_strcat( err, len, "more than one player name matches. " - "be more specific or use the slot #:\n" ); - for( p = plist; *p != -1; p++ ) + if( strstr( n2, s2 ) ) { - cl = &level.clients[ *p ]; - if( cl->pers.connected == CON_CONNECTED ) + if( p ) { - Com_sprintf( line, sizeof( line ), "%2i - %s^7\n", - *p, cl->pers.netname ); - if( strlen( err ) + strlen( line ) > len ) - break; - Q_strcat( err, len, line ); + l = Q_snprintf( p, l2, "%-2d - %s^7\n", i, cl->pers.netname ); + p += l; + l2 -= l; } + + found++; + m = i; } - return qfalse; } - return qtrue; + + if( found == 1 ) + return m; + + if( found == 0 && err ) + Q_strncpyz( err, "no connected player by that name or slot #\n", len ); + + return -1; } /* @@ -171,22 +162,27 @@ G_ClientNumbersFromString Sets plist to an array of integers that represent client numbers that have names that are a partial match for s. -Returns number of matching clientids up to MAX_CLIENTS. +Returns number of matching clientids up to max. ================== */ -int G_ClientNumbersFromString( char *s, int *plist) +int G_ClientNumbersFromString( char *s, int *plist, int max ) { gclient_t *p; int i, found = 0; + char *endptr; char n2[ MAX_NAME_LENGTH ] = {""}; char s2[ MAX_NAME_LENGTH ] = {""}; - int max = MAX_CLIENTS; - // if a number is provided, it might be a slot # - for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); - if( !s[ i ] ) + if( max == 0 ) + return 0; + + if( !s[ 0 ] ) + return 0; + + // if a number is provided, it is a clientnum + i = strtol( s, &endptr, 10 ); + if( *endptr == '\0' ) { - i = atoi( s ); if( i >= 0 && i < level.maxclients ) { p = &level.clients[ i ]; @@ -197,15 +193,14 @@ int G_ClientNumbersFromString( char *s, int *plist) } } // we must assume that if only a number is provided, it is a clientNum - *plist = -1; return 0; } // now look for name matches G_SanitiseString( s, s2, sizeof( s2 ) ); - if( strlen( s2 ) < 1 ) + if( !s2[ 0 ] ) return 0; - for( i = 0; i < level.maxclients && found <= max; i++ ) + for( i = 0; i < level.maxclients && found < max; i++ ) { p = &level.clients[ i ]; if( p->pers.connected == CON_DISCONNECTED ) @@ -219,7 +214,6 @@ int G_ClientNumbersFromString( char *s, int *plist) found++; } } - *plist = -1; return found; } @@ -254,15 +248,12 @@ void ScoreboardMessage( gentity_t *ent ) if( cl->pers.connected == CON_CONNECTING ) ping = -1; - else if( cl->sess.spectatorState == SPECTATOR_FOLLOW ) - ping = cl->pers.ping < 999 ? cl->pers.ping : 999; else ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - //If (loop) client is a spectator, they have nothing, so indicate such. - //Only send the client requesting the scoreboard the weapon/upgrades information for members of their team. If they are not on a team, send it all. - if( cl->sess.sessionTeam != TEAM_SPECTATOR && - (ent->client->pers.teamSelection == PTE_NONE || cl->pers.teamSelection == ent->client->pers.teamSelection ) ) + if( cl->sess.spectatorState == SPECTATOR_NOT && + ( ent->client->pers.teamSelection == TEAM_NONE || + cl->pers.teamSelection == ent->client->pers.teamSelection ) ) { weapon = cl->ps.weapon; @@ -286,19 +277,19 @@ void ScoreboardMessage( gentity_t *ent ) } Com_sprintf( entry, sizeof( entry ), - " %d %d %d %d %d %d", level.sortedClients[ i ], cl->pers.score, ping, - ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade ); + " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ], + ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade ); j = strlen( entry ); - if( stringlength + j > 1024 ) + if( stringlength + j >= sizeof( string ) ) break; strcpy( string + stringlength, entry ); stringlength += j; } - trap_SendServerCommand( ent-g_entities, va( "scores %i %i %i%s", i, + trap_SendServerCommand( ent-g_entities, va( "scores %i %i%s", level.alienKills, level.humanKills, string ) ); } @@ -346,50 +337,120 @@ char *ConcatArgs( int start ) /* ================== -G_Flood_Limited - -Determine whether a user is flood limited, and adjust their flood demerits +ConcatArgsPrintable +Duplicate of concatargs but enquotes things that need to be +Used to log command arguments in a way that preserves user intended tokenizing ================== */ +char *ConcatArgsPrintable( int start ) +{ + int i, c, tlen; + static char line[ MAX_STRING_CHARS ]; + int len; + char arg[ MAX_STRING_CHARS + 2 ]; + const char* printArg; + + len = 0; + c = trap_Argc( ); + + for( i = start; i < c; i++ ) + { + printArg = arg; + trap_Argv( i, arg, sizeof( arg ) ); + if( strchr( arg, ' ' ) ) + printArg = va( "\"%s\"", arg ); + tlen = strlen( printArg ); + + if( len + tlen >= MAX_STRING_CHARS - 1 ) + break; + + memcpy( line + len, printArg, tlen ); + len += tlen; + + if( len == MAX_STRING_CHARS - 1 ) + break; + + if( i != c - 1 ) + { + line[ len ] = ' '; + len++; + } + } + + line[ len ] = 0; -qboolean G_Flood_Limited( gentity_t *ent ) + return line; +} + +static void Give_Class( gentity_t *ent, char *s ) { - int millisSinceLastCommand; - int maximumDemerits; + class_t currentClass = ent->client->pers.classSelection; + int clientNum = ent->client - level.clients; + vec3_t infestOrigin; + vec3_t oldVel; + int oldBoostTime = -1; + int newClass = BG_ClassByName( s )->number; - // This shouldn't be called if g_floodMinTime isn't set, but handle it anyway. - if( !g_floodMinTime.integer ) - return qfalse; - - // Do not limit admins with no censor/flood flag - if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) - return qfalse; - - millisSinceLastCommand = level.time - ent->client->pers.lastFloodTime; - if( millisSinceLastCommand < g_floodMinTime.integer ) - ent->client->pers.floodDemerits += ( g_floodMinTime.integer - millisSinceLastCommand ); - else + if( newClass == PCL_NONE ) + return; + + if( !G_RoomForClassChange( ent, newClass, infestOrigin ) ) { - ent->client->pers.floodDemerits -= ( millisSinceLastCommand - g_floodMinTime.integer ); - if( ent->client->pers.floodDemerits < 0 ) - ent->client->pers.floodDemerits = 0; + ADMP("give: not enough room to evolve\n"); + return; } - ent->client->pers.lastFloodTime = level.time; + ent->client->pers.evolveHealthFraction + = (float)ent->client->ps.stats[ STAT_HEALTH ] + / (float)BG_Class( currentClass )->health; - // If g_floodMaxDemerits == 0, then we go against g_floodMinTime^2. - - if( !g_floodMaxDemerits.integer ) - maximumDemerits = g_floodMinTime.integer * g_floodMinTime.integer / 1000; - else - maximumDemerits = g_floodMaxDemerits.integer; + if( ent->client->pers.evolveHealthFraction < 0.0f ) + ent->client->pers.evolveHealthFraction = 0.0f; + else if( ent->client->pers.evolveHealthFraction > 1.0f ) + ent->client->pers.evolveHealthFraction = 1.0f; - if( ent->client->pers.floodDemerits > maximumDemerits ) - return qtrue; + //remove credit + //G_AddCreditToClient( ent->client, -cost, qtrue ); + ent->client->pers.classSelection = newClass; + ClientUserinfoChanged( clientNum, qfalse ); + VectorCopy( infestOrigin, ent->s.pos.trBase ); + VectorCopy( ent->client->ps.velocity, oldVel ); - return qfalse; + if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + oldBoostTime = ent->client->boostedTime; + + ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); + + VectorCopy( oldVel, ent->client->ps.velocity ); + if( oldBoostTime > 0 ) + { + ent->client->boostedTime = oldBoostTime; + ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + } } - + +static void Give_Gun( gentity_t *ent, char *s ) +{ + int w = BG_WeaponByName( s )->number; + + if ( w == WP_NONE ) + return; + + //if( !BG_Weapon( w )->purchasable ) + // return; + + ent->client->ps.stats[ STAT_WEAPON ] = w; + ent->client->ps.ammo = BG_Weapon( w )->maxAmmo; + ent->client->ps.clips = BG_Weapon( w )->maxClips; + G_ForceWeaponChange( ent, w ); +} + +static void Give_Upgrade( gentity_t *ent, char *s ) +{ + int u = BG_UpgradeByName( s )->number; + BG_AddUpgradeToInventory( u, ent->client->ps.stats ); +} + /* ================== Cmd_Give_f @@ -402,52 +463,157 @@ void Cmd_Give_f( gentity_t *ent ) char *name; qboolean give_all = qfalse; + if( trap_Argc( ) < 2 ) + { + ADMP( "^3give: ^7usage: give [what]\n" + "health, funds , stamina, poison, gas, ammo, " + "^3level0, level1, level1upg, level2, level2upg, level3, level3upg, level4, builder, builderupg, " + "human_base, human_bsuit, " + "^5blaster, rifle, psaw, shotgun, lgun, mdriver, chaingun, flamer, prifle, grenade, lockblob, " + "hive, teslagen, mgturret, abuild, abuildupg, portalgun, proximity, smokecan, " + "^2larmour, helmet, medkit, battpak, jetpack, bsuit, gren \n" ); + return; + } + name = ConcatArgs( 1 ); if( Q_stricmp( name, "all" ) == 0 ) give_all = qtrue; if( give_all || Q_stricmp( name, "health" ) == 0 ) { - if(!g_devmapNoGod.integer) + ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; + BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); + } + + if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) + { + float credits; + + if( give_all || trap_Argc( ) < 3 ) + credits = 30000.0f; + else { - ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; - BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); + credits = atof( name + 6 ) * + ( ent->client->pers.teamSelection == + TEAM_ALIENS ? ALIEN_CREDITS_PER_KILL : 1.0f ); + + // clamp credits manually, as G_AddCreditToClient() expects a short int + if( credits > SHRT_MAX ) + credits = 30000.0f; + else if( credits < SHRT_MIN ) + credits = -30000.0f; } + + G_AddCreditToClient( ent->client, (short)credits, qtrue ); } - if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) + if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || + ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + if( !( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) ) + G_TriggerMenu( ent-g_entities, MN_CMD_ALIVE ); + return; + } + + if( give_all || Q_stricmp( name, "health" ) == 0 ) { - int credits = give_all ? HUMAN_MAX_CREDITS : atoi( name + 6 ); - G_AddCreditToClient( ent->client, credits, qtrue ); + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] ) + { + ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health; + } + BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); } if( give_all || Q_stricmp( name, "stamina" ) == 0 ) - ent->client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; + ent->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + + // Adding guns + Give_Gun(ent, name); + + // Adding upgrades + Give_Upgrade( ent, name); + + // Change class- this allows you to be any alien class on TEAM_HUMAN and the + // otherway round. + Give_Class(ent, name); if( Q_stricmp( name, "poison" ) == 0 ) { - ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; - ent->client->lastBoostedTime = level.time; + if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED; + ent->client->lastPoisonTime = level.time; + ent->client->lastPoisonClient = ent; + } + else + { + ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + ent->client->boostedTime = level.time; + } + } + + if( Q_stricmp( name, "gas" ) == 0 ) + { + ent->client->ps.eFlags |= EF_POISONCLOUDED; + ent->client->lastPoisonCloudedTime = level.time; + trap_SendServerCommand( ent->client->ps.clientNum, "poisoncloud" ); } if( give_all || Q_stricmp( name, "ammo" ) == 0 ) { - int maxAmmo, maxClips; gclient_t *client = ent->client; if( client->ps.weapon != WP_ALEVEL3_UPG && - BG_FindInfinteAmmoForWeapon( client->ps.weapon ) ) + BG_Weapon( client->ps.weapon )->infiniteAmmo ) return; - BG_FindAmmoForWeapon( client->ps.weapon, &maxAmmo, &maxClips ); + client->ps.ammo = BG_Weapon( client->ps.weapon )->maxAmmo; + client->ps.clips = BG_Weapon( client->ps.weapon )->maxClips; - if( BG_FindUsesEnergyForWeapon( client->ps.weapon ) && + if( BG_Weapon( client->ps.weapon )->usesEnergy && BG_InventoryContainsUpgrade( UP_BATTPACK, client->ps.stats ) ) - maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); + client->ps.ammo = (int)( (float)client->ps.ammo * BATTPACK_MODIFIER ); + } +} + +/* +Cmd_Drop_f +Drop a weapon onto the ground +*/ +void Cmd_Drop_f( gentity_t *ent ) +{ + char t[ MAX_TOKEN_CHARS ]; + char angle[ MAX_TOKEN_CHARS ]; + float ang = 0.0f; + int i; + + if( trap_Argc( ) < 2 ) + { + ADMP("^3drop: ^7usage: drop [angle]\n"); + return; + } - client->ps.ammo = maxAmmo; - client->ps.clips = maxClips; + trap_Argv( 1, t, sizeof(t) ); + + if ( trap_Argc() > 2 ) + { + trap_Argv( 2, angle, sizeof(angle) ); + ang = atof( angle ); } + + switch ((i = BG_WeaponByName( t )->number)) + { + case WP_NONE: + ADMP("^3drop: ^7usage: drop [angle]\n"); + break; + + default: + G_DropWeapon( ent, i, ang ); + break; + }; + + } @@ -464,19 +630,12 @@ void Cmd_God_f( gentity_t *ent ) { char *msg; - if( !g_devmapNoGod.integer ) - { ent->flags ^= FL_GODMODE; if( !( ent->flags & FL_GODMODE ) ) msg = "godmode OFF\n"; else msg = "godmode ON\n"; - } - else - { - msg = "Godmode has been disabled.\n"; - } trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); } @@ -495,19 +654,12 @@ void Cmd_Notarget_f( gentity_t *ent ) { char *msg; - if( !g_devmapNoGod.integer ) - { ent->flags ^= FL_NOTARGET; if( !( ent->flags & FL_NOTARGET ) ) msg = "notarget OFF\n"; else msg = "notarget ON\n"; - } - else - { - msg = "Godmode has been disabled.\n"; - } trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); } @@ -524,19 +676,22 @@ void Cmd_Noclip_f( gentity_t *ent ) { char *msg; - if( !g_devmapNoGod.integer ) - { if( ent->client->noclip ) + { msg = "noclip OFF\n"; + ent->r.contents = ent->client->cliprcontents; + } else + { msg = "noclip ON\n"; + ent->client->cliprcontents = ent->r.contents; + ent->r.contents = 0; + } ent->client->noclip = !ent->client->noclip; - } - else - { - msg = "Godmode has been disabled.\n"; - } + + if( ent->r.linked ) + trap_LinkEntity( ent ); trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); } @@ -565,15 +720,6 @@ Cmd_Kill_f */ void Cmd_Kill_f( gentity_t *ent ) { - if( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) - return; - - if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) - { - trap_SendServerCommand( ent-g_entities, "print \"Leave the hovel first (use your destroy key)\n\"" ); - return; - } - if( g_cheats.integer ) { ent->flags &= ~FL_GODMODE; @@ -589,162 +735,12 @@ void Cmd_Kill_f( gentity_t *ent ) } else if( ent->suicideTime > level.time ) { - trap_SendServerCommand( ent-g_entities, "print \"Suicide canceled\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Suicide cancelled\n\"" ); ent->suicideTime = 0; } } } -/* -================== -G_LeaveTeam -================== -*/ -void G_LeaveTeam( gentity_t *self ) -{ - pTeam_t team = self->client->pers.teamSelection; - gentity_t *ent; - int i; - - if( team == PTE_ALIENS ) - G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum ); - else if( team == PTE_HUMANS ) - G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum ); - else - { - if( self->client->sess.spectatorState == SPECTATOR_FOLLOW ) - { - G_StopFollowing( self ); - } - return; - } - - // Cancel pending suicides - self->suicideTime = 0; - - // stop any following clients - G_StopFromFollowing( self ); - - for( i = 0; i < level.num_entities; i++ ) - { - ent = &g_entities[ i ]; - if( !ent->inuse ) - continue; - - // clean up projectiles - if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number ) - G_FreeEntity( ent ); - if( ent->client && ent->client->pers.connected == CON_CONNECTED ) - { - // cure poison - if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && - ent->client->lastPoisonCloudedClient == self ) - ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; - if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED && - ent->client->lastPoisonClient == self ) - ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; - } - } -} - -/* -================= -G_ChangeTeam -================= -*/ -void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ) -{ - pTeam_t oldTeam = ent->client->pers.teamSelection; - qboolean isFixingImbalance=qfalse; - - if( oldTeam == newTeam ) - return; - - G_LeaveTeam( ent ); - ent->client->pers.teamSelection = newTeam; - - ent->client->pers.lastFreekillTime = level.time; - - // G_LeaveTeam() calls G_StopFollowing() which sets spec mode to free. - // Undo that in this case, or else people can freespec while in the spawn queue on their new team - if( newTeam != PTE_NONE ) - { - ent->client->sess.spectatorState = SPECTATOR_LOCKED; - } - - - if ( ( level.numAlienClients - level.numHumanClients > 2 && oldTeam==PTE_ALIENS && newTeam == PTE_HUMANS && level.numHumanSpawns>0 ) || - ( level.numHumanClients - level.numAlienClients > 2 && oldTeam==PTE_HUMANS && newTeam == PTE_ALIENS && level.numAlienSpawns>0 ) ) - { - isFixingImbalance=qtrue; - } - - // under certain circumstances, clients can keep their kills and credits - // when switching teams - if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) || - ( g_teamImbalanceWarnings.integer && isFixingImbalance ) || - ( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS ) - && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) ) - { - if( oldTeam == PTE_ALIENS ) - ent->client->pers.credit *= (float)FREEKILL_HUMAN / FREEKILL_ALIEN; - else if( newTeam == PTE_ALIENS ) - ent->client->pers.credit *= (float)FREEKILL_ALIEN / FREEKILL_HUMAN; - } - else - { - ent->client->pers.credit = 0; - ent->client->pers.score = 0; - } - - ent->client->ps.persistant[ PERS_KILLED ] = 0; - ent->client->pers.statscounters.kills = 0; - ent->client->pers.statscounters.structskilled = 0; - ent->client->pers.statscounters.assists = 0; - ent->client->pers.statscounters.repairspoisons = 0; - ent->client->pers.statscounters.headshots = 0; - ent->client->pers.statscounters.hits = 0; - ent->client->pers.statscounters.hitslocational = 0; - ent->client->pers.statscounters.deaths = 0; - ent->client->pers.statscounters.feeds = 0; - ent->client->pers.statscounters.suicides = 0; - ent->client->pers.statscounters.teamkills = 0; - ent->client->pers.statscounters.dmgdone = 0; - ent->client->pers.statscounters.structdmgdone = 0; - ent->client->pers.statscounters.ffdmgdone = 0; - ent->client->pers.statscounters.structsbuilt = 0; - ent->client->pers.statscounters.timealive = 0; - ent->client->pers.statscounters.timeinbase = 0; - ent->client->pers.statscounters.dretchbasytime = 0; - ent->client->pers.statscounters.jetpackusewallwalkusetime = 0; - - if( G_admin_permission( ent, ADMF_DBUILDER ) ) - { - if( !ent->client->pers.designatedBuilder ) - { - ent->client->pers.designatedBuilder = qtrue; - trap_SendServerCommand( ent-g_entities, - "print \"Your designation has been restored\n\"" ); - } - } - else if( ent->client->pers.designatedBuilder ) - { - ent->client->pers.designatedBuilder = qfalse; - trap_SendServerCommand( ent-g_entities, - "print \"You have lost designation due to teamchange\n\"" ); - } - - ent->client->pers.classSelection = PCL_NONE; - ClientSpawn( ent, NULL, NULL, NULL ); - - ent->client->pers.joinedATeam = qtrue; - ent->client->pers.teamChangeTime = level.time; - - //update ClientInfo - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); - G_CheckDBProtection( ); -} - /* ================= Cmd_Team_f @@ -752,567 +748,396 @@ Cmd_Team_f */ void Cmd_Team_f( gentity_t *ent ) { - pTeam_t team; - pTeam_t oldteam = ent->client->pers.teamSelection; - char s[ MAX_TOKEN_CHARS ]; - char buf[ MAX_STRING_CHARS ]; - qboolean force = G_admin_permission(ent, ADMF_FORCETEAMCHANGE); - int aliens = level.numAlienClients; - int humans = level.numHumanClients; + team_t team; + team_t oldteam = ent->client->pers.teamSelection; + char s[ MAX_TOKEN_CHARS ]; + qboolean force = G_admin_permission( ent, ADMF_FORCETEAMCHANGE ); + int aliens = level.numAlienClients; + int humans = level.numHumanClients; + + if( oldteam == TEAM_ALIENS ) + aliens--; + else if( oldteam == TEAM_HUMANS ) + humans--; // stop team join spam - if( level.time - ent->client->pers.teamChangeTime < 1000 ) + if( ent->client->pers.teamChangeTime && + level.time - ent->client->pers.teamChangeTime < 1000 ) return; - // Prevent invisible players from joining a team - if ( ent->client->sess.invisible == qtrue ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"You cannot join a team while invisible\n\"" ) ); - return; - } - if( g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) ) + // stop switching teams for gameplay exploit reasons by enforcing a long + // wait before they can come back + if( !force && !g_cheats.integer && ent->client->pers.secondsAlive && + level.time - ent->client->pers.teamChangeTime < 30000 ) { trap_SendServerCommand( ent-g_entities, - va( "print \"You can't join a team when scrim mode is enabled\n\"" ) ); + va( "print \"You must wait another %d seconds before changing teams again\n\"", + (int) ( ( 30000 - ( level.time - ent->client->pers.teamChangeTime ) ) / 1000.f ) ) ); return; } - if( oldteam == PTE_ALIENS ) - aliens--; - else if( oldteam == PTE_HUMANS ) - humans--; + trap_Argv( 1, s, sizeof( s ) ); - // do warm up - if( g_doWarmup.integer && g_warmupMode.integer == 1 && - level.time - level.startTime < g_warmup.integer * 1000 ) + if( !s[ 0 ] ) { - trap_SendServerCommand( ent - g_entities, va( "print \"team: you can't join" - " a team during warm up (%d seconds remaining)\n\"", - g_warmup.integer - ( level.time - level.startTime ) / 1000 ) ); + trap_SendServerCommand( ent-g_entities, va( "print \"team: %s\n\"", + BG_TeamName( oldteam ) ) ); return; } - trap_Argv( 1, s, sizeof( s ) ); - - if( !strlen( s ) ) + if( !Q_stricmp( s, "auto" ) ) { - trap_SendServerCommand( ent-g_entities, va("print \"team: %i\n\"", - oldteam ) ); - return; - } + if( level.humanTeamLocked && level.alienTeamLocked ) + team = TEAM_NONE; + else if( level.humanTeamLocked || humans > aliens ) + team = TEAM_ALIENS; - if( Q_stricmpn( s, "spec", 4 ) ){ - if(G_admin_level(ent)client->pers.teamSelection == PTE_NONE && - g_maxGameClients.integer && level.numPlayingClients >= - g_maxGameClients.integer ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"The maximum number " - "of playing clients has been reached (g_maxGameClients = %i)\n\"", - g_maxGameClients.integer ) ); - return; - } - else if ( ent->client->pers.specExpires > level.time ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't join a team yet. Expires in %d seconds.\n\"", - ( ent->client->pers.specExpires - level.time ) / 1000 ) ); - return; + else if( level.alienTeamLocked || aliens > humans ) + team = TEAM_HUMANS; + else + team = TEAM_ALIENS + rand( ) / ( RAND_MAX / 2 + 1 ); } - else if( !Q_stricmpn( s, "alien", 5 ) ) + else switch( G_TeamFromString( s ) ) { - if( g_forceAutoSelect.integer && !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can only join teams using autoselect\n\"" ); - return; - } + case TEAM_NONE: + team = TEAM_NONE; + break; - if( level.alienTeamLocked && !force ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"Alien team has been ^1LOCKED\n\"" ) ); - return; - } - else if( level.humanTeamLocked ) - { - // if only one team has been locked, let people join the other - // regardless of balance - force = qtrue; - } + case TEAM_ALIENS: + if( level.alienTeamLocked ) + { + G_TriggerMenu( ent - g_entities, MN_A_TEAMLOCKED ); + return; + } + else if( level.humanTeamLocked ) + force = qtrue; - if( !force && g_teamForceBalance.integer && aliens > humans ) - { - G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); - return; - } - + if( !force && g_teamForceBalance.integer && aliens > humans ) + { + G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); + return; + } - team = PTE_ALIENS; - } - else if( !Q_stricmpn( s, "human", 5 ) ) - { - if( g_forceAutoSelect.integer && !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can only join teams using autoselect\n\"" ); - return; - } + team = TEAM_ALIENS; + break; - if( level.humanTeamLocked && !force ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"Human team has been ^1LOCKED\n\"" ) ); - return; - } - else if( level.alienTeamLocked ) - { - // if only one team has been locked, let people join the other - // regardless of balance - force = qtrue; - } + case TEAM_HUMANS: + if( level.humanTeamLocked ) + { + G_TriggerMenu( ent - g_entities, MN_H_TEAMLOCKED ); + return; + } + else if( level.alienTeamLocked ) + force = qtrue; - if( !force && g_teamForceBalance.integer && humans > aliens ) - { - G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); - return; - } + if( !force && g_teamForceBalance.integer && humans > aliens ) + { + G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); + return; + } - team = PTE_HUMANS; - } - else if( !Q_stricmp( s, "auto" ) ) - { - if( level.humanTeamLocked && level.alienTeamLocked ) - team = PTE_NONE; - else if( humans > aliens ) - team = PTE_ALIENS; - else if( humans < aliens ) - team = PTE_HUMANS; - else - team = PTE_ALIENS + ( rand( ) % 2 ); + team = TEAM_HUMANS; + break; - if( team == PTE_ALIENS && level.alienTeamLocked ) - team = PTE_HUMANS; - else if( team == PTE_HUMANS && level.humanTeamLocked ) - team = PTE_ALIENS; - } - else - { - trap_SendServerCommand( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) ); - return; + default: + trap_SendServerCommand( ent-g_entities, + va( "print \"Unknown team: %s\n\"", s ) ); + return; } // stop team join spam if( oldteam == team ) return; - //guard against build timer exploit - if( oldteam != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR && - ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || - ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || - BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) || - BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) && - ent->client->ps.stats[ STAT_MISC ] > 0 ) + if( team != TEAM_NONE && g_maxGameClients.integer && + level.numPlayingClients >= g_maxGameClients.integer ) { - trap_SendServerCommand( ent-g_entities, - va( "print \"You cannot change teams until build timer expires\n\"" ) ); + G_TriggerMenu( ent - g_entities, MN_PLAYERLIMIT ); return; } - if (team != PTE_NONE) - { - char namebuff[32]; - - Q_strncpyz (namebuff, ent->client->pers.netname, sizeof(namebuff)); - Q_CleanStr (namebuff); - - if (!namebuff[0] || !Q_stricmp (namebuff, "UnnamedPlayer")) - { - trap_SendServerCommand( ent-g_entities, va( "print \"Please set your player name before joining a team. Press ESC and use the Options / Game menu or use /name in the console\n\"") ); - return; - } - } - - + // Apply the change G_ChangeTeam( ent, team ); - - - - if( team == PTE_ALIENS ) { - if ( oldteam == PTE_HUMANS ) - Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned humans and joined the aliens.", ent->client->pers.netname ); - else - Com_sprintf( buf, sizeof( buf ), "%s^7 joined the aliens.", ent->client->pers.netname ); - } - else if( team == PTE_HUMANS ) { - if ( oldteam == PTE_ALIENS ) - Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned the aliens and joined the humans.", ent->client->pers.netname ); - else - Com_sprintf( buf, sizeof( buf ), "%s^7 joined the humans.", ent->client->pers.netname ); - } - else if( team == PTE_NONE ) { - if ( oldteam == PTE_HUMANS ) - Com_sprintf( buf, sizeof( buf ), "%s^7 left the humans.", ent->client->pers.netname ); - else - Com_sprintf( buf, sizeof( buf ), "%s^7 left the aliens.", ent->client->pers.netname ); - } - trap_SendServerCommand( -1, va( "print \"%s\n\"", buf ) ); - G_LogOnlyPrintf("ClientTeam: %s\n",buf); } - /* ================== -G_Say +G_CensorString ================== */ -static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, const char *prefix ) -{ - qboolean ignore = qfalse; - qboolean specAllChat = qfalse; +static char censors[ 20000 ]; +static int numcensors; - if( !other ) - return; +void G_LoadCensors( void ) +{ + char *text_p, *token; + char text[ 20000 ]; + char *term; + int len; + fileHandle_t f; - if( !other->inuse ) - return; + numcensors = 0; - if( !other->client ) + if( !g_censorship.string[ 0 ] ) return; - if( other->client->pers.connected != CON_CONNECTED ) + len = trap_FS_FOpenFile( g_censorship.string, &f, FS_READ ); + if( len < 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: Censors file %s doesn't exist\n", + g_censorship.string ); return; - - if( ( mode == SAY_TEAM || mode == SAY_ACTION_T ) && !OnSameTeam( ent, other ) ) + } + if( len == 0 || len >= sizeof( text ) - 1 ) { - if( other->client->pers.teamSelection != PTE_NONE ) - return; - - specAllChat = G_admin_permission( other, ADMF_SPEC_ALLCHAT ); - if( !specAllChat ) - return; - - // specs with ADMF_SPEC_ALLCHAT flag can see team chat + trap_FS_FCloseFile( f ); + Com_Printf( S_COLOR_RED "ERROR: Censors file %s is %s\n", + g_censorship.string, len == 0 ? "empty" : "too long" ); + return; } + trap_FS_Read( text, len, f ); + trap_FS_FCloseFile( f ); + text[ len ] = 0; - if( mode == SAY_ADMINS && - (!G_admin_permission( other, ADMF_ADMINCHAT ) || other->client->pers.ignoreAdminWarnings || - ( g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) ) ) ) - return; - - if( mode == SAY_HADMINS && - (!G_admin_permission( other, ADMF_HIGHADMINCHAT ) || other->client->pers.ignoreAdminWarnings ) ) - return; - - if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) ) - ignore = qtrue; - - if ( ignore && g_fullIgnore.integer ) - return; + term = censors; - trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%s%c%c%s\"", - ( mode == SAY_TEAM || mode == SAY_ACTION_T ) ? "tchat" : "chat", - ( ignore ) ? "[skipnotify]" : "", - ( specAllChat ) ? prefix : "", - name, Q_COLOR_ESCAPE, color, message ) ); + text_p = text; + while( 1 ) + { + token = COM_Parse( &text_p ); + if( !*token || sizeof( censors ) - ( term - censors ) < 4 ) + break; + Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); + Q_strlwr( term ); + term += strlen( term ) + 1; + if( sizeof( censors ) - ( term - censors ) == 0 ) + break; + token = COM_ParseExt( &text_p, qfalse ); + Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); + term += strlen( term ) + 1; + numcensors++; + } + G_Printf( "Parsed %d string replacements\n", numcensors ); } -#define EC "\x19" - -void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) +void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) { - int j; - gentity_t *other; - int color; - const char *prefix; - char name[ 64 ]; - // don't let text be too long for malicious reasons - char text[ MAX_SAY_TEXT ]; - char location[ 64 ]; - - // Bail if the text is blank. - if( ! chatText[0] ) - return; + const char *s, *m; + int i; - // Invisible players cannot use chat - if( ent->client->sess.invisible == qtrue ) + if( !numcensors || G_admin_permission( ent, ADMF_NOCENSORFLOOD) ) { - if( !G_admin_cmd_check( ent, qtrue ) ) - trap_SendServerCommand( ent-g_entities, "print \"You cannot chat while invisible\n\"" ); + Q_strncpyz( out, in, len ); return; } - if( ent && ent->client->pers.teamSelection == PTE_NONE && g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) && mode != SAY_TEAM ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't chat when scrim mode is enabled.\n\"" ); - return; - } - - // Spam limit: If they said this message recently, ignore it. - if( g_spamTime.integer ) + len--; + while( *in ) { - if ( ( level.time - ent->client->pers.lastMessageTime ) < ( g_spamTime.integer * 1000 ) && - !Q_stricmp( ent->client->pers.lastMessage, chatText) && - !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) && - ent->client->pers.floodDemerits <= g_floodMaxDemerits.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your message has been ignored to prevent spam\n\"" ); - return; - } - else - { - ent->client->pers.lastMessageTime = level.time; - - Q_strncpyz( ent->client->pers.lastMessage, chatText, - sizeof( ent->client->pers.lastMessage ) ); - } - } - - // Flood limit. If they're talking too fast, determine that and return. - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) + if( Q_IsColorString( in ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); - return; + if( len < 2 ) + break; + *out++ = *in++; + *out++ = *in++; + len -= 2; + continue; } - - if (g_chatTeamPrefix.integer && ent && ent->client ) - { - switch( ent->client->pers.teamSelection) + if( !isalnum( *in ) ) { - default: - case PTE_NONE: - prefix = "[^3S^7] "; + if( len < 1 ) break; - - case PTE_ALIENS: - prefix = "[^1A^7] "; - break; - - case PTE_HUMANS: - prefix = "[^4H^7] "; + *out++ = *in++; + len--; + continue; } - } - else - prefix = ""; - - switch( mode ) - { - default: - case SAY_ALL: - G_LogPrintf( "say: %s^7: %s^7\n", ent->client->pers.netname, chatText ); - Com_sprintf( name, sizeof( name ), "%s%s%c%c"EC": ", prefix, - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_GREEN; - break; - - case SAY_TEAM: - G_LogPrintf( "sayteam: %s%s^7: %s^7\n", prefix, ent->client->pers.netname, chatText ); - if( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) - Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC") (%s)"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); - else - Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC")"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - - if( ent->client->pers.teamSelection == PTE_NONE ) - color = COLOR_YELLOW; - else - color = COLOR_CYAN; - break; - - case SAY_TELL: - if( target && OnSameTeam( target, ent ) && - Team_GetLocationMsg( ent, location, sizeof( location ) ) ) - Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"] (%s)"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); - else - Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"]"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_MAGENTA; - break; - - case SAY_ACTION: - G_LogPrintf( "action: %s^7: %s^7\n", ent->client->pers.netname, chatText ); - Com_sprintf( name, sizeof( name ), "^2%s^7%s%s%c%c"EC" ", g_actionPrefix.string, prefix, - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_WHITE; - break; - - case SAY_ACTION_T: - G_LogPrintf( "actionteam: %s%s^7: %s^7\n", prefix, ent->client->pers.netname, chatText ); - if( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) - Com_sprintf( name, sizeof( name ), EC"^5%s^7%s%c%c"EC"(%s)"EC" ", g_actionPrefix.string, - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); - else - Com_sprintf( name, sizeof( name ), EC"^5%s^7%s%c%c"EC""EC" ", g_actionPrefix.string, - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_WHITE; - break; - - case SAY_ADMINS: - if( G_admin_permission( ent, ADMF_ADMINCHAT ) ) //Differentiate between inter-admin chatter and user-admin alerts - { - G_LogPrintf( "say_admins: [ADMIN]%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); - Com_sprintf( name, sizeof( name ), "%s[ADMIN]%s%c%c"EC": ", prefix, - ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_MAGENTA; - } - else - { - G_LogPrintf( "say_admins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText ); - Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix, - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_MAGENTA; - } - break; - - case SAY_HADMINS: - if( G_admin_permission( ent, ADMF_HIGHADMINCHAT ) ) //Differentiate between inter-high-admin chatter and lower-admin-high-admin-admin alerts and user-admin alerts + m = censors; + for( i = 0; i < numcensors; i++, m++ ) + { + s = in; + while( *s && *m ) + { + if( Q_IsColorString( s ) ) { - G_LogPrintf( "say_hadmins: ^5[^1HIGH ADMIN^5]^7%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); - Com_sprintf( name, sizeof( name ), "%s^5[^1HIGH ADMIN^5]^7%s%c%c"EC": ", prefix, - ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_RED; + s += 2; + continue; } - else if( G_admin_permission( ent, ADMF_ADMINCHAT ) ) + if( !isalnum( *s ) ) { - G_LogPrintf( "say_haadmins: ^1[^6LOWER ADMIN^1]^7%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); - Com_sprintf( name, sizeof( name ), "%s[^6LOWER ADMIN^7]%s%c%c"EC": ", prefix, - ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_RED; + s++; + continue; } - else + if( tolower( *s ) != *m ) + break; + s++; + m++; + } + // match + if( !*m ) + { + in = s; + m++; + while( *m ) { - G_LogPrintf( "say_hadmins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText ); - Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix, - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_RED; + if( len < 1 ) + break; + *out++ = *m++; + len--; } break; + } + else + { + while( *m ) + m++; + m++; + while( *m ) + m++; + } + } + if( len < 1 ) + break; + // no match + if( i == numcensors ) + { + *out++ = *in++; + len--; + } + } + *out = 0; } +/* +================== +G_Say +================== +*/ +static qboolean G_SayTo( gentity_t *ent, gentity_t *other, saymode_t mode, const char *message ) +{ + if( !other ) + return qfalse; + + if( !other->inuse ) + return qfalse; + + if( !other->client ) + return qfalse; + + if( other->client->pers.connected != CON_CONNECTED ) + return qfalse; + + if( Com_ClientListContains( &other->client->sess.ignoreList, (int)( ent - g_entities ) ) ) + return qfalse; - if( mode!=SAY_TEAM && ent && ent->client && ent->client->pers.teamSelection == PTE_NONE && G_admin_level(ent)client->pers.teamSelection != TEAM_NONE ) + return qfalse; + + // specs with ADMF_SPEC_ALLCHAT flag can see team chat + if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) && mode != SAY_TPRIVMSG ) + return qfalse; } - Com_sprintf( text, sizeof( text ), "%s^7", chatText ); + trap_SendServerCommand( other-g_entities, va( "chat %d %d \"%s\"", + (int)( ent ? ent-g_entities : -1 ), + mode, + message ) ); - if( ent && ent->client && g_aimbotAdvertBan.integer && ( Q_stricmp( text, "^1N^7ullify for ^1T^7remulous [beta] | Get it at CheatersUtopia.com^7" ) == 0 ) ) + return qtrue; +} + +void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ) +{ + int j; + gentity_t *other; + // don't let text be too long for malicious reasons + char text[ MAX_SAY_TEXT ]; + + // check if blocked by g_specChat 0 + if( ( !g_specChat.integer ) && ( mode != SAY_TEAM ) && + ( ent ) && ( ent->client->pers.teamSelection == TEAM_NONE ) && + ( !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) ) { - trap_SendConsoleCommand( 0, - va( "!ban %s %s %s\n", - ent->client->pers.ip, - ( Q_stricmp( g_aimbotAdvertBanTime.string, "0" ) == 1 ) ? g_aimbotAdvertBanTime.string : "" , - g_aimbotAdvertBanReason.string ) ); - Q_strncpyz( text, "^7has been caught hacking and will be dealt with.", sizeof( text ) ); + trap_SendServerCommand( ent-g_entities, "print \"say: Global chatting for " + "spectators has been disabled. You may only use team chat.\n\"" ); + mode = SAY_TEAM; } - if( target ) + switch( mode ) { - G_SayTo( ent, target, mode, color, name, text, prefix ); - return; + case SAY_ALL: + G_LogPrintf( "Say: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_GREEN "%s\n", + (int)( ( ent ) ? ent - g_entities : -1 ), + ( ent ) ? ent->client->pers.netname : "console", chatText ); + break; + case SAY_TEAM: + // console say_team is handled in g_svscmds, not here + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "SAY_TEAM by non-client entity" ); + G_LogPrintf( "SayTeam: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_CYAN "%s\n", + (int)( ent - g_entities ), ent->client->pers.netname, chatText ); + break; + case SAY_RAW: + if( ent ) + Com_Error( ERR_FATAL, "SAY_RAW by client entity" ); + G_LogPrintf( "Chat: -1 \"console\": %s\n", chatText ); + default: + break; } - + G_CensorString( text, chatText, sizeof( text ), ent ); - // Ugly hax: if adminsayfilter is off, do the SAY first to prevent text from going out of order - if( !g_adminSayFilter.integer ) - { - // send it to all the apropriate clients - for( j = 0; j < level.maxclients; j++ ) - { - other = &g_entities[ j ]; - G_SayTo( ent, other, mode, color, name, text, prefix ); - } - } - - if( g_adminParseSay.integer && ( mode== SAY_ALL || mode == SAY_TEAM ) ) - { - if( G_admin_cmd_check ( ent, qtrue ) && g_adminSayFilter.integer ) - { - return; - } - } - - // if it's on, do it here, where it won't happen if it was an admin command - if( g_adminSayFilter.integer ) + // send it to all the apropriate clients + for( j = 0; j < level.maxclients; j++ ) { - // send it to all the apropriate clients - for( j = 0; j < level.maxclients; j++ ) - { - other = &g_entities[ j ]; - G_SayTo( ent, other, mode, color, name, text, prefix ); - } + other = &g_entities[ j ]; + G_SayTo( ent, other, mode, text ); } - - } +/* +================== +Cmd_SayArea_f +================== +*/ static void Cmd_SayArea_f( gentity_t *ent ) { int entityList[ MAX_GENTITIES ]; int num, i; - int color = COLOR_BLUE; - const char *prefix; - vec3_t range = { HELMET_RANGE, HELMET_RANGE, HELMET_RANGE }; + vec3_t range = { 1000.0f, 1000.0f, 1000.0f }; vec3_t mins, maxs; - char *msg = ConcatArgs( 1 ); - char name[ 64 ]; - - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); - return; - } - - if (g_chatTeamPrefix.integer) + char *msg; + + if( trap_Argc( ) < 2 ) { - switch( ent->client->pers.teamSelection) - { - default: - case PTE_NONE: - prefix = "[^3S^7] "; - break; + ADMP( "usage: say_area [message]\n" ); + return; + } - case PTE_ALIENS: - prefix = "[^1A^7] "; - break; + msg = ConcatArgs( 1 ); - case PTE_HUMANS: - prefix = "[^4H^7] "; - } - } - else - prefix = ""; + for(i = 0; i < 3; i++ ) + range[ i ] = g_sayAreaRange.value; - G_LogPrintf( "sayarea: %s%s^7: %s\n", prefix, ent->client->pers.netname, msg ); - Com_sprintf( name, sizeof( name ), EC"<%s%c%c"EC"> ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + G_LogPrintf( "SayArea: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_BLUE "%s\n", + (int)( ent - g_entities ), ent->client->pers.netname, msg ); - VectorAdd( ent->s.origin, range, maxs ); - VectorSubtract( ent->s.origin, range, mins ); + VectorAdd( ent->r.currentOrigin, range, maxs ); + VectorSubtract( ent->r.currentOrigin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) - G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_TEAM, color, name, msg, prefix ); - + G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_AREA, msg ); + //Send to ADMF_SPEC_ALLCHAT candidates for( i = 0; i < level.maxclients; i++ ) { - if( (&g_entities[ i ])->client->pers.teamSelection == PTE_NONE && + if( g_entities[ i ].client->pers.teamSelection == TEAM_NONE && G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) { - G_SayTo( ent, &g_entities[ i ], SAY_TEAM, color, name, msg, prefix ); + G_SayTo( ent, &g_entities[ i ], SAY_AREA, msg ); } } } @@ -1326,160 +1151,141 @@ Cmd_Say_f static void Cmd_Say_f( gentity_t *ent ) { char *p; - char *args; - int mode = SAY_ALL; - int skipargs = 0; + char cmd[ MAX_TOKEN_CHARS ]; + saymode_t mode = SAY_ALL; - args = G_SayConcatArgs( 0 ); - if( Q_stricmpn( args, "say_team ", 9 ) == 0 ) - mode = SAY_TEAM; - if( Q_stricmpn( args, "say_admins ", 11 ) == 0 || Q_stricmpn( args, "a ", 2 ) == 0) - mode = SAY_ADMINS; - if( Q_stricmpn( args, "say_hadmins ", 12 ) == 0 || Q_stricmpn( args, "ha ", 3 ) == 0) - mode = SAY_HADMINS; - - // support parsing /m out of say text since some people have a hard - // time figuring out what the console is. - if( !Q_stricmpn( args, "say /m ", 7 ) || - !Q_stricmpn( args, "say_team /m ", 12 ) || - !Q_stricmpn( args, "say /mt ", 8 ) || - !Q_stricmpn( args, "say_team /mt ", 13 ) ) - { - G_PrivateMessage( ent ); + if( trap_Argc( ) < 2 ) return; - } - if( !Q_stricmpn( args, "say /a ", 7) || - !Q_stricmpn( args, "say_team /a ", 12) || - !Q_stricmpn( args, "say /say_admins ", 16) || - !Q_stricmpn( args, "say_team /say_admins ", 21) ) - { - mode = SAY_ADMINS; - skipargs=1; - } - - if( !Q_stricmpn( args, "say /ha ", 8) || - !Q_stricmpn( args, "say_team /ha ", 13) || - !Q_stricmpn( args, "say /say_hadmins ", 17) || - !Q_stricmpn( args, "say_team /say_hadmins ", 22) ) - { - mode = SAY_HADMINS; - skipargs=1; - } - - if( mode == SAY_ADMINS) - if(!G_admin_permission( ent, ADMF_ADMINCHAT ) ) - { - if( !g_publicSayadmins.integer ) - { - ADMP( "Sorry, but public use of say_admins has been disabled.\n" ); - return; - } - else - { - ADMP( "Your message has been sent to any available admins and to the server logs.\n" ); - } - } - - if( mode == SAY_HADMINS) - if(!G_admin_permission( ent, ADMF_HIGHADMINCHAT ) ) - { - { - ADMP( "You don't have permissions to see/use this channel.\n" ); - } - } - - if(!Q_stricmpn( args, "say /me ", 8 ) ) + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "say_team" ) == 0 ) + mode = SAY_TEAM; + + p = ConcatArgs( 1 ); + + G_Say( ent, mode, p ); +} + +/* +================== +Cmd_VSay_f +================== +*/ +void Cmd_VSay_f( gentity_t *ent ) +{ + char arg[MAX_TOKEN_CHARS]; + char text[ MAX_TOKEN_CHARS ]; + voiceChannel_t vchan; + voice_t *voice; + voiceCmd_t *cmd; + voiceTrack_t *track; + int cmdNum = 0; + int trackNum = 0; + char voiceName[ MAX_VOICE_NAME_LEN ] = {"default"}; + char voiceCmd[ MAX_VOICE_CMD_LEN ] = {""}; + char vsay[ 12 ] = {""}; + weapon_t weapon; + + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "Cmd_VSay_f() called by non-client entity" ); + + trap_Argv( 0, arg, sizeof( arg ) ); + if( trap_Argc( ) < 2 ) { - if( g_actionPrefix.string[0] ) - { - mode = SAY_ACTION; - skipargs=1; - } else return; + trap_SendServerCommand( ent-g_entities, va( + "print \"usage: %s command [text] \n\"", arg ) ); + return; } - else if(!Q_stricmpn( args, "say_team /me ", 13 ) ) + if( !level.voices ) { - if( g_actionPrefix.string[0] ) - { - mode = SAY_ACTION_T; - skipargs=1; - } else return; + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system is not installed on this server\n\"", arg ) ); + return; } - else if( !Q_stricmpn( args, "me ", 3 ) ) + if( !g_voiceChats.integer ) { - if( g_actionPrefix.string[0] ) - { - mode = SAY_ACTION; - } else return; + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system administratively disabled on this server\n\"", + arg ) ); + return; } - else if( !Q_stricmpn( args, "me_team ", 8 ) ) - { - if( g_actionPrefix.string[0] ) - { - mode = SAY_ACTION_T; - } else return; - } - + if( !Q_stricmp( arg, "vsay" ) ) + vchan = VOICE_CHAN_ALL; + else if( !Q_stricmp( arg, "vsay_team" ) ) + vchan = VOICE_CHAN_TEAM; + else if( !Q_stricmp( arg, "vsay_local" ) ) + vchan = VOICE_CHAN_LOCAL; + else + return; + Q_strncpyz( vsay, arg, sizeof( vsay ) ); - if( g_allowShare.integer ) + if( ent->client->pers.voice[ 0 ] ) + Q_strncpyz( voiceName, ent->client->pers.voice, sizeof( voiceName ) ); + voice = BG_VoiceByName( level.voices, voiceName ); + if( !voice ) { - args = G_SayConcatArgs(0); - if( !Q_stricmpn( args, "say /share", 10 ) || - !Q_stricmpn( args, "say_team /share", 15 ) ) - { - Cmd_Share_f( ent ); - return; - } - if( !Q_stricmpn( args, "say /donate", 11 ) || - !Q_stricmpn( args, "say_team /donate", 16 ) ) - { - Cmd_Donate_f( ent ); - return; - } + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice '%s' not found\n\"", vsay, voiceName ) ); + return; } - - if( trap_Argc( ) < 2 ) + trap_Argv( 1, voiceCmd, sizeof( voiceCmd ) ) ; + cmd = BG_VoiceCmdFind( voice->cmds, voiceCmd, &cmdNum ); + if( !cmd ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: command '%s' not found in voice '%s'\n\"", + vsay, voiceCmd, voiceName ) ); return; + } - p = G_SayConcatArgs( 1 + skipargs ); - - G_Say( ent, NULL, mode, p ); -} - -/* -================== -Cmd_Tell_f -================== -*/ -static void Cmd_Tell_f( gentity_t *ent ) -{ - int targetNum; - gentity_t *target; - char *p; - char arg[MAX_TOKEN_CHARS]; + // filter non-spec humans by their primary weapon as well + weapon = WP_NONE; + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + weapon = ent->client->ps.stats[ STAT_WEAPON ]; + } - if( trap_Argc( ) < 2 ) + track = BG_VoiceTrackFind( cmd->tracks, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, (int)ent->client->voiceEnthusiasm, + &trackNum ); + if( !track ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: no available track for command '%s', team %d, " + "class %d, weapon %d, and enthusiasm %d in voice '%s'\n\"", + vsay, voiceCmd, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, + (int)ent->client->voiceEnthusiasm, voiceName ) ); return; + } - trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = atoi( arg ); + if( !Q_stricmp( ent->client->lastVoiceCmd, cmd->cmd ) ) + ent->client->voiceEnthusiasm++; - if( targetNum < 0 || targetNum >= level.maxclients ) - return; + Q_strncpyz( ent->client->lastVoiceCmd, cmd->cmd, + sizeof( ent->client->lastVoiceCmd ) ); - target = &g_entities[ targetNum ]; - if( !target || !target->inuse || !target->client ) - return; + // optional user supplied text + trap_Argv( 2, arg, sizeof( arg ) ); + G_CensorString( text, arg, sizeof( text ), ent ); - p = ConcatArgs( 2 ); - - G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); - G_Say( ent, target, SAY_TELL, p ); - // don't tell to the player self if it was already directed to this player - // also don't send the chat back to a bot - if( ent != target ) - G_Say( ent, ent, SAY_TELL, p ); + switch( vchan ) + { + case VOICE_CHAN_ALL: + case VOICE_CHAN_LOCAL: + trap_SendServerCommand( -1, va( + "voice %d %d %d %d \"%s\"\n", + (int)( ent-g_entities ), vchan, cmdNum, trackNum, text ) ); + break; + case VOICE_CHAN_TEAM: + G_TeamCommand( ent->client->pers.teamSelection, va( + "voice %d %d %d %d \"%s\"\n", + (int)( ent-g_entities ), vchan, cmdNum, trackNum, text ) ); + break; + default: + break; + } } /* @@ -1489,27 +1295,12 @@ Cmd_Where_f */ void Cmd_Where_f( gentity_t *ent ) { - trap_SendServerCommand( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) ); -} - - -static qboolean map_is_votable( const char *map ) -{ - char maps[ MAX_CVAR_VALUE_STRING ]; - char *token, *token_p; - - if( !g_votableMaps.string[ 0 ] ) - return qtrue; - - Q_strncpyz( maps, g_votableMaps.string, sizeof( maps ) ); - token_p = maps; - while( *( token = COM_Parse( &token_p ) ) ) - { - if( !Q_stricmp( token, map ) ) - return qtrue; - } - - return qfalse; + if( !ent->client ) + return; + trap_SendServerCommand( ent - g_entities, + va( "print \"origin: %f %f %f\n\"", + ent->r.currentOrigin[ 0 ], ent->r.currentOrigin[ 1 ], + ent->r.currentOrigin[ 2 ] ) ); } /* @@ -1519,1099 +1310,449 @@ Cmd_CallVote_f */ void Cmd_CallVote_f( gentity_t *ent ) { - int i; - char arg1[ MAX_STRING_TOKENS ]; - char arg2[ MAX_STRING_TOKENS ]; - int clientNum = -1; - char name[ MAX_NETNAME ]; - char *arg1plus; - char *arg2plus; - char message[ MAX_STRING_CHARS ]; - char targetname[ MAX_NAME_LENGTH] = ""; - char reason[ MAX_STRING_CHARS ] = ""; - char *ptr = NULL; - - arg1plus = G_SayConcatArgs( 1 ); - arg2plus = G_SayConcatArgs( 2 ); - - // Invisible players cannot call votes - if( ent->client->sess.invisible == qtrue ) - { - trap_SendServerCommand( ent-g_entities, "print \"You cannot call votes while invisible\n\"" ); - return; - } - - if( !g_allowVote.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); - return; - } + char cmd[ MAX_TOKEN_CHARS ], + vote[ MAX_TOKEN_CHARS ], + arg[ MAX_TOKEN_CHARS ], + extra[ MAX_TOKEN_CHARS ]; + char name[ MAX_NAME_LENGTH ] = ""; + char caller[ MAX_NAME_LENGTH ] = ""; + char reason[ MAX_TOKEN_CHARS ]; + char *creason; + int clientNum = -1; + int id = -1; + team_t team; - // Flood limit. If they're talking too fast, determine that and return. - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your /callvote attempt is flood-limited; wait before chatting again\n\"" ); - return; - } - - //see if they can vote - if( G_admin_permission( ent, ADMF_NO_VOTE ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); - return; - } + trap_Argv( 0, cmd, sizeof( cmd ) ); + trap_Argv( 1, vote, sizeof( vote ) ); + trap_Argv( 2, arg, sizeof( arg ) ); + trap_Argv( 3, extra, sizeof( extra ) ); + creason = ConcatArgs( 3 ); + G_DecolorString( creason, reason, sizeof( reason ) ); + + if( !Q_stricmp( cmd, "callteamvote" ) ) + team = ent->client->pers.teamSelection; + else + team = TEAM_NONE; - if( g_voteMinTime.integer - && ent->client->pers.firstConnect - && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000 - && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) - && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) + if( !g_allowVote.integer ) { - trap_SendServerCommand( ent-g_entities, va( - "print \"You must wait %d seconds after connecting before calling a vote\n\"", - g_voteMinTime.integer ) ); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: voting not allowed here\n\"", cmd ) ); return; } - if( level.voteTime ) + if( level.voteTime[ team ] ) { - trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress\n\"" ); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: a vote is already in progress\n\"", cmd ) ); return; } - if( g_voteLimit.integer > 0 - && ent->client->pers.voteCount >= g_voteLimit.integer - && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"You have already called the maximum number of votes (%d)\n\"", - g_voteLimit.integer ) ); - return; - } - - if( G_IsMuted( ent->client ) ) + // protect against the dreaded exploit of '\n'-interpretation inside quotes + if( strchr( arg, '\n' ) || strchr( arg, '\r' ) || + strchr( extra, '\n' ) || strchr( extra, '\r' ) || + strchr( creason, '\n' ) || strchr( creason, '\r' ) ) { - trap_SendServerCommand( ent - g_entities, - "print \"You are muted and cannot call votes\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); return; } - if( !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) && g_scrimMode.integer != 0 && - ent->client->pers.teamSelection == PTE_NONE ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You can't call votes when scrim mode is enabled\n\"" ); - return; - } + if( level.voteExecuteTime[ team ] ) + G_ExecuteVote( team ); - // make sure it is a valid command to vote on - trap_Argv( 1, arg1, sizeof( arg1 ) ); - trap_Argv( 2, arg2, sizeof( arg2 ) ); + level.voteDelay[ team ] = 0; + level.voteThreshold[ team ] = 50; - if( strchr( arg1plus, ';' ) ) + if( g_voteLimit.integer > 0 && + ent->client->pers.namelog->voteCount >= g_voteLimit.integer && + !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: you have already called the maximum number of votes (%d)\n\"", + cmd, g_voteLimit.integer ) ); return; } - // if there is still a vote to be executed - if( level.voteExecuteTime ) + // kick, mute, unmute, denybuild, allowbuild + if( !Q_stricmp( vote, "kick" ) || + !Q_stricmp( vote, "mute" ) || !Q_stricmp( vote, "unmute" ) || + !Q_stricmp( vote, "denybuild" ) || !Q_stricmp( vote, "allowbuild" ) ) { - if( !Q_stricmp( level.voteString, "map_restart" ) ) - { - G_admin_maplog_result( "r" ); - } - else if( !Q_stricmpn( level.voteString, "map", 3 ) ) - { - G_admin_maplog_result( "m" ); - } - - level.voteExecuteTime = 0; - trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); - } - - level.votePassThreshold=50; - - ptr = strstr(arg1plus, " -"); - if( ptr ) - { - *ptr = '\0'; - ptr+=2; - - if( *ptr == 'r' || *ptr=='R' ) - { - ptr++; - while( *ptr == ' ' ) - ptr++; - strcpy(reason, ptr); - } - else - { - trap_SendServerCommand( ent-g_entities, "print \"callvote: Warning: invalid argument specified \n\"" ); - } - } + char err[ MAX_STRING_CHARS ]; - // detect clientNum for partial name match votes - if( !Q_stricmp( arg1, "kick" ) || - !Q_stricmp( arg1, "spec" ) || - !Q_stricmp( arg1, "mute" ) || - !Q_stricmp( arg1, "unmute" ) ) - { - int clientNums[ MAX_CLIENTS ] = { -1 }; - int numMatches=0; - char err[ MAX_STRING_CHARS ] = ""; - - Q_strncpyz(targetname, arg2plus, sizeof(targetname)); - ptr = strstr(targetname, " -"); - if( ptr ) - *ptr = '\0'; - - if( g_requireVoteReasons.integer && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) && !Q_stricmp( arg1, "kick" ) && reason[ 0 ]=='\0' ) - { - trap_SendServerCommand( ent-g_entities, "print \"callvote: You must specify a reason. Use /callvote kick [player] -r [reason] \n\"" ); - return; - } - - if( !targetname[ 0 ] ) + if( !arg[ 0 ] ) { trap_SendServerCommand( ent-g_entities, - "print \"callvote: no target\n\"" ); + va( "print \"%s: no target\n\"", cmd ) ); return; } - numMatches = G_ClientNumbersFromString( targetname, clientNums ); - if( numMatches == 1 ) - { - // there was only one partial name match - clientNum = clientNums[ 0 ]; - } - else - { - // look for an exact name match (sets clientNum to -1 if it fails) - clientNum = G_ClientNumberFromString( ent, targetname ); - } - - if( clientNum==-1 && numMatches > 1 ) + // with a little extra work only players from the right team are considered + clientNum = G_ClientNumberFromString( arg, err, sizeof( err ) ); + + if( clientNum == -1 ) { - G_MatchOnePlayer( clientNums, err, sizeof( err ) ); - ADMP( va( "^3callvote: ^7%s\n", err ) ); + ADMP( va( "%s: %s", cmd, err ) ); return; } - - if( clientNum != -1 && - level.clients[ clientNum ].pers.connected != CON_CONNECTED ) - { - clientNum = -1; - } - if( clientNum != -1 ) + G_DecolorString( level.clients[ clientNum ].pers.netname, name, sizeof( name ) ); + id = level.clients[ clientNum ].pers.namelog->id; + + if( !Q_stricmp( vote, "kick" ) || !Q_stricmp( vote, "mute" ) || + !Q_stricmp( vote, "denybuild" ) ) { - Q_strncpyz( name, level.clients[ clientNum ].pers.netname, - sizeof( name ) ); - Q_CleanStr( name ); - if ( G_admin_permission ( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) + if( G_admin_permission( g_entities + clientNum, ADMF_IMMUNITY ) ) { - char reasonprint[ MAX_STRING_CHARS ] = ""; - - if( reason[ 0 ] != '\0' ) - Com_sprintf(reasonprint, sizeof(reasonprint), "With reason: %s", reason); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: admin is immune\n\"", cmd ) ); + + G_AdminMessage( NULL, va( S_COLOR_WHITE "%s" S_COLOR_YELLOW " attempted %s %s" + " on immune admin " S_COLOR_WHITE "%s" S_COLOR_YELLOW + " for: %s", + ent->client->pers.netname, cmd, vote, + g_entities[ clientNum ].client->pers.netname, + reason[ 0 ] ? reason : "no reason" ) ); + return; + } - Com_sprintf( message, sizeof( message ), "%s^7 attempted /callvote %s %s on immune admin %s^7 %s^7", - ent->client->pers.netname, arg1, targetname, g_entities[ clientNum ].client->pers.netname, reasonprint ); + if( team != TEAM_NONE && + ( ent->client->pers.teamSelection != + level.clients[ clientNum ].pers.teamSelection ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is not on your team\n\"", cmd ) ); + return; } - } - else - { - trap_SendServerCommand( ent-g_entities, - "print \"callvote: invalid player\n\"" ); - return; - } - } - - if( !Q_stricmp( arg1, "kick" ) ) - { - if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callvote: admin is immune from vote kick\n\"" ); - G_AdminsPrintf("%s\n",message); - G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); - return; - } - // use ip in case this player disconnects before the vote ends - Com_sprintf( level.voteString, sizeof( level.voteString ), - "!ban %s \"%s\" vote kick", level.clients[ clientNum ].pers.ip, - g_adminTempBan.string ); - if ( reason[0]!='\0' ) - Q_strcat( level.voteString, sizeof( level.voteDisplayString ), va( "(%s^7)", reason ) ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), - "Kick player \'%s\'", name ); - } - else if( !Q_stricmp( arg1, "spec" ) ) - { - if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"callvote: admin is immune from vote spec\n\"" ); - return; + if( !reason[ 0 ] && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: You must provide a reason\n\"", cmd ) ); + return; + } } - Com_sprintf( level.voteString, sizeof( level.voteString ), "!putteam %i s %s", clientNum, g_adminTempSpec.string ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Spec player \'%s\'", name ); - } - else if( !Q_stricmp( arg1, "mute" ) ) - { - if( G_IsMuted( &level.clients[ clientNum ] ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callvote: player is already muted\n\"" ); - return; - } - if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callvote: admin is immune from vote mute\n\"" ); - G_AdminsPrintf("%s\n",message); - G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); - return; - } - Com_sprintf( level.voteString, sizeof( level.voteString ), - "!mute %i %s", clientNum, g_adminTempMute.string ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), - "Mute player \'%s\'", name ); - } - else if( !Q_stricmp( arg1, "unmute" ) ) + if( !Q_stricmp( vote, "kick" ) ) { - if( !G_IsMuted( &level.clients[ clientNum ] ) ) + if( level.clients[ clientNum ].pers.localClient ) { trap_SendServerCommand( ent-g_entities, - "print \"callvote: player is not currently muted\n\"" ); - return; - } - Com_sprintf( level.voteString, sizeof( level.voteString ), - "!unmute %i", clientNum ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), - "Un-Mute player \'%s\'", name ); - } - else if( !Q_stricmp( arg1, "map_restart" ) ) - { - if( g_mapvoteMaxTime.integer - && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 ) - && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) - && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"You cannot call for a restart after %d seconds\n\"", - g_mapvoteMaxTime.integer ) ); - G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); - return; - } - Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", arg1 ); - Com_sprintf( level.voteDisplayString, - sizeof( level.voteDisplayString ), "Restart current map" ); - level.votePassThreshold = g_mapVotesPercent.integer; - } - else if( !Q_stricmp( arg1, "map" ) ) - { - if( g_mapvoteMaxTime.integer - && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 ) - && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) - && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"You cannot call for a mapchange after %d seconds\n\"", - g_mapvoteMaxTime.integer ) ); - G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); - return; - } - - if( !G_MapExists( arg2 ) ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " - "'maps/%s.bsp' could not be found on the server\n\"", arg2 ) ); + va( "print \"%s: admin is immune\n\"", cmd ) ); return; } - if( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) && !map_is_votable( arg2 ) ) + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "ban %s \"1s%s\" vote kick (%s)", level.clients[ clientNum ].pers.ip.str, + g_adminTempBan.string, reason ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), "Kick player '%s'", name ); + if( reason[ 0 ] ) { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " - "Only admins may call a vote for map: %s\n\"", arg2 ) ); - return; + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); } - - Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); - Com_sprintf( level.voteDisplayString, - sizeof( level.voteDisplayString ), "Change to map '%s'", arg2 ); - level.votePassThreshold = g_mapVotesPercent.integer; } - else if( !Q_stricmp( arg1, "nextmap" ) ) + else if( team == TEAM_NONE ) { - if( G_MapExists( g_nextMap.string ) ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " - "the next map is already set to '%s^7'\n\"", g_nextMap.string ) ); - return; - } - - if( !arg2[ 0 ] ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callvote: you must specify a map\n\"" ); - return; - } - - if( !G_MapExists( arg2 ) ) + if( !Q_stricmp( vote, "mute" ) ) { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " - "'maps/%s^7.bsp' could not be found on the server\n\"", arg2 ) ); - return; - } - - if( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) && !map_is_votable( arg2 ) ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " - "Only admins may call a vote for map: %s\n\"", arg2 ) ); - return; - } - - Com_sprintf( level.voteString, sizeof( level.voteString ), - "set g_nextMap %s", arg2 ); - Com_sprintf( level.voteDisplayString, - sizeof( level.voteDisplayString ), "Set the next map to '%s^7'", arg2 ); - level.votePassThreshold = g_mapVotesPercent.integer; - } - else if( !Q_stricmp( arg1, "draw" ) ) - { - Com_sprintf( level.voteString, sizeof( level.voteString ), "evacuation" ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), - "End match in a draw" ); - level.votePassThreshold = g_mapVotesPercent.integer; - } - else if( !Q_stricmp( arg1, "poll" ) ) - { - if( arg2plus[ 0 ] == '\0' ) + if( level.clients[ clientNum ].pers.namelog->muted ) { - trap_SendServerCommand( ent-g_entities, "print \"callvote: You forgot to specify what people should vote on.\n\"" ); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is already muted\n\"", cmd ) ); return; } - level.voteString[ 0 ] = '\0'; - Com_sprintf( level.voteDisplayString, - sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus ); - } - else if( !Q_stricmp( arg1, "sudden_death" ) || - !Q_stricmp( arg1, "suddendeath" ) ) - { - if(!g_suddenDeathVotePercent.integer) - { - trap_SendServerCommand( ent-g_entities, "print \"Sudden Death votes have been disabled\n\"" ); - return; - } - else if( g_suddenDeath.integer ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: Sudden Death has already begun\n\"") ); - return; - } - else if( G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"callvote: Sudden Death is already immenent\n\"") ); - return; - } - else - { - level.votePassThreshold = g_suddenDeathVotePercent.integer; - Com_sprintf( level.voteString, sizeof( level.voteString ), "suddendeath" ); - Com_sprintf( level.voteDisplayString, - sizeof( level.voteDisplayString ), "Begin sudden death" ); - - if( g_suddenDeathVoteDelay.integer ) - Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " in %d seconds", g_suddenDeathVoteDelay.integer ) ); - - } - } - else if( !Q_stricmp( arg1, "extend" ) ) - { - if( !g_extendVotesPercent.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Extend votes have been disabled\n\"" ); - return; - } - if( g_extendVotesCount.integer - && level.extend_vote_count >= g_extendVotesCount.integer ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"callvote: Maximum number of %d extend votes has been reached\n\"", - g_extendVotesCount.integer ) ); - return; - } - if( !g_timelimit.integer ) { - trap_SendServerCommand( ent-g_entities, - "print \"This match has no timelimit so extend votes wont work\n\"" ); - return; - } - if( level.time - level.startTime < - ( g_timelimit.integer - g_extendVotesTime.integer / 2 ) * 60000 ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"callvote: Extend votes only allowed with less than %d minutes remaining\n\"", - g_extendVotesTime.integer / 2 ) ); - return; - } - level.extend_vote_count++; - level.votePassThreshold = g_extendVotesPercent.integer; - Com_sprintf( level.voteString, sizeof( level.voteString ), - "timelimit %i", g_timelimit.integer + g_extendVotesTime.integer ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), - "Extend the timelimit by %d minutes", g_extendVotesTime.integer ); - } - else - { - qboolean match = qfalse; - char customVoteKeys[ MAX_STRING_CHARS ]; - - customVoteKeys[ 0 ] = '\0'; - if( g_customVotePercent.integer ) - { - char text[ MAX_STRING_CHARS ]; - char *votekey, *votemsg, *votecmd, *voteperc; - int votePValue; - text[ 0 ] = '\0'; - for( i = 0; i < CUSTOM_VOTE_COUNT; i++ ) + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "mute %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Mute player '%s'", name ); + if( reason[ 0 ] ) { - switch( i ) - { - case 0: - Q_strncpyz( text, g_customVote1.string, sizeof( text ) ); - break; - case 1: - Q_strncpyz( text, g_customVote2.string, sizeof( text ) ); - break; - case 2: - Q_strncpyz( text, g_customVote3.string, sizeof( text ) ); - break; - case 3: - Q_strncpyz( text, g_customVote4.string, sizeof( text ) ); - break; - case 4: - Q_strncpyz( text, g_customVote5.string, sizeof( text ) ); - break; - case 5: - Q_strncpyz( text, g_customVote6.string, sizeof( text ) ); - break; - case 6: - Q_strncpyz( text, g_customVote7.string, sizeof( text ) ); - break; - case 7: - Q_strncpyz( text, g_customVote8.string, sizeof( text ) ); - break; - } - if ( text[ 0 ] == '\0' ) - continue; - - // custom vote cvar format: "callvote_name,Vote message string,vote success command[,percent]" - votekey = text; - votemsg = strchr( votekey, ',' ); - if( !votemsg || *votemsg != ',' ) - continue; - *votemsg = '\0'; - votemsg++; - Q_strcat( customVoteKeys, sizeof( customVoteKeys ), - va( "%s%s", ( customVoteKeys[ 0 ] == '\0' ) ? "" : ", ", votekey ) ); - votecmd = strchr( votemsg, ',' ); - if( !votecmd || *votecmd != ',' ) - continue; - *votecmd = '\0'; - votecmd++; - - voteperc = strchr( votecmd, ',' ); - if( !voteperc || *voteperc != ',' ) - votePValue = g_customVotePercent.integer; - else - { - *voteperc = '\0'; - voteperc++; - votePValue = atoi( voteperc ); - if( !votePValue ) - votePValue = g_customVotePercent.integer; - } - - if( Q_stricmp( arg1, votekey ) != 0 ) - continue; - if( votemsg[ 0 ] == '\0' || votecmd[ 0 ] == '\0' ) - continue; - - level.votePassThreshold = votePValue; - Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", votecmd ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", votemsg ); - match = qtrue; - break; + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); } - } - - if( !match ) + } + else if( !Q_stricmp( vote, "unmute" ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); - trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " - "map, map_restart, draw, extend, nextmap, kick, spec, mute, unmute, poll, and sudden_death\n" ); - if( customVoteKeys[ 0 ] != '\0' ) + if( !level.clients[ clientNum ].pers.namelog->muted ) + { trap_SendServerCommand( ent-g_entities, - va( "print \"Additional custom vote commands: %s\n\"", customVoteKeys ) ); - return; - } - } - - if( level.votePassThreshold!=50 ) - { - Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( "^7 (Needs > %d percent)", level.votePassThreshold ) ); - } - - if ( reason[0]!='\0' ) - Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " Reason: '%s^7'", reason ) ); - - G_admin_adminlog_log( ent, "vote", NULL, 0, qtrue ); - - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE - " called a vote: %s" S_COLOR_WHITE "\n\"", ent->client->pers.netname, level.voteDisplayString ) ); - - G_LogPrintf("Vote: %s^7 called a vote: %s^7\n", ent->client->pers.netname, level.voteDisplayString ); - - Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " Called by: '%s^7'", ent->client->pers.netname ) ); - - ent->client->pers.voteCount++; - - // start the voting, the caller autoamtically votes yes - level.voteTime = level.time; - level.voteNo = 0; - - for( i = 0 ; i < level.maxclients ; i++ ) - level.clients[i].ps.eFlags &= ~EF_VOTED; - - if( !Q_stricmp( arg1, "poll" ) ) - { - level.voteYes = 0; - } - else - { - level.voteYes = 1; - ent->client->ps.eFlags |= EF_VOTED; - } - - trap_SetConfigstring( CS_VOTE_TIME, va( "%i", level.voteTime ) ); - trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString ); - trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); - trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); -} - - -/* -================== -Cmd_Vote_f -================== -*/ -void Cmd_Vote_f( gentity_t *ent ) -{ - char msg[ 64 ]; + va( "print \"%s: player is not currently muted\n\"", cmd ) ); + return; + } - if ( level.intermissiontime || level.paused ) - { - if( level.mapRotationVoteTime ) - { - trap_Argv( 1, msg, sizeof( msg ) ); - if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) - G_IntermissionMapVoteCommand( ent, qfalse, qtrue ); + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "unmute %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Unmute player '%s'", name ); } - - return; - } - - if( !level.voteTime ) - { - if( ent->client->pers.teamSelection != PTE_NONE ) + else if( !Q_stricmp( vote, "map_restart" ) ) { - // If there is a teamvote going on but no global vote, forward this vote on as a teamvote - // (ugly hack for 1.1 cgames + noobs who can't figure out how to use any command that isn't bound by default) - int cs_offset = 0; - if( ent->client->pers.teamSelection == PTE_ALIENS ) - cs_offset = 1; - - if( level.teamVoteTime[ cs_offset ] ) + if( arg[ 0 ] ) { - if( !(ent->client->ps.eFlags & EF_TEAMVOTED ) ) + char map[ MAX_QPATH ]; + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + if( !G_LayoutExists( map, arg ) ) { - Cmd_TeamVote_f(ent); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: layout '%s' does not exist for map '%s'\n\"", + cmd, arg, map ) ); return; } } - } - trap_SendServerCommand( ent-g_entities, "print \"No vote in progress\n\"" ); - return; - } - - if( ent->client->ps.eFlags & EF_VOTED ) - { - trap_SendServerCommand( ent-g_entities, "print \"Vote already cast\n\"" ); - return; - } - - trap_SendServerCommand( ent-g_entities, "print \"Vote cast\n\"" ); - - ent->client->ps.eFlags |= EF_VOTED; - - trap_Argv( 1, msg, sizeof( msg ) ); - - if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) - { - level.voteYes++; - trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); - } - else - { - level.voteNo++; - trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); - } - // a majority will be determined in G_CheckVote, which will also account - // for players entering or leaving -} - -/* -================== -Cmd_CallTeamVote_f -================== -*/ -void Cmd_CallTeamVote_f( gentity_t *ent ) -{ - int i, team, cs_offset = 0; - char arg1[ MAX_STRING_TOKENS ]; - char arg2[ MAX_STRING_TOKENS ]; - int clientNum = -1; - char name[ MAX_NETNAME ]; - char message[ MAX_STRING_CHARS ]; - char targetname[ MAX_NAME_LENGTH] = ""; - char reason[ MAX_STRING_CHARS ] = ""; - char *arg1plus; - char *arg2plus; - char *ptr = NULL; - int numVoters = 0; - - arg1plus = G_SayConcatArgs( 1 ); - arg2plus = G_SayConcatArgs( 2 ); - - team = ent->client->pers.teamSelection; - - if( team == PTE_ALIENS ) - cs_offset = 1; - - if(team==PTE_ALIENS) - numVoters = level.numAlienClients; - else if(team==PTE_HUMANS) - numVoters = level.numHumanClients; - - if( !g_allowVote.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); - return; - } - - if( level.teamVoteTime[ cs_offset ] ) - { - trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress\n\"" ); - return; - } - - //see if they can vote - if( G_admin_permission( ent, ADMF_NO_VOTE ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); - return; - } - - if( g_voteLimit.integer > 0 - && ent->client->pers.voteCount >= g_voteLimit.integer - && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"You have already called the maximum number of votes (%d)\n\"", - g_voteLimit.integer ) ); - return; - } - - if( G_IsMuted( ent->client ) ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You are muted and cannot call teamvotes\n\"" ); - return; - } - - if( g_voteMinTime.integer - && ent->client->pers.firstConnect - && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000 - && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) - && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"You must wait %d seconds after connecting before calling a vote\n\"", - g_voteMinTime.integer ) ); - return; - } - - // make sure it is a valid command to vote on - trap_Argv( 1, arg1, sizeof( arg1 ) ); - trap_Argv( 2, arg2, sizeof( arg2 ) ); - - if( strchr( arg1plus, ';' ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Invalid team vote string\n\"" ); - return; - } - - ptr = strstr(arg1plus, " -"); - if( ptr ) - { - *ptr = '\0'; - ptr+=2; - - if( *ptr == 'r' || *ptr=='R' ) - { - ptr++; - while( *ptr == ' ' ) - ptr++; - strcpy(reason, ptr); - } - else - { - trap_SendServerCommand( ent-g_entities, "print \"callteamvote: Warning: invalid argument specified \n\"" ); - } - } - - // detect clientNum for partial name match votes - if( !Q_stricmp( arg1, "kick" ) || - !Q_stricmp( arg1, "denybuild" ) || - !Q_stricmp( arg1, "allowbuild" ) || - !Q_stricmp( arg1, "designate" ) || - !Q_stricmp( arg1, "undesignate" ) ) - { - int clientNums[ MAX_CLIENTS ] = { -1 }; - int numMatches=0; - char err[ MAX_STRING_CHARS ]; - - Q_strncpyz(targetname, arg2plus, sizeof(targetname)); - ptr = strstr(targetname, " -"); - if( ptr ) - *ptr = '\0'; - - if( g_requireVoteReasons.integer && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) && !Q_stricmp( arg1, "kick" ) && reason[ 0 ]=='\0' ) - { - trap_SendServerCommand( ent-g_entities, "print \"callvote: You must specify a reason. Use /callteamvote kick [player] -r [reason] \n\"" ); - return; - } - - - if( !arg2[ 0 ] ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: no target\n\"" ); - return; + Com_sprintf( level.voteDisplayString[ team ], sizeof( level.voteDisplayString[ team ] ), + "Restart the current map%s", arg[ 0 ] ? va( " with layout '%s'", arg ) : "" ); + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "set g_nextMap \"\" ; set g_nextLayout \"%s\" ; %s", arg, vote ); + // map_restart comes with a default delay } - - numMatches = G_ClientNumbersFromString( targetname, clientNums ); - if( numMatches == 1 ) + else if( !Q_stricmp( vote, "map" ) ) { - // there was only one partial name match - clientNum = clientNums[ 0 ]; - } - else - { - // look for an exact name match (sets clientNum to -1 if it fails) - clientNum = G_ClientNumberFromString( ent, targetname ); - } - - if( clientNum==-1 && numMatches > 1 ) - { - G_MatchOnePlayer( clientNums, err, sizeof( err ) ); - ADMP( va( "^3callteamvote: ^7%s\n", err ) ); - return; - } - - // make sure this player is on the same team - if( clientNum != -1 && level.clients[ clientNum ].pers.teamSelection != - team ) - { - clientNum = -1; - } - - if( clientNum != -1 && - level.clients[ clientNum ].pers.connected == CON_DISCONNECTED ) - { - clientNum = -1; - } - - if( clientNum != -1 ) - { - Q_strncpyz( name, level.clients[ clientNum ].pers.netname, - sizeof( name ) ); - Q_CleanStr( name ); - if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) + if( !G_MapExists( arg ) ) { - char reasonprint[ MAX_STRING_CHARS ] = ""; - if( reason[ 0 ] != '\0' ) - Com_sprintf(reasonprint, sizeof(reasonprint), "With reason: %s", reason); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", + cmd, arg ) ); + return; + } - Com_sprintf( message, sizeof( message ), "%s^7 attempted /callteamvote %s %s on immune admin %s^7 %s^7", - ent->client->pers.netname, arg1, arg2, g_entities[ clientNum ].client->pers.netname, reasonprint ); + if( extra[ 0 ] && !G_LayoutExists( arg, extra ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: layout '%s' does not exist for map '%s'\n\"", + cmd, extra, arg ) ); + return; } - } - else - { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: invalid player\n\"" ); - return; - } - } - if( !Q_stricmp( arg1, "kick" ) ) - { - if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: admin is immune from vote kick\n\"" ); - G_AdminsPrintf("%s\n",message); - G_admin_adminlog_log( ent, "teamvote", NULL, 0, qfalse ); - return; + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "set g_nextMap \"\" ; set g_nextLayout \"%s\" ; %s \"%s\"", extra, vote, arg ); + Com_sprintf( level.voteDisplayString[ team ], sizeof( level.voteDisplayString[ team ] ), + "Change to map '%s'%s", arg, extra[ 0 ] ? va( " with layout '%s'", extra ) : "" ); + level.voteDelay[ team ] = 3000; } + else if( !Q_stricmp( vote, "nextmap" ) ) + { + if( G_MapExists( g_nextMap.string ) && + ( !g_nextLayout.string[ 0 ] || G_LayoutExists( g_nextMap.string, g_nextLayout.string ) ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: the next map is already set to '%s'%s\n\"", + cmd, g_nextMap.string, + g_nextLayout.string[ 0 ] ? va( " with layout '%s'", g_nextLayout.string ) : "" ) ); + return; + } + if( !G_MapExists( arg ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", + cmd, arg ) ); + return; + } - // use ip in case this player disconnects before the vote ends - Com_sprintf( level.teamVoteString[ cs_offset ], - sizeof( level.teamVoteString[ cs_offset ] ), - "!ban %s \"%s\" team vote kick", level.clients[ clientNum ].pers.ip, - g_adminTempBan.string ); - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.teamVoteDisplayString[ cs_offset ] ), - "Kick player '%s'", name ); - } - else if( !Q_stricmp( arg1, "denybuild" ) ) - { - if( level.clients[ clientNum ].pers.denyBuild ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: player already lost building rights\n\"" ); - return; - } + if( extra[ 0 ] && !G_LayoutExists( arg, extra ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: layout '%s' does not exist for map '%s'\n\"", + cmd, extra, arg ) ); + return; + } - if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: admin is immune from denybuild\n\"" ); - G_AdminsPrintf("%s\n",message); - G_admin_adminlog_log( ent, "teamvote", NULL, 0, qfalse ); - return; + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "set g_nextMap \"%s\" ; set g_nextLayout \"%s\"", arg, extra ); + Com_sprintf( level.voteDisplayString[ team ], sizeof( level.voteDisplayString[ team ] ), + "Set the next map to '%s'%s", arg, extra[ 0 ] ? va( " with layout '%s'", extra ) : "" ); } - - Com_sprintf( level.teamVoteString[ cs_offset ], - sizeof( level.teamVoteString[ cs_offset ] ), "!denybuild %i", clientNum ); - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.teamVoteDisplayString[ cs_offset ] ), - "Take away building rights from '%s'", name ); - } - else if( !Q_stricmp( arg1, "allowbuild" ) ) - { - if( !level.clients[ clientNum ].pers.denyBuild ) + else if( !Q_stricmp( vote, "draw" ) ) { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: player already has building rights\n\"" ); - return; + strcpy( level.voteString[ team ], "evacuation" ); + strcpy( level.voteDisplayString[ team ], "End match in a draw" ); + level.voteDelay[ team ] = 3000; } - - Com_sprintf( level.teamVoteString[ cs_offset ], - sizeof( level.teamVoteString[ cs_offset ] ), "!allowbuild %i", clientNum ); - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.teamVoteDisplayString[ cs_offset ] ), - "Allow '%s' to build", name ); - } - else if( !Q_stricmp( arg1, "designate" ) ) - { - if( !g_designateVotes.integer ) + else if( !Q_stricmp( vote, "sudden_death" ) ) { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: Designate votes have been disabled.\n\"" ); - return; + if(!g_suddenDeathVotePercent.integer) + { + trap_SendServerCommand( ent-g_entities, + "print \"Sudden Death votes have been disabled\n\"" ); + return; + } + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"callvote: Sudden Death has already begun\n\"") ); + return; + } + if( level.suddenDeathBeginTime > 0 && + G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"callvote: Sudden Death is imminent\n\"") ); + return; + } + level.voteThreshold[ team ] = g_suddenDeathVotePercent.integer; + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "suddendeath %d", g_suddenDeathVoteDelay.integer ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Begin sudden death in %d seconds", + g_suddenDeathVoteDelay.integer ); } - - if( level.clients[ clientNum ].pers.designatedBuilder ) + else { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: player is already a designated builder\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " + "map, nextmap, map_restart, draw, sudden_death, kick, mute and unmute\n" ); return; } - Com_sprintf( level.teamVoteString[ cs_offset ], - sizeof( level.teamVoteString[ cs_offset ] ), "!designate %i", clientNum ); - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.teamVoteDisplayString[ cs_offset ] ), - "Make '%s' a designated builder", name ); } - else if( !Q_stricmp( arg1, "undesignate" ) ) + else if( !Q_stricmp( vote, "denybuild" ) ) { - - if( !g_designateVotes.integer ) + if( level.clients[ clientNum ].pers.namelog->denyBuild ) { trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: Designate votes have been disabled.\n\"" ); + va( "print \"%s: player already lost building rights\n\"", cmd ) ); return; } - if( !level.clients[ clientNum ].pers.designatedBuilder ) + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "denybuild %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Take away building rights from '%s'", name ); + if( reason[ 0 ] ) { - trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: player is not currently a designated builder\n\"" ); - return; + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); } - Com_sprintf( level.teamVoteString[ cs_offset ], - sizeof( level.teamVoteString[ cs_offset ] ), "!undesignate %i", clientNum ); - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.teamVoteDisplayString[ cs_offset ] ), - "Remove designated builder status from '%s'", name ); } - else if( !Q_stricmp( arg1, "admitdefeat" ) ) + else if( !Q_stricmp( vote, "allowbuild" ) ) { - if( numVoters <=1 ) + if( !level.clients[ clientNum ].pers.namelog->denyBuild ) { trap_SendServerCommand( ent-g_entities, - "print \"callteamvote: You cannot admitdefeat by yourself. Use /callvote draw.\n\"" ); + va( "print \"%s: player already has building rights\n\"", cmd ) ); return; } - Com_sprintf( level.teamVoteString[ cs_offset ], - sizeof( level.teamVoteString[ cs_offset ] ), "admitdefeat %i", team ); - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.teamVoteDisplayString[ cs_offset ] ), - "Admit Defeat" ); - } - else if( !Q_stricmp( arg1, "poll" ) ) - { - if( arg2plus[ 0 ] == '\0' ) - { - trap_SendServerCommand( ent-g_entities, "print \"callteamvote: You forgot to specify what people should vote on.\n\"" ); - return; - } - level.teamVoteString[ cs_offset ][ 0 ] = '\0'; - Com_sprintf( level.teamVoteDisplayString[ cs_offset ], - sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus ); - } + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "allowbuild %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Allow '%s' to build", name ); + } + else if( !Q_stricmp( vote, "admitdefeat" ) ) + { + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "admitdefeat %d", team ); + strcpy( level.voteDisplayString[ team ], "Admit Defeat" ); + level.voteDelay[ team ] = 3000; + } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Valid team vote commands are: " - "kick, denybuild, allowbuild, poll, designate, undesignate, and admitdefeat\n\"" ); + "kick, denybuild, allowbuild and admitdefeat\n\"" ); return; } - ent->client->pers.voteCount++; - G_admin_adminlog_log( ent, "teamvote", arg1, 0, qtrue ); - - if ( reason[0]!='\0' ) - Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Reason: '%s'^7", reason ) ); + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": %s\n", + team == TEAM_NONE ? "CallVote" : "CallTeamVote", + (int)( ent - g_entities ), ent->client->pers.netname, level.voteString[ team ] ); - for( i = 0 ; i < level.maxclients ; i++ ) + if( team == TEAM_NONE ) { - if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) - continue; + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE + " called a vote: %s\n\"", ent->client->pers.netname, + level.voteDisplayString[ team ] ) ); + } + else + { + int i; - if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team ) + for( i = 0 ; i < level.maxclients ; i++ ) { - trap_SendServerCommand( i, va("print \"%s " S_COLOR_WHITE - "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); - } - else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) && - ( ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) ) || - level.clients[ i ].pers.teamSelection == PTE_NONE ) ) - { - trap_SendServerCommand( i, va("print \"^6[Admins]^7 %s " S_COLOR_WHITE - "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + { + if( level.clients[ i ].pers.teamSelection == team || + ( level.clients[ i ].pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) + { + trap_SendServerCommand( i, va( "print \"%s" S_COLOR_WHITE + " called a team vote: %s\n\"", ent->client->pers.netname, + level.voteDisplayString[ team ] ) ); + } + else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) + { + trap_SendServerCommand( i, va( "chat -1 %d \"" S_COLOR_YELLOW "%s" + S_COLOR_YELLOW " called a team vote (%ss): %s\"", SAY_ADMINS, + ent->client->pers.netname, BG_TeamName( team ), + level.voteDisplayString[ team ] ) ); + } + } } } - - if(team==PTE_ALIENS) - G_LogPrintf("Teamvote: %s^7 called a teamvote (aliens): %s^7\n", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ); - else if(team==PTE_HUMANS) - G_LogPrintf("Teamvote: %s^7 called a teamvote (humans): %s^7\n", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ); - - Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Called by: '%s^7'", ent->client->pers.netname ) ); - // start the voting, the caller autoamtically votes yes - level.teamVoteTime[ cs_offset ] = level.time; - level.teamVoteNo[ cs_offset ] = 0; + G_DecolorString( ent->client->pers.netname, caller, sizeof( caller ) ); - for( i = 0 ; i < level.maxclients ; i++ ) - { - if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team ) - level.clients[ i ].ps.eFlags &= ~EF_TEAMVOTED; - } - - if( !Q_stricmp( arg1, "poll" ) ) - { - level.teamVoteYes[ cs_offset ] = 0; - } - else - { - level.teamVoteYes[ cs_offset ] = 1; - ent->client->ps.eFlags |= EF_TEAMVOTED; - } + level.voteTime[ team ] = level.time; + trap_SetConfigstring( CS_VOTE_TIME + team, + va( "%d", level.voteTime[ team ] ) ); + trap_SetConfigstring( CS_VOTE_STRING + team, + level.voteDisplayString[ team ] ); + trap_SetConfigstring( CS_VOTE_CALLER + team, + caller ); - trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va( "%i", level.teamVoteTime[ cs_offset ] ) ); - trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteDisplayString[ cs_offset ] ); - trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) ); - trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) ); + ent->client->pers.namelog->voteCount++; + ent->client->pers.vote |= 1 << team; + G_Vote( ent, team, qtrue ); } - /* ================== -Cmd_TeamVote_f +Cmd_Vote_f ================== */ -void Cmd_TeamVote_f( gentity_t *ent ) +void Cmd_Vote_f( gentity_t *ent ) { - int cs_offset = 0; - char msg[ 64 ]; + char cmd[ MAX_TOKEN_CHARS ], vote[ MAX_TOKEN_CHARS ]; + team_t team = ent->client->pers.teamSelection; - if( ent->client->pers.teamSelection == PTE_ALIENS ) - cs_offset = 1; + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "teamvote" ) ) + team = TEAM_NONE; - if( !level.teamVoteTime[ cs_offset ] ) + if( !level.voteTime[ team ] ) { - trap_SendServerCommand( ent-g_entities, "print \"No team vote in progress\n\"" ); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: no vote in progress\n\"", cmd ) ); return; } - if( ent->client->ps.eFlags & EF_TEAMVOTED ) + if( ent->client->pers.voted & ( 1 << team ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Team vote already cast\n\"" ); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: vote already cast\n\"", cmd ) ); return; } - trap_SendServerCommand( ent-g_entities, "print \"Team vote cast\n\"" ); - - ent->client->ps.eFlags |= EF_TEAMVOTED; - - trap_Argv( 1, msg, sizeof( msg ) ); + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: vote cast\n\"", cmd ) ); - if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) - { - level.teamVoteYes[ cs_offset ]++; - trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) ); - } + trap_Argv( 1, vote, sizeof( vote ) ); + if( vote[ 0 ] == 'y' ) + ent->client->pers.vote |= 1 << team; else - { - level.teamVoteNo[ cs_offset ]++; - trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) ); - } - - // a majority will be determined in TeamCheckVote, which will also account - // for players entering or leaving + ent->client->pers.vote &= ~( 1 << team ); + G_Vote( ent, team, qtrue ); } @@ -2626,29 +1767,38 @@ void Cmd_SetViewpos_f( gentity_t *ent ) char buffer[ MAX_TOKEN_CHARS ]; int i; - if( trap_Argc( ) != 5 ) + if( trap_Argc( ) < 4 ) { - trap_SendServerCommand( ent-g_entities, va( "print \"usage: setviewpos x y z yaw\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"usage: setviewpos [ []]\n\"" ); return; } - VectorClear( angles ); - - for( i = 0 ; i < 3 ; i++ ) + for( i = 0; i < 3; i++ ) { trap_Argv( i + 1, buffer, sizeof( buffer ) ); origin[ i ] = atof( buffer ); } + origin[ 2 ] -= ent->client->ps.viewheight; - trap_Argv( 4, buffer, sizeof( buffer ) ); - angles[ YAW ] = atof( buffer ); + VectorCopy( ent->client->ps.viewangles, angles ); + angles[ ROLL ] = 0; + + if( trap_Argc() >= 5 ) { + trap_Argv( 4, buffer, sizeof( buffer ) ); + angles[ YAW ] = atof( buffer ); + if( trap_Argc() >= 6 ) { + trap_Argv( 5, buffer, sizeof( buffer ) ); + angles[ PITCH ] = atof( buffer ); + } + } - TeleportPlayer( ent, origin, angles ); + TeleportPlayer( ent, origin, angles, 0.0f ); } #define AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3) -qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin ) +static qboolean G_RoomForClassChange( gentity_t *ent, class_t class, + vec3_t newOrigin ) { vec3_t fromMins, fromMaxs; vec3_t toMins, toMaxs; @@ -2656,12 +1806,12 @@ qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin trace_t tr; float nudgeHeight; float maxHorizGrowth; - pClass_t oldClass = ent->client->ps.stats[ STAT_PCLASS ]; + class_t oldClass = ent->client->ps.stats[ STAT_CLASS ]; - BG_FindBBoxForClass( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); - BG_FindBBoxForClass( class, toMins, toMaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, toMins, toMaxs, NULL, NULL, NULL ); - VectorCopy( ent->s.origin, newOrigin ); + VectorCopy( ent->client->ps.origin, newOrigin ); // find max x/y diff maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ]; @@ -2684,7 +1834,10 @@ qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin } // find what the new origin would be on a level surface - newOrigin[ 2 ] += fabs( toMins[ 2 ] ) - fabs( fromMins[ 2 ] ); + newOrigin[ 2 ] -= toMins[ 2 ] - fromMins[ 2 ]; + + if( ent->client->noclip ) + return qtrue; //compute a place up in the air to start the real trace VectorCopy( newOrigin, temp ); @@ -2702,10 +1855,7 @@ qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin ent->s.number, MASK_PLAYERSOLID ); //check there is room to evolve - if( !tr.startsolid && tr.fraction == 1.0f ) - return qtrue; - else - return qfalse; + return ( !tr.startsolid && tr.fraction == 1.0f ); } /* @@ -2719,56 +1869,43 @@ void Cmd_Class_f( gentity_t *ent ) int clientNum; int i; vec3_t infestOrigin; - pClass_t currentClass = ent->client->pers.classSelection; - pClass_t newClass; - int numLevels; + class_t currentClass = ent->client->pers.classSelection; + class_t newClass; int entityList[ MAX_GENTITIES ]; vec3_t range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 }; vec3_t mins, maxs; int num; gentity_t *other; - qboolean humanNear = qfalse; - vec3_t oldVel; int oldBoostTime = -1; + vec3_t oldVel; clientNum = ent->client - level.clients; trap_Argv( 1, s, sizeof( s ) ); - newClass = BG_FindClassNumForName( s ); + newClass = BG_ClassByName( s )->number; - if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) { if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); - - if( ent->client->pers.teamSelection == PTE_ALIENS ) + if( ent->client->pers.teamSelection == TEAM_ALIENS ) { if( newClass != PCL_ALIEN_BUILDER0 && newClass != PCL_ALIEN_BUILDER0_UPG && newClass != PCL_ALIEN_LEVEL0 ) { - trap_SendServerCommand( ent-g_entities, - va( "print \"You cannot spawn with class %s\n\"", s ) ); - return; - } - - if( !BG_ClassIsAllowed( newClass ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"Class %s is not allowed\n\"", s ) ); + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTSPAWN, newClass ); return; } - - if( !BG_FindStagesForClass( newClass, g_alienStage.integer ) ) + + if( !BG_ClassIsAllowed( newClass ) ) { - trap_SendServerCommand( ent-g_entities, - va( "print \"Class %s not allowed at stage %d\n\"", - s, g_alienStage.integer ) ); + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTALLOWED, newClass ); return; } - - if( ent->client->pers.denyBuild && ( newClass==PCL_ALIEN_BUILDER0 || newClass==PCL_ALIEN_BUILDER0_UPG ) ) + + if( !BG_ClassAllowedInStage( newClass, g_alienStage.integer ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Your building rights have been revoked\n\"" ); + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTATSTAGE, newClass ); return; } @@ -2776,40 +1913,32 @@ void Cmd_Class_f( gentity_t *ent ) if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) ) { ent->client->pers.classSelection = newClass; - ent->client->ps.stats[ STAT_PCLASS ] = newClass; + ent->client->ps.stats[ STAT_CLASS ] = newClass; } } - else if( ent->client->pers.teamSelection == PTE_HUMANS ) + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) { //set the item to spawn with - if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && + if( !Q_stricmp( s, BG_Weapon( WP_MACHINEGUN )->name ) && BG_WeaponIsAllowed( WP_MACHINEGUN ) ) { ent->client->pers.humanItemSelection = WP_MACHINEGUN; } - else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && + else if( !Q_stricmp( s, BG_Weapon( WP_HBUILD )->name ) && BG_WeaponIsAllowed( WP_HBUILD ) ) { ent->client->pers.humanItemSelection = WP_HBUILD; } - else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && - BG_WeaponIsAllowed( WP_HBUILD2 ) && - BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) ) - { - ent->client->pers.humanItemSelection = WP_HBUILD2; - } else { - trap_SendServerCommand( ent-g_entities, - "print \"Unknown starting item\n\"" ); + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNSPAWNITEM ); return; } // spawn from a telenode - G_LogOnlyPrintf("ClientTeamClass: %i human %s\n", clientNum, s); if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) ) { ent->client->pers.classSelection = PCL_HUMAN; - ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN; + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; } } return; @@ -2818,24 +1947,23 @@ void Cmd_Class_f( gentity_t *ent ) if( ent->health <= 0 ) return; - if( ent->client->pers.teamSelection == PTE_ALIENS && - !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) && - !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) + if( ent->client->pers.teamSelection == TEAM_ALIENS ) { if( newClass == PCL_NONE ) { - trap_SendServerCommand( ent-g_entities, "print \"Unknown class\n\"" ); + G_TriggerMenu( ent->client->ps.clientNum, MN_A_UNKNOWNCLASS ); return; } //if we are not currently spectating, we are attempting evolution if( ent->client->pers.classSelection != PCL_NONE ) { - if( ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || - ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) + int cost; + + //check that we have an overmind + if( !G_Overmind( ) ) { - trap_SendServerCommand( ent-g_entities, - "print \"You cannot evolve while wallwalking\n\"" ); + G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); return; } @@ -2848,64 +1976,42 @@ void Cmd_Class_f( gentity_t *ent ) { other = &g_entities[ entityList[ i ] ]; - if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || - ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) ) - { - humanNear = qtrue; - } - //If its the OM, then ignore all humans. - if(other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_OVERMIND) + if( ( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || + ( other->s.eType == ET_BUILDABLE && other->buildableTeam == TEAM_HUMANS && + other->powered ) ) { - humanNear = qfalse; - break; + G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); + return; } } - if(humanNear == qtrue) { - G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); - return; - } - - if( !level.overmindPresent ) + //check that we are not wallwalking + if( ent->client->ps.eFlags & EF_WALLCLIMB ) { - G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); - return; - } - - // denyweapons - if( newClass >= PCL_ALIEN_LEVEL1 && newClass <= PCL_ALIEN_LEVEL4 && - ent->client->pers.denyAlienClasses & ( 1 << newClass ) ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from using this class\n\"" ) ); + G_TriggerMenu( clientNum, MN_A_EVOLVEWALLWALK ); return; } - //guard against selling the HBUILD weapons exploit - if( ent->client->sess.sessionTeam != TEAM_SPECTATOR && - ( currentClass == PCL_ALIEN_BUILDER0 || + if( ent->client->sess.spectatorState == SPECTATOR_NOT && + ( currentClass == PCL_ALIEN_BUILDER0 || currentClass == PCL_ALIEN_BUILDER0_UPG ) && ent->client->ps.stats[ STAT_MISC ] > 0 ) { - trap_SendServerCommand( ent-g_entities, - va( "print \"You cannot evolve until build timer expires\n\"" ) ); + G_TriggerMenu( ent->client->ps.clientNum, MN_A_EVOLVEBUILDTIMER ); return; } - numLevels = BG_ClassCanEvolveFromTo( currentClass, - newClass, - (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 ); + cost = BG_ClassCanEvolveFromTo( currentClass, newClass, + ent->client->pers.credit, + g_alienStage.integer, 0 ); if( G_RoomForClassChange( ent, newClass, infestOrigin ) ) { - //...check we can evolve to that class - if( numLevels >= 0 && - BG_FindStagesForClass( newClass, g_alienStage.integer ) && - BG_ClassIsAllowed( newClass ) ) + if( cost >= 0 ) { - G_LogOnlyPrintf("ClientTeamClass: %i alien %s\n", clientNum, s); ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] / - (float)BG_FindHealthForClass( currentClass ); + (float)BG_Class( currentClass )->health; if( ent->client->pers.evolveHealthFraction < 0.0f ) ent->client->pers.evolveHealthFraction = 0.0f; @@ -2913,91 +2019,35 @@ void Cmd_Class_f( gentity_t *ent ) ent->client->pers.evolveHealthFraction = 1.0f; //remove credit - G_AddCreditToClient( ent->client, -(short)numLevels, qtrue ); + G_AddCreditToClient( ent->client, -cost, qtrue ); ent->client->pers.classSelection = newClass; ClientUserinfoChanged( clientNum, qfalse ); VectorCopy( infestOrigin, ent->s.pos.trBase ); VectorCopy( ent->client->ps.velocity, oldVel ); + if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) - oldBoostTime = ent->client->lastBoostedTime; + oldBoostTime = ent->client->boostedTime; + ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); + VectorCopy( oldVel, ent->client->ps.velocity ); if( oldBoostTime > 0 ) { - ent->client->lastBoostedTime = oldBoostTime; + ent->client->boostedTime = oldBoostTime; ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; } - return; } else - { - trap_SendServerCommand( ent-g_entities, - "print \"You cannot evolve from your current class\n\"" ); - return; - } + G_TriggerMenuArgs( clientNum, MN_A_CANTEVOLVE, newClass ); } else - { G_TriggerMenu( clientNum, MN_A_NOEROOM ); - return; - } - } - else if( ent->client->pers.teamSelection == PTE_HUMANS ) - { - //humans cannot use this command whilst alive - if( ent->client->pers.classSelection != PCL_NONE ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You must be dead to use the class command\n\"" ) ); - return; - } - - ent->client->pers.classSelection = - ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN; - - //set the item to spawn with - if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && BG_WeaponIsAllowed( WP_MACHINEGUN ) ) - ent->client->pers.humanItemSelection = WP_MACHINEGUN; - else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && BG_WeaponIsAllowed( WP_HBUILD ) ) - ent->client->pers.humanItemSelection = WP_HBUILD; - else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && BG_WeaponIsAllowed( WP_HBUILD2 ) && - BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) ) - ent->client->pers.humanItemSelection = WP_HBUILD2; - else - { - ent->client->pers.classSelection = PCL_NONE; - trap_SendServerCommand( ent-g_entities, va( "print \"Unknown starting item\n\"" ) ); - return; } - - G_LogOnlyPrintf("ClientTeamClass: %i human %s\n", clientNum, s); - - G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ); } - } + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + G_TriggerMenu( clientNum, MN_H_DEADTOCLASS ); } -/* -================= -DBCommand - -Send command to all designated builders of selected team -================= -*/ -void DBCommand( pTeam_t team, const char *text ) -{ - int i; - gentity_t *ent; - - for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++ ) - { - if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) || - ( ent->client->pers.teamSelection != team ) || - !ent->client->pers.designatedBuilder ) - continue; - - trap_SendServerCommand( i, text ); - } -} /* ================= @@ -3006,16 +2056,16 @@ Cmd_Destroy_f */ void Cmd_Destroy_f( gentity_t *ent ) { - vec3_t forward, end; + vec3_t viewOrigin, forward, end; trace_t tr; gentity_t *traceEnt; char cmd[ 12 ]; qboolean deconstruct = qtrue; + qboolean lastSpawn = qfalse; - if( ent->client->pers.denyBuild ) + if( ent->client->pers.namelog->denyBuild ) { - trap_SendServerCommand( ent-g_entities, - "print \"Your building rights have been revoked\n\"" ); + G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); return; } @@ -3023,251 +2073,104 @@ void Cmd_Destroy_f( gentity_t *ent ) if( Q_stricmp( cmd, "destroy" ) == 0 ) deconstruct = qfalse; - if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) + BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( viewOrigin, 100, forward, end ); + + trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + if( tr.fraction < 1.0f && + ( traceEnt->s.eType == ET_BUILDABLE ) && + ( traceEnt->buildableTeam == ent->client->pers.teamSelection ) && + ( ( ent->client->ps.weapon >= WP_ABUILD ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) ) { - if( ( ent->client->hovel->s.eFlags & EF_DBUILDER ) && - !ent->client->pers.designatedBuilder ) + // Always let the builder prevent the explosion + if( traceEnt->health <= 0 ) { - trap_SendServerCommand( ent-g_entities, - "print \"This structure is protected by designated builder\n\"" ); - DBCommand( ent->client->pers.teamSelection, - va( "print \"%s^3 has attempted to decon a protected structure!\n\"", - ent->client->pers.netname ) ); + G_QueueBuildPoints( traceEnt ); + G_RewardAttackers( traceEnt ); + G_FreeEntity( traceEnt ); return; } - G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin, - 10000, 0, MOD_SUICIDE ); - } - if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ) - { - AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); - VectorMA( ent->client->ps.origin, 100, forward, end ); - - trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; - - if( tr.fraction < 1.0f && - ( traceEnt->s.eType == ET_BUILDABLE ) && - ( traceEnt->biteam == ent->client->pers.teamSelection ) && - ( ( ent->client->ps.weapon >= WP_ABUILD ) && - ( ent->client->ps.weapon <= WP_HBUILD ) ) ) + // Cancel deconstruction (unmark) + if( deconstruct && g_markDeconstruct.integer && traceEnt->deconstruct ) { - // Cancel deconstruction - if( g_markDeconstruct.integer == 1 && traceEnt->deconstruct ) - { - traceEnt->deconstruct = qfalse; - return; - } - if( ( traceEnt->s.eFlags & EF_DBUILDER ) && - !ent->client->pers.designatedBuilder ) - { - trap_SendServerCommand( ent-g_entities, - "print \"This structure is protected by designated builder\n\"" ); - DBCommand( ent->client->pers.teamSelection, - va( "print \"%s^3 has attempted to decon a protected structure!\n\"", - ent->client->pers.netname ) ); - return; - } - - // Check the minimum level to deconstruct - if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You do not have deconstructuction rights.\n\"" ); - return; - } + traceEnt->deconstruct = qfalse; + return; + } - // Prevent destruction of the last spawn - if( g_markDeconstruct.integer != 1 && !g_cheats.integer ) - { - if( ent->client->pers.teamSelection == PTE_ALIENS && - traceEnt->s.modelindex == BA_A_SPAWN ) - { - if( level.numAlienSpawns <= 1 ) - return; - } - else if( ent->client->pers.teamSelection == PTE_HUMANS && - traceEnt->s.modelindex == BA_H_SPAWN ) - { - if( level.numHumanSpawns <= 1 ) - return; - } - } + // Prevent destruction of the last spawn + if( ent->client->pers.teamSelection == TEAM_ALIENS && + traceEnt->s.modelindex == BA_A_SPAWN ) + { + if( level.numAlienSpawns <= 1 ) + lastSpawn = qtrue; + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS && + traceEnt->s.modelindex == BA_H_SPAWN ) + { + if( level.numHumanSpawns <= 1 ) + lastSpawn = qtrue; + } - // Don't allow destruction of hovel with granger inside - if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active ) - return; + if( lastSpawn && !g_cheats.integer && + !g_markDeconstruct.integer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); + return; + } - // Don't allow destruction of buildables that cannot be rebuilt - if(g_suddenDeath.integer && traceEnt->health > 0 && - ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE && - !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || - ( g_suddenDeathMode.integer == SDMODE_BP && - BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || - g_suddenDeathMode.integer == SDMODE_NO_BUILD || - g_suddenDeathMode.integer == SDMODE_NO_DECON ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"During Sudden Death you can only decon buildings that " - "can be rebuilt\n\"" ); - return; - } + // Don't allow destruction of buildables that cannot be rebuilt + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); + return; + } + if( !g_markDeconstruct.integer || + ( ent->client->pers.teamSelection == TEAM_HUMANS && + !G_FindPower( traceEnt, qtrue ) ) ) + { if( ent->client->ps.stats[ STAT_MISC ] > 0 ) { G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); return; } - - if( traceEnt->health > 0 || g_deconDead.integer ) - { - if( g_markDeconstruct.integer == 1 ) - { - traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction - traceEnt->deconstructTime = level.time; - } - else - { - if( traceEnt->health > 0 ) - { - buildHistory_t *new; - - new = G_Alloc( sizeof( buildHistory_t ) ); - new->ID = ( ++level.lastBuildID > 1000 ) - ? ( level.lastBuildID = 1 ) : level.lastBuildID; - new->ent = ent; - new->name[ 0 ] = 0; - new->buildable = traceEnt->s.modelindex; - VectorCopy( traceEnt->s.pos.trBase, new->origin ); - VectorCopy( traceEnt->s.angles, new->angles ); - VectorCopy( traceEnt->s.origin2, new->origin2 ); - VectorCopy( traceEnt->s.angles2, new->angles2 ); - new->fate = BF_DECONNED; - new->next = NULL; - new->marked = NULL; - G_LogBuild( new ); - - G_TeamCommand( ent->client->pers.teamSelection, - va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), - ent->client->pers.netname ) ); - - G_LogPrintf( "Decon: %i %i 0: %s^7 deconstructed %s\n", - ent->client->ps.clientNum, - traceEnt->s.modelindex, - ent->client->pers.netname, - BG_FindNameForBuildable( traceEnt->s.modelindex ) ); - } - - if( !deconstruct ) - G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); - else - G_FreeEntity( traceEnt ); - - if( !g_cheats.integer ) - ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; - } - } } - } -} - -void Cmd_Mark_f( gentity_t *ent ) -{ - vec3_t forward, end; - trace_t tr; - gentity_t *traceEnt; - - if( g_markDeconstruct.integer != 2 ) - { - trap_SendServerCommand( ent-g_entities, - "print \"Mark is disabled\n\"" ); - return; - } - - if( ent->client->pers.denyBuild ) - { - trap_SendServerCommand( ent-g_entities, - "print \"Your building rights have been revoked\n\"" ); - return; - } - - // Check the minimum level to deconstruct - if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder && g_minDeconAffectsMark.integer > 0 ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You do not have deconstructuction rights.\n\"" ); - return; - } - // can't mark when in hovel - if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) - return; - - if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ) - { - AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); - VectorMA( ent->client->ps.origin, 100, forward, end ); - - trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; - - if( tr.fraction < 1.0f && - ( traceEnt->s.eType == ET_BUILDABLE ) && - ( traceEnt->biteam == ent->client->pers.teamSelection ) && - ( ( ent->client->ps.weapon >= WP_ABUILD ) && - ( ent->client->ps.weapon <= WP_HBUILD ) ) ) + if( traceEnt->health > 0 ) { - if( ( traceEnt->s.eFlags & EF_DBUILDER ) && - !ent->client->pers.designatedBuilder ) - { - trap_SendServerCommand( ent-g_entities, - "print \"this structure is protected by a designated builder\n\"" ); - return; - } - - // Cancel deconstruction - if( traceEnt->deconstruct ) - { - traceEnt->deconstruct = qfalse; - - trap_SendServerCommand( ent-g_entities, - va( "print \"%s no longer marked for deconstruction\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); - return; - } - - // Don't allow marking of buildables that cannot be rebuilt - if(g_suddenDeath.integer && traceEnt->health > 0 && - ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE && - !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || - ( g_suddenDeathMode.integer == SDMODE_BP && - BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || - g_suddenDeathMode.integer == SDMODE_NO_BUILD || - g_suddenDeathMode.integer == SDMODE_NO_DECON ) ) + if( !deconstruct ) { - trap_SendServerCommand( ent-g_entities, - "print \"During Sudden Death you can only mark buildings that " - "can be rebuilt\n\"" ); - return; + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + traceEnt->health, 0, MOD_SUICIDE ); } - - if( traceEnt->health > 0 ) + else if( g_markDeconstruct.integer && + ( ent->client->pers.teamSelection != TEAM_HUMANS || + G_FindPower( traceEnt , qtrue ) || lastSpawn ) ) { traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction traceEnt->deconstructTime = level.time; - - trap_SendServerCommand( ent-g_entities, - va( "print \"%s marked for deconstruction\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); + } + else + { + if( !g_cheats.integer ) // add a bit to the build timer + { + ent->client->ps.stats[ STAT_MISC ] += + BG_Buildable( traceEnt->s.modelindex )->buildTime / 4; + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + traceEnt->health, 0, MOD_DECONSTRUCT ); + G_RemoveRangeMarkerFrom( traceEnt ); + G_FreeEntity( traceEnt ); } } } } - /* ================= Cmd_ActivateItem_f @@ -3281,13 +2184,28 @@ void Cmd_ActivateItem_f( gentity_t *ent ) int upgrade, weapon; trap_Argv( 1, s, sizeof( s ) ); - upgrade = BG_FindUpgradeNumForName( s ); - weapon = BG_FindWeaponNumForName( s ); + + // "weapon" aliased to whatever weapon you have + if( !Q_stricmp( "weapon", s ) ) + { + if( ent->client->ps.weapon == WP_BLASTER && + BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + G_ForceWeaponChange( ent, WP_NONE ); + return; + } + + upgrade = BG_UpgradeByName( s )->number; + weapon = BG_WeaponByName( s )->number; if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); - else if( weapon != WP_NONE && BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) - G_ForceWeaponChange( ent, weapon ); + else if( weapon != WP_NONE && + BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + if( ent->client->ps.weapon != weapon && + BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + G_ForceWeaponChange( ent, weapon ); + } else trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); } @@ -3302,11 +2220,11 @@ Deactivate an item */ void Cmd_DeActivateItem_f( gentity_t *ent ) { - char s[ MAX_TOKEN_CHARS ]; - int upgrade; + char s[ MAX_TOKEN_CHARS ]; + upgrade_t upgrade; trap_Argv( 1, s, sizeof( s ) ); - upgrade = BG_FindUpgradeNumForName( s ); + upgrade = BG_UpgradeByName( s )->number; if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); @@ -3322,38 +2240,25 @@ Cmd_ToggleItem_f */ void Cmd_ToggleItem_f( gentity_t *ent ) { - char s[ MAX_TOKEN_CHARS ]; - int upgrade, weapon, i; + char s[ MAX_TOKEN_CHARS ]; + weapon_t weapon; + upgrade_t upgrade; trap_Argv( 1, s, sizeof( s ) ); - upgrade = BG_FindUpgradeNumForName( s ); - weapon = BG_FindWeaponNumForName( s ); + upgrade = BG_UpgradeByName( s )->number; + weapon = BG_WeaponByName( s )->number; if( weapon != WP_NONE ) { + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + //special case to allow switching between //the blaster and the primary weapon - if( ent->client->ps.weapon != WP_BLASTER ) weapon = WP_BLASTER; else - { - //find a held weapon which isn't the blaster - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( i == WP_BLASTER ) - continue; - - if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) - { - weapon = i; - break; - } - } - - if( i == WP_NUM_WEAPONS ) - weapon = WP_BLASTER; - } + weapon = WP_NONE; G_ForceWeaponChange( ent, weapon ); } @@ -3375,60 +2280,32 @@ Cmd_Buy_f */ void Cmd_Buy_f( gentity_t *ent ) { - char s[ MAX_TOKEN_CHARS ]; - int i; - int weapon, upgrade, numItems = 0; - int maxAmmo, maxClips; - qboolean buyingEnergyAmmo = qfalse; - qboolean hasEnergyWeapon = qfalse; - - for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) ) - numItems++; - } - - for( i = WP_NONE; i < WP_NUM_WEAPONS; i++ ) - { - if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) - { - if( BG_FindUsesEnergyForWeapon( i ) ) - hasEnergyWeapon = qtrue; - numItems++; - } - } + char s[ MAX_TOKEN_CHARS ]; + weapon_t weapon; + upgrade_t upgrade; + qboolean energyOnly; trap_Argv( 1, s, sizeof( s ) ); - weapon = BG_FindWeaponNumForName( s ); - upgrade = BG_FindUpgradeNumForName( s ); - - //special case to keep norf happy - if( weapon == WP_NONE && upgrade == UP_AMMO ) - { - buyingEnergyAmmo = hasEnergyWeapon; - } - - if( buyingEnergyAmmo ) - { - //no armoury nearby - if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) && - !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) && - !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"You must be near a reactor, repeater or armoury\n\"" ) ); - return; - } - } + weapon = BG_WeaponByName( s )->number; + upgrade = BG_UpgradeByName( s )->number; + + // Only give energy from reactors or repeaters + if( G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + energyOnly = qfalse; + else if( upgrade == UP_AMMO && + BG_Weapon( ent->client->ps.stats[ STAT_WEAPON ] )->usesEnergy && + ( G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) || + G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) ) + energyOnly = qtrue; else { - //no armoury nearby - if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); - return; - } + if( upgrade == UP_AMMO && + BG_Weapon( ent->client->ps.weapon )->usesEnergy ) + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOENERGYAMMOHERE ); + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); + return; } if( weapon != WP_NONE ) @@ -3440,59 +2317,52 @@ void Cmd_Buy_f( gentity_t *ent ) return; } - // denyweapons - if( weapon >= WP_PAIN_SAW && weapon <= WP_GRENADE && - ent->client->pers.denyHumanWeapons & ( 1 << (weapon - WP_BLASTER) ) ) + // Only humans can buy stuff + if( BG_Weapon( weapon )->team != TEAM_HUMANS ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from buying this weapon\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); return; } - //can afford this? - if( BG_FindPriceForWeapon( weapon ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) + //are we /allowed/ to buy this? + if( !BG_Weapon( weapon )->purchasable ) { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); return; } - //have space to carry this? - if( BG_FindSlotsForWeapon( weapon ) & ent->client->ps.stats[ STAT_SLOTS ] ) + //are we /allowed/ to buy this? + if( !BG_WeaponAllowedInStage( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) ) { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); return; } - if( BG_FindTeamForWeapon( weapon ) != WUT_HUMANS ) + //can afford this? + if( BG_Weapon( weapon )->price > (short)ent->client->pers.credit ) { - //shouldn't need a fancy dialog - trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) ); + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); return; } - //are we /allowed/ to buy this? - if( !BG_FindPurchasableForWeapon( weapon ) ) + //have space to carry this? + if( BG_Weapon( weapon )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); return; } - //are we /allowed/ to buy this? - if( !BG_FindStagesForWeapon( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); + // In some instances, weapons can't be changed + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) return; - } - //add to inventory - BG_AddWeaponToInventory( weapon, ent->client->ps.stats ); - BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); + ent->client->ps.stats[ STAT_WEAPON ] = weapon; + ent->client->ps.ammo = BG_Weapon( weapon )->maxAmmo; + ent->client->ps.clips = BG_Weapon( weapon )->maxClips; - if( BG_FindUsesEnergyForWeapon( weapon ) && + if( BG_Weapon( weapon )->usesEnergy && BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) - maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); - - ent->client->ps.ammo = maxAmmo; - ent->client->ps.clips = maxClips; + ent->client->ps.ammo *= BATTPACK_MODIFIER; G_ForceWeaponChange( ent, weapon ); @@ -3500,7 +2370,7 @@ void Cmd_Buy_f( gentity_t *ent ) ent->client->ps.stats[ STAT_MISC ] = 0; //subtract from funds - G_AddCreditToClient( ent->client, -(short)BG_FindPriceForWeapon( weapon ), qfalse ); + G_AddCreditToClient( ent->client, -(short)BG_Weapon( weapon )->price, qfalse ); } else if( upgrade != UP_NONE ) { @@ -3511,60 +2381,60 @@ void Cmd_Buy_f( gentity_t *ent ) return; } - // denyweapons - if( upgrade == UP_GRENADE && - ent->client->pers.denyHumanWeapons & ( 1 << (WP_GRENADE - WP_BLASTER) ) ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from buying this upgrade\n\"" ) ); - return; - } - //can afford this? - if( BG_FindPriceForUpgrade( upgrade ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) + if( BG_Upgrade( upgrade )->price > (short)ent->client->pers.credit ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); return; } //have space to carry this? - if( BG_FindSlotsForUpgrade( upgrade ) & ent->client->ps.stats[ STAT_SLOTS ] ) + if( BG_Upgrade( upgrade )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); return; } - if( BG_FindTeamForUpgrade( upgrade ) != WUT_HUMANS ) + // Only humans can buy stuff + if( BG_Upgrade( upgrade )->team != TEAM_HUMANS ) { - //shouldn't need a fancy dialog - trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); return; } //are we /allowed/ to buy this? - if( !BG_FindPurchasableForUpgrade( upgrade ) ) + if( !BG_Upgrade( upgrade )->purchasable ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); return; } //are we /allowed/ to buy this? - if( !BG_FindStagesForUpgrade( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); - return; - } - - if( upgrade == UP_BATTLESUIT && ent->client->ps.pm_flags & PMF_DUCKED ) + if( !BG_UpgradeAllowedInStage( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) ) { - trap_SendServerCommand( ent-g_entities, - va( "print \"You can't buy this item while crouching\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); return; } if( upgrade == UP_AMMO ) - G_GiveClientMaxAmmo( ent, buyingEnergyAmmo ); + G_GiveClientMaxAmmo( ent, energyOnly ); else { + if( upgrade == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN_BSUIT, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITON ); + return; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN_BSUIT; + ent->client->pers.classSelection = PCL_HUMAN_BSUIT; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + //add to inventory BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats ); } @@ -3573,30 +2443,17 @@ void Cmd_Buy_f( gentity_t *ent ) G_GiveClientMaxAmmo( ent, qtrue ); //subtract from funds - G_AddCreditToClient( ent->client, -(short)BG_FindPriceForUpgrade( upgrade ), qfalse ); + G_AddCreditToClient( ent->client, -(short)BG_Upgrade( upgrade )->price, qfalse ); } else { - trap_SendServerCommand( ent-g_entities, va( "print \"Unknown item\n\"" ) ); - } - - if( trap_Argc( ) >= 2 ) - { - trap_Argv( 2, s, sizeof( s ) ); - - //retrigger the armoury menu - if( !Q_stricmp( s, "retrigger" ) ) - ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES; - } - if( ent->client->pers.paused ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You may not deconstruct while paused\n\"" ); + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); return; } //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + ent->client->pers.infoChangeTime = level.time; } @@ -3609,26 +2466,36 @@ void Cmd_Sell_f( gentity_t *ent ) { char s[ MAX_TOKEN_CHARS ]; int i; - int weapon, upgrade; + weapon_t weapon; + upgrade_t upgrade; trap_Argv( 1, s, sizeof( s ) ); //no armoury nearby if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); return; } - weapon = BG_FindWeaponNumForName( s ); - upgrade = BG_FindUpgradeNumForName( s ); + if( !Q_stricmpn( s, "weapon", 6 ) ) + weapon = ent->client->ps.stats[ STAT_WEAPON ]; + else + weapon = BG_WeaponByName( s )->number; + + upgrade = BG_UpgradeByName( s )->number; if( weapon != WP_NONE ) { + weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); + + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + //are we /allowed/ to sell this? - if( !BG_FindPurchasableForWeapon( weapon ) ) + if( !BG_Weapon( weapon )->purchasable ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't sell this weapon\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"You can't sell this weapon\n\"" ); return; } @@ -3636,67 +2503,59 @@ void Cmd_Sell_f( gentity_t *ent ) if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) { //guard against selling the HBUILD weapons exploit - if( ( weapon == WP_HBUILD || weapon == WP_HBUILD2 ) && - ent->client->ps.stats[ STAT_MISC ] > 0 ) + if( weapon == WP_HBUILD && ent->client->ps.stats[ STAT_MISC ] > 0 ) { - trap_SendServerCommand( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) ); + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); return; } - BG_RemoveWeaponFromInventory( weapon, ent->client->ps.stats ); + ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; + // Cancel ghost buildables + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( weapon ), qfalse ); + G_AddCreditToClient( ent->client, (short)BG_Weapon( weapon )->price, qfalse ); } //if we have this weapon selected, force a new selection - if( weapon == ent->client->ps.weapon ) - G_ForceWeaponChange( ent, WP_NONE ); + if( weapon == selected ) + G_ForceWeaponChange( ent, WP_BLASTER ); } else if( upgrade != UP_NONE ) { //are we /allowed/ to sell this? - if( !BG_FindPurchasableForUpgrade( upgrade ) ) + if( !BG_Upgrade( upgrade )->purchasable ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You can't sell this item\n\"" ) ); + trap_SendServerCommand( ent-g_entities, "print \"You can't sell this item\n\"" ); return; } //remove upgrade if carried if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) { + // shouldn't really need to test for this, but just to be safe + if( upgrade == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); + return; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + + //add to inventory BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats ); if( upgrade == UP_BATTPACK ) G_GiveClientMaxAmmo( ent, qtrue ); //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( upgrade ), qfalse ); - } - } - else if( !Q_stricmp( s, "weapons" ) ) - { - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - //guard against selling the HBUILD weapons exploit - if( ( i == WP_HBUILD || i == WP_HBUILD2 ) && - ent->client->ps.stats[ STAT_MISC ] > 0 ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) ); - continue; - } - - if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && - BG_FindPurchasableForWeapon( i ) ) - { - BG_RemoveWeaponFromInventory( i, ent->client->ps.stats ); - - //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( i ), qfalse ); - } - - //if we have this weapon selected, force a new selection - if( i == ent->client->ps.weapon ) - G_ForceWeaponChange( ent, WP_NONE ); + G_AddCreditToClient( ent->client, (short)BG_Upgrade( upgrade )->price, qfalse ); } } else if( !Q_stricmp( s, "upgrades" ) ) @@ -3705,47 +2564,44 @@ void Cmd_Sell_f( gentity_t *ent ) { //remove upgrade if carried if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) && - BG_FindPurchasableForUpgrade( i ) ) + BG_Upgrade( i )->purchasable ) { - BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); - if( i == UP_BATTPACK ) + // shouldn't really need to test for this, but just to be safe + if( i == UP_BATTLESUIT ) { - int j; + vec3_t newOrigin; - //remove energy - for( j = WP_NONE; j < WP_NUM_WEAPONS; j++ ) + if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) { - if( BG_InventoryContainsWeapon( j, ent->client->ps.stats ) && - BG_FindUsesEnergyForWeapon( j ) && - !BG_FindInfinteAmmoForWeapon( j ) ) - { - // GH FIXME - ent->client->ps.ammo = 0; - ent->client->ps.clips = 0; - } + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); + continue; } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; } + BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); + + if( i == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( i ), qfalse ); + G_AddCreditToClient( ent->client, (short)BG_Upgrade( i )->price, qfalse ); } } } else - trap_SendServerCommand( ent-g_entities, va( "print \"Unknown item\n\"" ) ); - - if( trap_Argc( ) >= 2 ) { - trap_Argv( 2, s, sizeof( s ) ); - - //retrigger the armoury menu - if( !Q_stricmp( s, "retrigger" ) ) - ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES; + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); + return; } //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + ent->client->pers.infoChangeTime = level.time; } @@ -3759,555 +2615,156 @@ void Cmd_Build_f( gentity_t *ent ) char s[ MAX_TOKEN_CHARS ]; buildable_t buildable; float dist; - vec3_t origin; - pTeam_t team; + vec3_t origin, normal; + int groundEntNum; + team_t team; - if( ent->client->pers.denyBuild ) + if( ent->client->pers.namelog->denyBuild ) { - trap_SendServerCommand( ent-g_entities, - "print \"Your building rights have been revoked\n\"" ); + G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); return; } - if( ent->client->pers.paused ) + + if( ent->client->pers.teamSelection == level.surrenderTeam ) { - trap_SendServerCommand( ent-g_entities, - "print \"You may not mark while paused\n\"" ); + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SURRENDER ); return; } trap_Argv( 1, s, sizeof( s ) ); - buildable = BG_FindBuildNumForName( s ); + buildable = BG_BuildableByName( s )->number; + team = ent->client->ps.stats[ STAT_TEAM ]; + if( buildable == BA_NONE || !BG_BuildableIsAllowed( buildable ) || + !( ( 1 << ent->client->ps.weapon ) & BG_Buildable( buildable )->buildWeapon ) || + ( team == TEAM_ALIENS && !BG_BuildableAllowedInStage( buildable, g_alienStage.integer ) ) || + ( team == TEAM_HUMANS && !BG_BuildableAllowedInStage( buildable, g_humanStage.integer ) ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT ); + return; + } - if( g_suddenDeath.integer) + if( G_TimeTilSuddenDeath( ) <= 0 ) { - if( g_suddenDeathMode.integer == SDMODE_SELECTIVE ) - { - if( !BG_FindReplaceableTestForBuildable( buildable ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"This building type cannot be rebuilt during Sudden Death\n\"" ); - return; - } - if( G_BuildingExists( buildable ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You can only rebuild one of each type of rebuildable building during Sudden Death.\n\"" ); - return; - } - } - else if( g_suddenDeathMode.integer == SDMODE_NO_BUILD ) - { - trap_SendServerCommand( ent-g_entities, - "print \"Building is not allowed during Sudden Death\n\"" ); - return; - } + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); + return; } - team = ent->client->ps.stats[ STAT_PTEAM ]; + ent->client->ps.stats[ STAT_BUILDABLE ] = buildable; - if( buildable != BA_NONE && - ( ( 1 << ent->client->ps.weapon ) & BG_FindBuildWeaponForBuildable( buildable ) ) && - !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) && - !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) && - BG_BuildableIsAllowed( buildable ) && - ( ( team == PTE_ALIENS && BG_FindStagesForBuildable( buildable, g_alienStage.integer ) ) || - ( team == PTE_HUMANS && BG_FindStagesForBuildable( buildable, g_humanStage.integer ) ) ) ) + if( 1 ) { - dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); + dynMenu_t err; + dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; //these are the errors displayed when the builder first selects something to use - switch( G_CanBuild( ent, buildable, dist, origin ) ) + switch( G_CanBuild( ent, buildable, dist, origin, normal, &groundEntNum ) ) { + // can place right away, set the blueprint and the valid togglebit case IBE_NONE: case IBE_TNODEWARN: - case IBE_RPTWARN: - case IBE_RPTWARN2: + case IBE_RPTNOREAC: + case IBE_RPTPOWERHERE: case IBE_SPWNWARN: - case IBE_NOROOM: + err = MN_NONE; + ent->client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; + break; + + // can't place yet but maybe soon: start with valid togglebit off case IBE_NORMAL: - case IBE_HOVELEXIT: - ent->client->ps.stats[ STAT_BUILDABLE ] = ( buildable | SB_VALID_TOGGLEBIT ); + err = MN_B_NORMAL; + break; + + case IBE_NOCREEP: + err = MN_A_NOCREEP; break; - case IBE_NOASSERT: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT ); + case IBE_NOROOM: + err = MN_B_NOROOM; break; case IBE_NOOVERMIND: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND ); + err = MN_A_NOOVMND; break; - case IBE_OVERMIND: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND ); + case IBE_NOPOWERHERE: + err = MN_NONE; break; - case IBE_REACTOR: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR ); + // more serious errors just pop a menu + case IBE_NOALIENBP: + err = MN_A_NOBP; break; - case IBE_REPEATER: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER ); + case IBE_ONEOVERMIND: + err = MN_A_ONEOVERMIND; break; - case IBE_NOPOWER: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER ); + case IBE_ONEREACTOR: + err = MN_H_ONEREACTOR; break; - case IBE_NOCREEP: - G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP ); + case IBE_NOHUMANBP: + err = MN_H_NOBP; break; case IBE_NODCC: - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC ); + err = MN_H_NODCC; break; - default: + case IBE_PERMISSION: + err = MN_B_NORMAL; break; - } - } - else - trap_SendServerCommand( ent-g_entities, va( "print \"Cannot build this item\n\"" ) ); -} - - -/* -================= -Cmd_Boost_f -================= -*/ -void Cmd_Boost_f( gentity_t *ent ) -{ - if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && - BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) ) - return; - - if( ent->client->pers.cmd.buttons & BUTTON_WALKING ) - return; - - if( ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) && - ( ent->client->ps.stats[ STAT_STAMINA ] > 0 ) ) - ent->client->ps.stats[ STAT_STATE ] |= SS_SPEEDBOOST; -} - -/* -================= -Cmd_Protect_f -================= -*/ -void Cmd_Protect_f( gentity_t *ent ) -{ - vec3_t forward, end; - trace_t tr; - gentity_t *traceEnt; - - if( !ent->client->pers.designatedBuilder ) - { - trap_SendServerCommand( ent-g_entities, "print \"Only designated" - " builders can toggle structure protection.\n\"" ); - return; - } - - AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); - VectorMA( ent->client->ps.origin, 100, forward, end ); - - trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, - MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; - - if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) && - ( traceEnt->biteam == ent->client->pers.teamSelection ) ) - { - if( traceEnt->s.eFlags & EF_DBUILDER ) - { - trap_SendServerCommand( ent-g_entities, - "print \"Structure protection removed\n\"" ); - traceEnt->s.eFlags &= ~EF_DBUILDER; - } - else - { - trap_SendServerCommand( ent-g_entities, - "print \"Structure protection applied\n\"" ); - traceEnt->s.eFlags |= EF_DBUILDER; - - // adding protection turns off deconstruction mark - traceEnt->deconstruct = qfalse; - } - } -} - - /* - ================= - Cmd_Resign_f - ================= - */ - void Cmd_Resign_f( gentity_t *ent ) - { - if( !ent->client->pers.designatedBuilder ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You are not a designated builder\n\"" ); - return; - } - - ent->client->pers.designatedBuilder = qfalse; - trap_SendServerCommand( -1, va( - "print \"%s" S_COLOR_WHITE " has resigned\n\"", - ent->client->pers.netname ) ); - G_CheckDBProtection( ); - } - - - -/* -================= -Cmd_Reload_f -================= -*/ -void Cmd_Reload_f( gentity_t *ent ) -{ - if( ( ent->client->ps.weapon >= WP_ABUILD ) && - ( ent->client->ps.weapon <= WP_HBUILD ) ) - { - if( ent->client->pers.designatedBuilder ) - Cmd_Protect_f( ent ); - else - Cmd_Mark_f( ent ); - } - else if( ent->client->ps.weaponstate != WEAPON_RELOADING ) - ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; -} - - -/* -================= -Cmd_MyStats_f -================= -*/ -void Cmd_MyStats_f( gentity_t *ent ) -{ - - if(!ent) return; - - - if( !level.intermissiontime && ent->client->pers.statscounters.timeLastViewed && (level.time - ent->client->pers.statscounters.timeLastViewed) <60000 ) - { - ADMP( "You may only check your stats once per minute and during intermission.\n"); - return; - } - - if( !g_myStats.integer ) - { - ADMP( "myStats has been disabled\n"); - return; - } - - ADMP( G_statsString( &ent->client->pers.statscounters, &ent->client->pers.teamSelection ) ); - ent->client->pers.statscounters.timeLastViewed = level.time; - - return; -} - -char *G_statsString( statsCounters_t *sc, pTeam_t *pt ) -{ - char *s; - - int percentNearBase=0; - int percentJetpackWallwalk=0; - int percentHeadshots=0; - double avgTimeAlive=0; - int avgTimeAliveMins = 0; - int avgTimeAliveSecs = 0; - - if( sc->timealive ) - percentNearBase = (int)(100 * (float) sc->timeinbase / ((float) (sc->timealive ) ) ); - - if( sc->timealive && sc->deaths ) - { - avgTimeAlive = sc->timealive / sc->deaths; - } - - avgTimeAliveMins = (int) (avgTimeAlive / 60.0f); - avgTimeAliveSecs = (int) (avgTimeAlive - (60.0f * avgTimeAliveMins)); - - if( *pt == PTE_ALIENS ) - { - if( sc->dretchbasytime > 0 ) - percentJetpackWallwalk = (int)(100 * (float) sc->jetpackusewallwalkusetime / ((float) ( sc->dretchbasytime) ) ); - - if( sc->hitslocational ) - percentHeadshots = (int)(100 * (float) sc->headshots / ((float) (sc->hitslocational) ) ); - - s = va( "^3Kills:^7 %3i ^3StructKills:^7 %3i ^3Assists:^7 %3i^7 ^3Poisons:^7 %3i ^3Headshots:^7 %3i (%3i)\n^3Deaths:^7 %3i ^3Feeds:^7 %3i ^3Suicides:^7 %3i ^3TKs:^7 %3i ^3Avg Lifespan:^7 %4d:%02d\n^3Damage to:^7 ^3Enemies:^7 %5i ^3Structs:^7 %5i ^3Friendlies:^7 %3i \n^3Structs Built:^7 %3i ^3Time Near Base:^7 %3i ^3Time wallwalking:^7 %3i\n", - sc->kills, - sc->structskilled, - sc->assists, - sc->repairspoisons, - sc->headshots, - percentHeadshots, - sc->deaths, - sc->feeds, - sc->suicides, - sc->teamkills, - avgTimeAliveMins, - avgTimeAliveSecs, - sc->dmgdone, - sc->structdmgdone, - sc->ffdmgdone, - sc->structsbuilt, - percentNearBase, - percentJetpackWallwalk - ); - } - else if( *pt == PTE_HUMANS ) - { - if( sc->timealive ) - percentJetpackWallwalk = (int)(100 * (float) sc->jetpackusewallwalkusetime / ((float) ( sc->timealive ) ) ); - s = va( "^3Kills:^7 %3i ^3StructKills:^7 %3i ^3Assists:^7 %3i \n^3Deaths:^7 %3i ^3Feeds:^7 %3i ^3Suicides:^7 %3i ^3TKs:^7 %3i ^3Avg Lifespan:^7 %4d:%02d\n^3Damage to:^7 ^3Enemies:^7 %5i ^3Structs:^7 %5i ^3Friendlies:^7 %3i \n^3Structs Built:^7 %3i ^3Repairs:^7 %4i ^3Time Near Base:^7 %3i ^3Time Jetpacking:^7 %3i\n", - sc->kills, - sc->structskilled, - sc->assists, - sc->deaths, - sc->feeds, - sc->suicides, - sc->teamkills, - avgTimeAliveMins, - avgTimeAliveSecs, - sc->dmgdone, - sc->structdmgdone, - sc->ffdmgdone, - sc->structsbuilt, - sc->repairspoisons, - percentNearBase, - percentJetpackWallwalk - ); - } - else s="No stats available\n"; - - return s; -} - - /* - ================= - Cmd_AllStats_f - ================= - */ - void Cmd_AllStats_f( gentity_t *ent ) - { - int i; - int NextViewTime; - int NumResults = 0; - int Teamcolor = 3; - gentity_t *tmpent; - - //check if ent exists - if(!ent) return; - - NextViewTime = ent->client->pers.statscounters.AllstatstimeLastViewed + g_AllStatsTime.integer * 1000; - //check if you can use the cmd at this time - if( !level.intermissiontime && level.time < NextViewTime) - { - ADMP( va("You may only check your stats every %i Seconds and during intermission. Next valid time is %d:%02d\n",( g_AllStatsTime.integer ) ? ( g_AllStatsTime.integer ) : 60, ( NextViewTime / 60000 ), ( NextViewTime / 1000 ) % 60 ) ); - return; - } - //see if allstats is enabled - if( !g_AllStats.integer ) - { - ADMP( "AllStats has been disabled\n"); - return; - } - ADMP("^3K^2=^7Kills ^3A^2=^7Assists ^3SK^2=^7StructKills\n^3D^2=^7Deaths ^3F^2=^7Feeds ^3S^2=^7Suicides ^3TK^2=^7Teamkills\n^3DD^2=^7Damage done ^3TDD^2=^7Team Damage done\n^3SB^2=^7Structs Built\n\n" ); - //display a header describing the data - ADMP( "^3 #| K A SK| D F S TK| DD TDD| SB| Name\n" ); - //loop through the clients that are connected - for( i = 0; i < level.numConnectedClients; i++ ) - { - //assign a tempent 4 the hell of it - tmpent = &g_entities[ level.sortedClients[ i ] ]; - - //check for what mode we are working in and display relevent data - if( g_AllStats.integer == 1 ) - { - //check if client is connected and on same team - if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && tmpent->client->pers.teamSelection == ent->client->pers.teamSelection && tmpent->client->pers.teamSelection != PTE_NONE ) - { - NumResults++; - if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1; - if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5; - ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n", - Teamcolor, - NumResults, - Teamcolor, - ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0, - ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0, - ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0, - ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0, - ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0, - ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0, - ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0, - ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) ); - } - } - else if( g_AllStats.integer == 2 ) - { - //check if client is connected and has some stats or atleast is on a team - if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && ( tmpent->client->pers.teamSelection != PTE_NONE ) ) - { - NumResults++; - if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1; - if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5; - ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n", - Teamcolor, - NumResults, - Teamcolor, - ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0, - ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0, - ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0, - ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0, - ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0, - ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0, - ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0, - ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) ); - } - } - } - if( NumResults == 0 ) { - ADMP( " ^3EMPTY!\n" ); - } else { - ADMP( va( "^7 %i Players found!\n", NumResults ) ); + case IBE_LASTSPAWN: + err = MN_B_LASTSPAWN; + break; + + default: + err = -1; // stop uninitialised warning + break; } - //update time last viewed - ent->client->pers.statscounters.AllstatstimeLastViewed = level.time; - return; + if( err == MN_NONE || ent->client->pers.disableBlueprintErrors ) + ent->client->ps.stats[ STAT_BUILDABLE ] |= buildable; + else + G_TriggerMenu( ent->client->ps.clientNum, err ); + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT ); } /* ================= -Cmd_TeamStatus_f +Cmd_Reload_f ================= */ -void Cmd_TeamStatus_f( gentity_t *ent ) +void Cmd_Reload_f( gentity_t *ent ) { - char multiple[ 12 ]; - int builders = 0; - int arm = 0, mediboost = 0; - int omrccount = 0, omrchealth = 0; - qboolean omrcbuild = qfalse; - gentity_t *tmp; - int i; - - if( !g_teamStatus.integer ) - { - trap_SendServerCommand( ent - g_entities, - "print \"teamstatus is disabled.\n\"" ); - return; - } + playerState_t *ps = &ent->client->ps; + int ammo; - if( G_IsMuted( ent->client ) ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You are muted and cannot use message commands.\n\"" ); + // weapon doesn't ever need reloading + if( BG_Weapon( ps->weapon )->infiniteAmmo ) return; - } - if( ent->client->pers.lastTeamStatus && - ( level.time - ent->client->pers.lastTeamStatus) < g_teamStatus.integer * 1000 ) - { - ADMP( va("You may only check your team's status once every %i seconds.\n", - g_teamStatus.integer )); + if( ps->clips <= 0 ) return; - } - - ent->client->pers.lastTeamStatus = level.time; - - tmp = &g_entities[ 0 ]; - for ( i = 0; i < level.num_entities; i++, tmp++ ) - { - if( i < MAX_CLIENTS ) - { - if( tmp->client && - tmp->client->pers.connected == CON_CONNECTED && - tmp->client->pers.teamSelection == ent->client->pers.teamSelection && - tmp->health > 0 && - ( tmp->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || - tmp->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || - BG_InventoryContainsWeapon( WP_HBUILD, tmp->client->ps.stats ) || - BG_InventoryContainsWeapon( WP_HBUILD2, tmp->client->ps.stats ) ) ) - builders++; - continue; - } - - if( tmp->s.eType == ET_BUILDABLE ) - { - if( tmp->biteam != ent->client->pers.teamSelection || - tmp->health <= 0 ) - continue; - - switch( tmp->s.modelindex ) - { - case BA_H_REACTOR: - case BA_A_OVERMIND: - omrccount++; - if( tmp->health > omrchealth ) - omrchealth = tmp->health; - if( !omrcbuild ) - omrcbuild = tmp->spawned; - break; - case BA_H_ARMOURY: - arm++; - break; - case BA_H_MEDISTAT: - case BA_A_BOOSTER: - mediboost++; - break; - default: - break; - } - } - } - if( omrccount > 1 ) - Com_sprintf( multiple, sizeof( multiple ), "^7[x%d]", omrccount ); + if( BG_Weapon( ps->weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, ps->stats ) ) + ammo = BG_Weapon( ps->weapon )->maxAmmo * BATTPACK_MODIFIER; else - multiple[ 0 ] = '\0'; + ammo = BG_Weapon( ps->weapon )->maxAmmo; - if( ent->client->pers.teamSelection == PTE_ALIENS ) - { - G_Say( ent, NULL, SAY_TEAM, - va( "^3OM: %s(%d)%s ^3Spawns: ^5%d ^3Builders: ^5%d ^3Boosters: ^5%d^7" , - ( !omrccount ) ? "^1Down" : ( omrcbuild ) ? "^2Up" : "^5Building", - omrchealth * 100 / OVERMIND_HEALTH, - multiple, - level.numAlienSpawns, - builders, - mediboost ) ); - } - else - { - G_Say( ent, NULL, SAY_TEAM, - va( "^3RC: %s(%d)%s ^3Spawns: ^5%d ^3Builders: ^5%d ^3Armouries: ^5%d ^3Medistations: ^5%d^7" , - ( !omrccount ) ? "^1Down" : ( omrcbuild ) ? "^2Up" : "^5Building", - omrchealth * 100 / REACTOR_HEALTH, - multiple, - level.numHumanSpawns, - builders, - arm, mediboost ) ); - } + // don't reload when full + if( ps->ammo >= ammo ) + return; + + // the animation, ammo refilling etc. is handled by PM_Weapon + if( ent->client->ps.weaponstate != WEAPON_RELOADING ) + ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; } /* @@ -4325,7 +2782,7 @@ void G_StopFromFollowing( gentity_t *ent ) for( i = 0; i < level.maxclients; i++ ) { if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && - level.clients[ i ].sess.spectatorClient == ent-g_entities ) + level.clients[ i ].sess.spectatorClient == ent->client->ps.clientNum ) { if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) G_StopFollowing( &g_entities[ i ] ); @@ -4343,44 +2800,86 @@ to free floating spectator mode */ void G_StopFollowing( gentity_t *ent ) { - ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; - ent->client->sess.sessionTeam = TEAM_SPECTATOR; - ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; - if( ent->client->pers.teamSelection == PTE_NONE ) + if( ent->client->pers.teamSelection == TEAM_NONE ) { - ent->client->sess.spectatorState = SPECTATOR_FREE; - ent->client->ps.pm_type = PM_SPECTATOR; - ent->client->ps.stats[ STAT_HEALTH ] = 100; // hacky server-side fix to prevent cgame from viewlocking a freespec + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FREE; } else { - vec3_t spawn_origin, spawn_angles; + vec3_t spawn_origin, spawn_angles; - ent->client->sess.spectatorState = SPECTATOR_LOCKED; - if( ent->client->pers.teamSelection == PTE_ALIENS ) + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_LOCKED; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( ent->client->pers.teamSelection == PTE_HUMANS ) + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, ent->client->ps.origin ); G_SetClientViewAngle( ent, spawn_angles ); } ent->client->sess.spectatorClient = -1; ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + ent->client->ps.stats[ STAT_STATE ] = 0; + ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; + ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + ent->client->ps.viewangles[ PITCH ] = 0.0f; + ent->client->ps.clientNum = ent - g_entities; + ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + + if( ent->client->pers.teamSelection == TEAM_NONE ) + { + vec3_t viewOrigin, angles; + + BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); + VectorCopy( ent->client->ps.viewangles, angles ); + angles[ ROLL ] = 0; + TeleportPlayer( ent, viewOrigin, angles, qfalse ); + } + + CalculateRanks( ); +} + +/* +================= +G_FollowLockView - // Prevent spawning with bsuit in rare case - if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) ) - BG_RemoveUpgradeFromInventory( UP_BATTLESUIT, ent->client->ps.stats ); +Client is still following a player, but that player has gone to spectator +mode and cannot be followed for the moment +================= +*/ +void G_FollowLockView( gentity_t *ent ) +{ + vec3_t spawn_origin, spawn_angles; + int clientNum; + clientNum = ent->client->sess.spectatorClient; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FOLLOW; + ent->client->ps.clientNum = clientNum; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; - ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; - ent->client->ps.eFlags &= ~EF_WALLCLIMB; + ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; + ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; ent->client->ps.viewangles[ PITCH ] = 0.0f; - ent->client->ps.clientNum = ent - g_entities; + // Put the view at the team spectator lock position + if( level.clients[ clientNum ].pers.teamSelection == TEAM_ALIENS ) + G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( level.clients[ clientNum ].pers.teamSelection == TEAM_HUMANS ) + G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); - CalculateRanks( ); + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, ent->client->ps.origin ); + G_SetClientViewAngle( ent, spawn_angles ); } /* @@ -4403,7 +2902,7 @@ qboolean G_FollowNewClient( gentity_t *ent, int dir ) else if( dir == 0 ) return qtrue; - if( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) return qfalse; // select any if no target exists @@ -4423,36 +2922,41 @@ qboolean G_FollowNewClient( gentity_t *ent, int dir ) if( clientnum < 0 ) clientnum = level.maxclients - 1; + // can't follow self + if( &g_entities[ clientnum ] == ent ) + continue; + // avoid selecting existing follow target if( clientnum == original && !selectAny ) continue; //effectively break; - // can't follow self - if( &level.clients[ clientnum ] == ent->client ) - continue; - // can only follow connected clients if( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) continue; - // can't follow another spectator - if( level.clients[ clientnum ].pers.teamSelection == PTE_NONE ) - continue; - - // can only follow teammates when dead and on a team - if( ent->client->pers.teamSelection != PTE_NONE && - ( level.clients[ clientnum ].pers.teamSelection != - ent->client->pers.teamSelection ) ) - continue; - - // cannot follow a teammate who is following you - if( level.clients[ clientnum ].sess.spectatorState == SPECTATOR_FOLLOW && - ( level.clients[ clientnum ].sess.spectatorClient == ent->s.number ) ) - continue; + // can't follow a spectator + if( level.clients[ clientnum ].pers.teamSelection == TEAM_NONE ) + continue; + + // if stickyspec is disabled, can't follow someone in queue either + if( !ent->client->pers.stickySpec && + level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) + continue; + + // can only follow teammates when dead and on a team + if( ent->client->pers.teamSelection != TEAM_NONE && + ( level.clients[ clientnum ].pers.teamSelection != + ent->client->pers.teamSelection ) ) + continue; // this is good, we can use it ent->client->sess.spectatorClient = clientnum; ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + + // if this client is in the spawn queue, we need to do something special + if( level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) + G_FollowLockView( ent ); + return qtrue; } while( clientnum != original ); @@ -4467,12 +2971,6 @@ G_ToggleFollow */ void G_ToggleFollow( gentity_t *ent ) { - if( level.mapRotationVoteTime ) - { - G_IntermissionMapVoteCommand( ent, qfalse, qtrue ); - return; - } - if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); else @@ -4487,20 +2985,11 @@ Cmd_Follow_f void Cmd_Follow_f( gentity_t *ent ) { int i; - int pids[ MAX_CLIENTS ]; - char arg[ MAX_TOKEN_CHARS ]; + char arg[ MAX_NAME_LENGTH ]; - if( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) - { - trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow unless you are dead or on the spectators.\n\"" ); - return; - } - if( ent->client->pers.paused ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You may not build while paused\n\"" ); + // won't work unless spectating + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) return; - } if( trap_Argc( ) != 2 ) { @@ -4508,45 +2997,32 @@ void Cmd_Follow_f( gentity_t *ent ) } else { + char err[ MAX_STRING_CHARS ]; trap_Argv( 1, arg, sizeof( arg ) ); - if( G_ClientNumbersFromString( arg, pids ) == 1 ) - { - i = pids[ 0 ]; - } - else - { - i = G_ClientNumberFromString( ent, arg ); - if( i == -1 ) - { - trap_SendServerCommand( ent - g_entities, - "print \"follow: invalid player\n\"" ); - return; - } + i = G_ClientNumberFromString( arg, err, sizeof( err ) ); + + if( i == -1 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"follow: %s\"", err ) ); + return; } // can't follow self if( &level.clients[ i ] == ent->client ) - { - trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow yourself.\n\"" ); return; - } - // can't follow another spectator - if( level.clients[ i ].pers.teamSelection == PTE_NONE) - { - trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow another spectator.\n\"" ); + // can't follow another spectator if sticky spec is off + if( !ent->client->pers.stickySpec && + level.clients[ i ].sess.spectatorState != SPECTATOR_NOT ) return; - } - // can only follow teammates when dead and on a team - if( ent->client->pers.teamSelection != PTE_NONE && - ( level.clients[ i ].pers.teamSelection != + // if not on team spectator, you can only follow teammates + if( ent->client->pers.teamSelection != TEAM_NONE && + ( level.clients[ i ].pers.teamSelection != ent->client->pers.teamSelection ) ) - { - trap_SendServerCommand( ent - g_entities, "print \"follow: You can only follow teammates, and only when you are dead.\n\"" ); return; - } ent->client->sess.spectatorState = SPECTATOR_FOLLOW; ent->client->sess.spectatorClient = i; @@ -4568,586 +3044,430 @@ void Cmd_FollowCycle_f( gentity_t *ent ) dir = -1; // won't work unless spectating - if( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) - return; - if( ent->client->sess.spectatorState == SPECTATOR_NOT ) - return; + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return; + G_FollowNewClient( ent, dir ); } +static void Cmd_Ignore_f( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ]; + char cmd[ 9 ]; + int matches = 0; + int i; + qboolean ignore = qfalse; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "ignore" ) == 0 ) + ignore = qtrue; + + if( trap_Argc() < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "usage: %s [clientNum | partial name match]\n\"", cmd ) ); + return; + } + + Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); + matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + if( matches < 1 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "%s: no clients match the name '%s'\n\"", cmd, name ) ); + return; + } + + for( i = 0; i < matches; i++ ) + { + if( ignore ) + { + if( !Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) + { + Com_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "ignore: added %s^7 to your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + else + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "ignore: %s^7 is already on your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + else + { + if( Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) + { + Com_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "unignore: removed %s^7 from your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + else + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "unignore: %s^7 is not on your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + } +} + /* ================= -Cmd_PTRCVerify_f +Cmd_ListMaps_f -Check a PTR code is valid +List all maps on the server ================= */ -void Cmd_PTRCVerify_f( gentity_t *ent ) + +static int SortMaps( const void *a, const void *b ) { - connectionRecord_t *connection; - char s[ MAX_TOKEN_CHARS ] = { 0 }; - int code; + return strcmp( *(char **)a, *(char **)b ); +} - if( ent->client->pers.connection ) - return; +#define MAX_MAPLIST_MAPS 256 +#define MAX_MAPLIST_ROWS 9 +void Cmd_ListMaps_f( gentity_t *ent ) +{ + char search[ 16 ] = {""}; + char fileList[ 4096 ] = {""}; + char *fileSort[ MAX_MAPLIST_MAPS ]; + char *filePtr, *p; + int numFiles; + int fileLen = 0; + int shown = 0; + int count = 0; + int page = 0; + int pages; + int row, rows; + int start, i, j; - trap_Argv( 1, s, sizeof( s ) ); + if( trap_Argc( ) > 1 ) + { + trap_Argv( 1, search, sizeof( search ) ); + for( p = search; ( *p ) && isdigit( *p ); p++ ); + if( !( *p ) ) + { + page = atoi( search ); + search[ 0 ] = '\0'; + } + else if( trap_Argc( ) > 2 ) + { + char lp[ 8 ]; + trap_Argv( 2, lp, sizeof( lp ) ); + page = atoi( lp ); + } + + if( page > 0 ) + page--; + else if( page < 0 ) + page = 0; + } + + numFiles = trap_FS_GetFileList( "maps/", ".bsp", + fileList, sizeof( fileList ) ); + filePtr = fileList; + for( i = 0; i < numFiles && count < MAX_MAPLIST_MAPS; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + if ( fileLen < 5 ) + continue; + + filePtr[ fileLen - 4 ] = '\0'; + + if( search[ 0 ] && !strstr( filePtr, search ) ) + continue; + + fileSort[ count ] = filePtr; + count++; + } + qsort( fileSort, count, sizeof( fileSort[ 0 ] ), SortMaps ); + + rows = ( count + 2 ) / 3; + pages = MAX( 1, ( rows + MAX_MAPLIST_ROWS - 1 ) / MAX_MAPLIST_ROWS ); + if( page >= pages ) + page = pages - 1; + + start = page * MAX_MAPLIST_ROWS * 3; + if( count < start + ( 3 * MAX_MAPLIST_ROWS ) ) + rows = ( count - start + 2 ) / 3; + else + rows = MAX_MAPLIST_ROWS; + + ADMBP_begin( ); + for( row = 0; row < rows; row++ ) + { + for( i = start + row, j = 0; i < count && j < 3; i += rows, j++ ) + { + ADMBP( va( "^7 %-20s", fileSort[ i ] ) ); + shown++; + } + ADMBP( "\n" ); + } + if ( search[ 0 ] ) + ADMBP( va( "^3listmaps: ^7found %d maps matching '%s^7'", count, search ) ); + else + ADMBP( va( "^3listmaps: ^7listing %d of %d maps", shown, count ) ); + if( pages > 1 ) + ADMBP( va( ", page %d of %d", page + 1, pages ) ); + if( page + 1 < pages ) + ADMBP( va( ", use 'listmaps %s%s%d' to see more", + search, ( search[ 0 ] ) ? " ": "", page + 2 ) ); + ADMBP( ".\n" ); + ADMBP_end( ); +} + +/* +================= +Cmd_ListVoices_f +================= +*/ +void Cmd_ListVoices_f( gentity_t *ent ) +{ + if ( !level.voices ) { + ADMP( "^3listvoices: ^7voice system is not installed on this server\n" ); + return; + } - if( !strlen( s ) ) + if ( !g_voiceChats.integer ) { + ADMP( "^3listvoices: ^7voice system administratively disabled on this server\n" ); return; + } - code = atoi( s ); + if ( trap_Argc() < 2 ) + { + voice_t *v; + int i = 0; - connection = G_FindConnectionForCode( code ); - if( connection && connection->clientNum == -1 ) + ADMBP_begin(); + for( v = level.voices; v; v = v->next ) + { + ADMBP(va("%d - %s\n", i+1, v->name)); + i++; + } + ADMBP(va("^3listvoices: ^7showing %d voices\n", i)); + ADMBP("^3listvoices: ^7run 'listvoices ' to see available commands.\n"); + ADMBP_end(); + return; + } + else if ( trap_Argc() >= 2 ) { - // valid code - if( connection->clientTeam != PTE_NONE ) - trap_SendServerCommand( ent->client->ps.clientNum, "ptrcconfirm" ); + voice_t *v; + voiceCmd_t *c; + int i = 0; + + char name[ MAX_VOICE_NAME_LEN ]; + trap_Argv(1, name, sizeof(name)); + + v = BG_VoiceByName(level.voices, name); + if ( !v ) + { + ADMP(va("^3listvoices: ^7no matching voice \"%s\"\n", name)); + return; + } + + ADMBP_begin(); + for ( c = v->cmds; c; c = c->next ) + { + ADMBP(va("%d - %s\n", i+1, c->cmd)); + i++; + } + ADMBP(va("^3listvoices: ^7showing %d voice commands for %s\n", i, v->name)); + ADMBP_end(); + } +} + +/* +================= +Cmd_ListModels_f + +List all the available player models installed on the server. +================= +*/ +void Cmd_ListModels_f( gentity_t *ent ) +{ + int i; + + ADMBP_begin(); + for (i = 0; i < level.playerModelCount; i++) + { + ADMBP(va("%d - %s\n", i+1, level.playerModel[i])); + } + ADMBP(va("^3listmodels: ^7showing %d player models\n", level.playerModelCount)); + ADMBP_end(); + +} + +/* +================= +Cmd_ListSkins_f +================= +*/ +void Cmd_ListSkins_f( gentity_t *ent ) +{ + char modelname[ 64 ]; + char skins[ MAX_PLAYER_MODEL ][ 64 ]; + int numskins; + int i; + + if ( trap_Argc() < 2 ) + { + ADMP("^3listskins: ^7usage: listskins \n"); + return; + } + + trap_Argv(1, modelname, sizeof(modelname)); - // restore mapping - ent->client->pers.connection = connection; - connection->clientNum = ent->client->ps.clientNum; - } - else - { - // invalid code -- generate a new one - connection = G_GenerateNewConnection( ent->client ); + G_GetPlayerModelSkins(modelname, skins, MAX_PLAYER_MODEL, &numskins); - if( connection ) + ADMBP_begin(); + for (i = 0; i < numskins; i++) { - trap_SendServerCommand( ent->client->ps.clientNum, - va( "ptrcissue %d", connection->ptrCode ) ); + ADMBP(va("%d - %s\n", i+1, skins[i])); } - } + ADMBP(va("^3listskins: ^7default skin ^2%s\n", GetSkin(modelname, "default"))); + ADMBP(va("^3listskins: ^7showing %d skins for %s\n", numskins, modelname)); + ADMBP_end(); } + /* ================= -Cmd_PTRCRestore_f - -Restore against a PTR code +Cmd_Test_f ================= */ -void Cmd_PTRCRestore_f( gentity_t *ent ) +void Cmd_Test_f( gentity_t *humanPlayer ) { - char s[ MAX_TOKEN_CHARS ] = { 0 }; - int code; - connectionRecord_t *connection; - - if( ent->client->pers.joinedATeam ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You cannot use a PTR code after joining a team\n\"" ); - return; - } - - trap_Argv( 1, s, sizeof( s ) ); +} - if( !strlen( s ) ) - return; +/* +================= +Cmd_Damage_f - code = atoi( s ); +Deals damage to you (for testing), arguments: [damage] [dx] [dy] [dz] +The dx/dy arguments describe the damage point's offset from the entity origin +================= +*/ +void Cmd_Damage_f( gentity_t *ent ) +{ + vec3_t point; + char arg[ 16 ]; + float dx = 0.0f, dy = 0.0f, dz = 100.0f; + int damage = 100; + qboolean nonloc = qtrue; - connection = ent->client->pers.connection; - if( connection && connection->ptrCode == code ) + if( trap_Argc() > 1 ) { - // Set the correct team - if( !( ent->client->pers.specExpires > level.time ) ) - { - // Check if the alien team is full - if( connection->clientTeam == PTE_ALIENS && - !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) && - g_teamForceBalance.integer && - level.numAlienClients > level.numHumanClients ) - { - G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); - } - // Check if the human team is full - else if( connection->clientTeam == PTE_HUMANS && - !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) && - g_teamForceBalance.integer && - level.numHumanClients > level.numAlienClients ) - { - G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); - } - else - { - G_ChangeTeam( ent, connection->clientTeam ); - } - } - - // set the correct credit etc. - ent->client->ps.persistant[ PERS_CREDIT ] = 0; - G_AddCreditToClient( ent->client, connection->clientCredit, qtrue ); - ent->client->pers.score = connection->clientScore; - ent->client->pers.enterTime = connection->clientEnterTime; + trap_Argv( 1, arg, sizeof( arg ) ); + damage = atoi( arg ); } - else + if( trap_Argc() > 4 ) { - trap_SendServerCommand( ent - g_entities, - va( "print \"\"%d\" is not a valid PTR code\n\"", code ) ); + trap_Argv( 2, arg, sizeof( arg ) ); + dx = atof( arg ); + trap_Argv( 3, arg, sizeof( arg ) ); + dy = atof( arg ); + trap_Argv( 4, arg, sizeof( arg ) ); + dz = atof( arg ); + nonloc = qfalse; } + VectorCopy( ent->r.currentOrigin, point ); + point[ 0 ] += dx; + point[ 1 ] += dy; + point[ 2 ] += dz; + G_Damage( ent, NULL, NULL, NULL, point, damage, + ( nonloc ? DAMAGE_NO_LOCDAMAGE : 0 ), MOD_TARGET_LASER ); } -static void Cmd_Ignore_f( gentity_t *ent ) +/* +================== +G_FloodLimited + +Determine whether a user is flood limited, and adjust their flood demerits +Print them a warning message if they are over the limit +Return is time in msec until the user can speak again +================== +*/ +int G_FloodLimited( gentity_t *ent ) { - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; - char cmd[ 9 ]; - int matches = 0; - int i; - qboolean ignore = qfalse; + int deltatime, ms; - trap_Argv( 0, cmd, sizeof( cmd ) ); - if( Q_stricmp( cmd, "ignore" ) == 0 ) - ignore = qtrue; + if( g_floodMinTime.integer <= 0 ) + return 0; - if( trap_Argc() < 2 ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "%s: usage \\%s [clientNum | partial name match]\n\"", cmd, cmd ) ); - return; - } + // handles !ent + if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) + return 0; - Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); - matches = G_ClientNumbersFromString( name, pids ); - if( matches < 1 ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "%s: no clients match the name '%s'\n\"", cmd, name ) ); - return; - } + deltatime = level.time - ent->client->pers.floodTime; - for( i = 0; i < matches; i++ ) - { - if( ignore ) - { - if( !BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) ) - { - BG_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "ignore: added %s^7 to your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - else - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "ignore: %s^7 is already on your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - } - else - { - if( BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) ) - { - BG_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "unignore: removed %s^7 from your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - else - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "unignore: %s^7 is not on your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - } - } -} + ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime; + if( ent->client->pers.floodDemerits < 0 ) + ent->client->pers.floodDemerits = 0; + ent->client->pers.floodTime = level.time; - /* - ================= - Cmd_Share_f - ================= - */ - void Cmd_Share_f( gentity_t *ent ) - { - int i, clientNum = 0, creds = 0, skipargs = 0; - int clientNums[ MAX_CLIENTS ] = { -1 }; - char cmd[ 12 ]; - char arg1[ MAX_STRING_TOKENS ]; - char arg2[ MAX_STRING_TOKENS ]; - pTeam_t team; - - if( !ent || !ent->client || ( ent->client->pers.teamSelection == PTE_NONE ) ) - { - return; - } - - if( !g_allowShare.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Share has been disabled.\n\"" ); - return; - } - - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); - return; - } - - team = ent->client->pers.teamSelection; - - G_SayArgv( 0, cmd, sizeof( cmd ) ); - if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) - { - skipargs = 1; - G_SayArgv( 1, cmd, sizeof( cmd ) ); - } - - // target player name is in arg1 - G_SayArgv( 1+skipargs, arg1, sizeof( arg1 ) ); - // amount to be shared is in arg2 - G_SayArgv( 2+skipargs, arg2, sizeof( arg2 ) ); - - if( arg1[0] && !strchr( arg1, ';' ) && Q_stricmp( arg1, "target_in_aim" ) ) - { - //check arg1 is a number - for( i = 0; arg1[ i ]; i++ ) - { - if( arg1[ i ] < '0' || arg1[ i ] > '9' ) - { - clientNum = -1; - break; - } - } - - if( clientNum >= 0 ) - { - clientNum = atoi( arg1 ); - } - else if( G_ClientNumbersFromString( arg1, clientNums ) == 1 ) - { - // there was one partial name match - clientNum = clientNums[ 0 ]; - } - else - { - // look for an exact name match before bailing out - clientNum = G_ClientNumberFromString( ent, arg1 ); - if( clientNum == -1 ) - { - trap_SendServerCommand( ent-g_entities, - "print \"share: invalid player name specified.\n\"" ); - return; - } - } - } - else // arg1 not set - { - vec3_t forward, end; - trace_t tr; - gentity_t *traceEnt; - - // trace a teammate - AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); - VectorMA( ent->client->ps.origin, 8192 * 16, forward, end ); - - trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; - - if( tr.fraction < 1.0f && traceEnt->client && - ( traceEnt->client->pers.teamSelection == team ) ) - { - clientNum = traceEnt - g_entities; - } - else - { - trap_SendServerCommand( ent-g_entities, - va( "print \"share: aim at a teammate to share %s.\n\"", - ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) ); - return; - } - } - - // verify target player team - if( ( clientNum < 0 ) || ( clientNum >= level.maxclients ) || - ( level.clients[ clientNum ].pers.teamSelection != team ) ) - { - trap_SendServerCommand( ent-g_entities, - "print \"share: not a valid player of your team.\n\"" ); - return; - } - - if( !arg2[0] || strchr( arg2, ';' ) ) - { - // default credit count - if( team == PTE_HUMANS ) - { - creds = FREEKILL_HUMAN; - } - else if( team == PTE_ALIENS ) - { - creds = FREEKILL_ALIEN; - } - } - else - { - //check arg2 is a number - for( i = 0; arg2[ i ]; i++ ) - { - if( arg2[ i ] < '0' || arg2[ i ] > '9' ) - { - trap_SendServerCommand( ent-g_entities, - "print \"usage: share [name|slot#] [amount]\n\"" ); - return; - } - } - - // credit count from parameter - creds = atoi( arg2 ); - } - - // player specified "0" to transfer - if( creds <= 0 ) - { - trap_SendServerCommand( ent-g_entities, - "print \"Ooh, you are a generous one, indeed!\n\"" ); - return; - } - - // transfer only credits the player really has - if( creds > ent->client->pers.credit ) - { - creds = ent->client->pers.credit; - } - - // player has no credits - if( creds <= 0 ) - { - trap_SendServerCommand( ent-g_entities, - "print \"Earn some first, lazy gal!\n\"" ); - return; - } - - // allow transfers only up to the credit/evo limit - if( ( team == PTE_HUMANS ) && - ( creds > HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit ) ) - { - creds = HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit; - } - else if( ( team == PTE_ALIENS ) && - ( creds > ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit ) ) - { - creds = ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit; - } - - // target cannot take any more credits - if( creds <= 0 ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"share: player cannot receive any more %s.\n\"", - ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) ); - return; - } - - // transfer credits - G_AddCreditToClient( ent->client, -creds, qfalse ); - trap_SendServerCommand( ent-g_entities, - va( "print \"share: transferred %d %s to %s^7.\n\"", creds, - ( team == PTE_HUMANS ) ? "credits" : "evolvepoints", - level.clients[ clientNum ].pers.netname ) ); - G_AddCreditToClient( &(level.clients[ clientNum ]), creds, qtrue ); - trap_SendServerCommand( clientNum, - va( "print \"You have received %d %s from %s^7.\n\"", creds, - ( team == PTE_HUMANS ) ? "credits" : "evolvepoints", - ent->client->pers.netname ) ); - - G_LogPrintf( "Share: %i %i %i %d: %s^7 transferred %d%s to %s^7\n", - ent->client->ps.clientNum, - clientNum, - team, - creds, - ent->client->pers.netname, - creds, - ( team == PTE_HUMANS ) ? "c" : "e", - level.clients[ clientNum ].pers.netname ); - } - - /* - ================= - Cmd_Donate_f - - Alms for the poor - ================= - */ - void Cmd_Donate_f( gentity_t *ent ) { - char s[ MAX_TOKEN_CHARS ] = "", *type = "evo(s)"; - int i, value, divisor, portion, new_credits, total=0, - max = ALIEN_MAX_KILLS, *amounts, *totals; - qboolean donated = qtrue; - - if( !ent->client ) return; - - if( !g_allowShare.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Donate has been disabled.\n\"" ); - return; - } - - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); - return; - } - - if( ent->client->pers.teamSelection == PTE_ALIENS ) - divisor = level.numAlienClients-1; - else if( ent->client->pers.teamSelection == PTE_HUMANS ) { - divisor = level.numHumanClients-1; - max = HUMAN_MAX_CREDITS; - type = "credit(s)"; - } else { - trap_SendServerCommand( ent-g_entities, - va( "print \"donate: spectators cannot be so gracious\n\"" ) ); - return; - } - - if( divisor < 1 ) { - trap_SendServerCommand( ent-g_entities, - "print \"donate: get yourself some teammates first\n\"" ); - return; - } - - trap_Argv( 1, s, sizeof( s ) ); - value = atoi(s); - if( value <= 0 ) { - trap_SendServerCommand( ent-g_entities, - "print \"donate: very funny\n\"" ); - return; - } - if( value > ent->client->pers.credit) - value = ent->client->pers.credit; - - // allocate memory for distribution amounts - amounts = G_Alloc( level.maxclients * sizeof( int ) ); - totals = G_Alloc( level.maxclients * sizeof( int ) ); - for( i = 0; i < level.maxclients; i++ ) { - amounts[ i ] = 0; - totals[ i ] = 0; - } - - // determine donation amounts for each client - total = value; - while( donated && value ) { - donated = qfalse; - portion = value / divisor; - if( portion < 1 ) portion = 1; - for( i = 0; i < level.maxclients; i++ ) - if( level.clients[ i ].pers.connected == CON_CONNECTED && - ent->client != level.clients + i && - level.clients[ i ].pers.teamSelection == - ent->client->pers.teamSelection && - level.clients[ i ].pers.credit < max ) { - new_credits = level.clients[ i ].pers.credit + portion; - amounts[ i ] = portion; - totals[ i ] += portion; - if( new_credits > max ) { - amounts[ i ] -= new_credits - max; - totals[ i ] -= new_credits - max; - new_credits = max; - } - if( amounts[ i ] ) { - G_AddCreditToClient( &(level.clients[ i ]), amounts[ i ], qtrue ); - donated = qtrue; - value -= amounts[ i ]; - if( value < portion ) break; - } - } - } - - // transfer funds - G_AddCreditToClient( ent->client, value - total, qtrue ); - for( i = 0; i < level.maxclients; i++ ) - if( totals[ i ] ) { - trap_SendServerCommand( i, - va( "print \"%s^7 donated %d %s to you, don't forget to say 'thank you'!\n\"", - ent->client->pers.netname, totals[ i ], type ) ); - } - - G_Free( amounts ); - G_Free( totals ); - - trap_SendServerCommand( ent-g_entities, - va( "print \"Donated %d %s to the cause.\n\"", - total-value, type ) ); - } + ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer; + if( ms <= 0 ) + return 0; + trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: " + "please wait %d second%s before trying again\n", + ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) ); + return ms; +} commands_t cmds[ ] = { - // normal commands - { "team", 0, Cmd_Team_f }, - { "vote", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Vote_f }, - { "ignore", 0, Cmd_Ignore_f }, - { "unignore", 0, Cmd_Ignore_f }, - - // communication commands - { "tell", CMD_MESSAGE, Cmd_Tell_f }, + { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_AdminMessage_f }, + { "build", CMD_TEAM|CMD_ALIVE, Cmd_Build_f }, + { "buy", CMD_HUMAN|CMD_ALIVE, Cmd_Buy_f }, + { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallVote_f }, { "callvote", CMD_MESSAGE, Cmd_CallVote_f }, - { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallTeamVote_f }, - { "say_area", CMD_MESSAGE|CMD_TEAM, Cmd_SayArea_f }, - // can be used even during intermission + { "class", CMD_TEAM, Cmd_Class_f }, + { "damage", CMD_CHEAT|CMD_ALIVE, Cmd_Damage_f }, + { "deconstruct", CMD_TEAM|CMD_ALIVE, Cmd_Destroy_f }, + { "destroy", CMD_CHEAT|CMD_TEAM|CMD_ALIVE, Cmd_Destroy_f }, + { "drop", CMD_HUMAN|CMD_CHEAT, Cmd_Drop_f }, + { "follow", CMD_SPEC, Cmd_Follow_f }, + { "follownext", CMD_SPEC, Cmd_FollowCycle_f }, + { "followprev", CMD_SPEC, Cmd_FollowCycle_f }, + { "give", CMD_CHEAT|CMD_TEAM, Cmd_Give_f }, + { "god", CMD_CHEAT, Cmd_God_f }, + { "ignore", 0, Cmd_Ignore_f }, + { "itemact", CMD_HUMAN|CMD_ALIVE, Cmd_ActivateItem_f }, + { "itemdeact", CMD_HUMAN|CMD_ALIVE, Cmd_DeActivateItem_f }, + { "itemtoggle", CMD_HUMAN|CMD_ALIVE, Cmd_ToggleItem_f }, + { "kill", CMD_TEAM|CMD_ALIVE, Cmd_Kill_f }, + { "listmaps", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListMaps_f }, + { "listmodels", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListModels_f }, + { "listskins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListSkins_f }, + { "listvoices", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListVoices_f }, + { "m", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, + { "mt", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, + { "noclip", CMD_CHEAT_TEAM, Cmd_Noclip_f }, + { "notarget", CMD_CHEAT|CMD_TEAM|CMD_ALIVE, Cmd_Notarget_f }, + { "reload", CMD_HUMAN|CMD_ALIVE, Cmd_Reload_f }, { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + { "say_area", CMD_MESSAGE|CMD_TEAM|CMD_ALIVE, Cmd_SayArea_f }, { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "say_admins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "say_hadmins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "ha", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "m", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage }, - { "mt", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage }, - { "me", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "me_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "score", CMD_INTERMISSION, ScoreboardMessage }, - { "mystats", CMD_TEAM|CMD_INTERMISSION, Cmd_MyStats_f }, - { "allstats", 0|CMD_INTERMISSION, Cmd_AllStats_f }, - { "teamstatus", CMD_TEAM, Cmd_TeamStatus_f }, - - // cheats - { "give", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Give_f }, - { "god", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_God_f }, - { "notarget", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Notarget_f }, - { "noclip", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Noclip_f }, - { "levelshot", CMD_CHEAT, Cmd_LevelShot_f }, - { "setviewpos", CMD_CHEAT, Cmd_SetViewpos_f }, - { "destroy", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, - - { "kill", CMD_TEAM|CMD_LIVING, Cmd_Kill_f }, - - // game commands - { "ptrcverify", CMD_NOTEAM, Cmd_PTRCVerify_f }, - { "ptrcrestore", CMD_NOTEAM, Cmd_PTRCRestore_f }, - - { "follow", 0, Cmd_Follow_f }, - { "follownext", 0, Cmd_FollowCycle_f }, - { "followprev", 0, Cmd_FollowCycle_f }, - - { "where", CMD_TEAM, Cmd_Where_f }, - { "teamvote", CMD_TEAM, Cmd_TeamVote_f }, - { "class", CMD_TEAM, Cmd_Class_f }, - - { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, - { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, - { "mark", CMD_TEAM|CMD_LIVING, Cmd_Mark_f }, - - { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, - { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, - { "itemact", CMD_HUMAN|CMD_LIVING, Cmd_ActivateItem_f }, - { "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f }, - { "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f }, - { "reload", CMD_TEAM|CMD_LIVING, Cmd_Reload_f }, - { "boost", 0, Cmd_Boost_f }, - { "share", CMD_TEAM, Cmd_Share_f }, - { "donate", CMD_TEAM, Cmd_Donate_f }, - { "protect", CMD_TEAM|CMD_LIVING, Cmd_Protect_f }, - { "resign", CMD_TEAM, Cmd_Resign_f }, - { "builder", 0, Cmd_Builder_f } + { "sell", CMD_HUMAN|CMD_ALIVE, Cmd_Sell_f }, + { "setviewpos", CMD_CHEAT_TEAM, Cmd_SetViewpos_f }, + { "team", 0, Cmd_Team_f }, + { "teamvote", CMD_TEAM, Cmd_Vote_f }, + { "test", CMD_CHEAT, Cmd_Test_f }, + { "unignore", 0, Cmd_Ignore_f }, + { "vote", 0, Cmd_Vote_f }, + { "vsay", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_local", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "where", 0, Cmd_Where_f } }; -static int numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] ); +static size_t numCmds = ARRAY_LEN( cmds ); /* ================= @@ -5156,25 +3476,21 @@ ClientCommand */ void ClientCommand( int clientNum ) { - gentity_t *ent; - char cmd[ MAX_TOKEN_CHARS ]; - int i; + gentity_t *ent; + char cmd[ MAX_TOKEN_CHARS ]; + commands_t *command; ent = g_entities + clientNum; - if( !ent->client ) + if( !ent->client || ent->client->pers.connected != CON_CONNECTED ) return; // not fully in game yet trap_Argv( 0, cmd, sizeof( cmd ) ); - for( i = 0; i < numCmds; i++ ) - { - if( Q_stricmp( cmd, cmds[ i ].cmdName ) == 0 ) - break; - } + command = bsearch( cmd, cmds, numCmds, sizeof( cmds[ 0 ] ), cmdcmp ); - if( i == numCmds ) + if( !command ) { - if( !G_admin_cmd_check( ent, qfalse ) ) + if( !G_admin_cmd_check( ent ) ) trap_SendServerCommand( clientNum, va( "print \"Unknown command %s\n\"", cmd ) ); return; @@ -5182,622 +3498,232 @@ void ClientCommand( int clientNum ) // do tests here to reduce the amount of repeated code - if( !( cmds[ i ].cmdFlags & CMD_INTERMISSION ) && ( level.intermissiontime || level.paused ) ) + if( !( command->cmdFlags & CMD_INTERMISSION ) && + ( level.intermissiontime || level.pausedTime ) ) return; - if( cmds[ i ].cmdFlags & CMD_CHEAT && !g_cheats.integer ) + if( command->cmdFlags & CMD_CHEAT && !g_cheats.integer ) { - trap_SendServerCommand( clientNum, - "print \"Cheats are not enabled on this server\n\"" ); + G_TriggerMenu( clientNum, MN_CMD_CHEAT ); return; } - if( cmds[ i ].cmdFlags & CMD_MESSAGE && G_IsMuted( ent->client ) ) - { - trap_SendServerCommand( clientNum, - "print \"You are muted and cannot use message commands.\n\"" ); + if( command->cmdFlags & CMD_MESSAGE && ( ent->client->pers.namelog->muted || + G_FloodLimited( ent ) ) ) return; - } - if( cmds[ i ].cmdFlags & CMD_TEAM && - ent->client->pers.teamSelection == PTE_NONE ) + if( command->cmdFlags & CMD_TEAM && + ent->client->pers.teamSelection == TEAM_NONE ) { - trap_SendServerCommand( clientNum, "print \"Join a team first\n\"" ); + G_TriggerMenu( clientNum, MN_CMD_TEAM ); return; } - if( cmds[ i ].cmdFlags & CMD_NOTEAM && - ent->client->pers.teamSelection != PTE_NONE ) + if( command->cmdFlags & CMD_CHEAT_TEAM && !g_cheats.integer && + ent->client->pers.teamSelection != TEAM_NONE ) { - trap_SendServerCommand( clientNum, - "print \"Cannot use this command when on a team\n\"" ); + G_TriggerMenu( clientNum, MN_CMD_CHEAT_TEAM ); return; } - if( cmds[ i ].cmdFlags & CMD_ALIEN && - ent->client->pers.teamSelection != PTE_ALIENS ) + if( command->cmdFlags & CMD_SPEC && + ent->client->sess.spectatorState == SPECTATOR_NOT ) { - trap_SendServerCommand( clientNum, - "print \"Must be alien to use this command\n\"" ); + G_TriggerMenu( clientNum, MN_CMD_SPEC ); return; } - if( cmds[ i ].cmdFlags & CMD_HUMAN && - ent->client->pers.teamSelection != PTE_HUMANS ) + if( command->cmdFlags & CMD_ALIEN && + ent->client->pers.teamSelection != TEAM_ALIENS ) { - trap_SendServerCommand( clientNum, - "print \"Must be human to use this command\n\"" ); + G_TriggerMenu( clientNum, MN_CMD_ALIEN ); return; } - if( cmds[ i ].cmdFlags & CMD_LIVING && - ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || - ent->client->sess.sessionTeam == TEAM_SPECTATOR ) ) + if( command->cmdFlags & CMD_HUMAN && + ent->client->pers.teamSelection != TEAM_HUMANS ) { - trap_SendServerCommand( clientNum, - "print \"Must be living to use this command\n\"" ); + G_TriggerMenu( clientNum, MN_CMD_HUMAN ); return; } - // Disallow a large class of commands if a player is restricted. - if( G_admin_is_restricted( ent, qtrue ) && - ( !Q_stricmp( cmd, "team" ) || - ( cmds[ i ].cmdFlags & ( CMD_MESSAGE | CMD_TEAM | CMD_NOTEAM ) ) ) ) + if( command->cmdFlags & CMD_ALIVE && + ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || + ent->client->sess.spectatorState != SPECTATOR_NOT ) ) { + G_TriggerMenu( clientNum, MN_CMD_ALIVE ); return; } - cmds[ i ].cmdHandler( ent ); + command->cmdHandler( ent ); } -int G_SayArgc( void ) +void G_ListCommands( gentity_t *ent ) { - int c = 0; - char *s; + int i; + char out[ MAX_STRING_CHARS ] = ""; + int len, outlen; - s = ConcatArgs( 0 ); - while( 1 ) + outlen = 0; + + for( i = 0; i < numCmds; i++ ) { - while( *s == ' ' ) - s++; - if( !*s ) - break; - c++; - while( *s && *s != ' ' ) - s++; - } - return c; -} + // never advertise cheats + if( cmds[ i ].cmdFlags & CMD_CHEAT ) + continue; -qboolean G_SayArgv( int n, char *buffer, int bufferLength ) -{ - int bc = 0; - int c = 0; - char *s; + len = strlen( cmds[ i ].cmdName ) + 1; + if( len + outlen >= sizeof( out ) - 1 ) + { + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + outlen = 0; + } - if( bufferLength < 1 ) - return qfalse; - if( n < 0 ) - return qfalse; - s = ConcatArgs( 0 ); - while( c < n ) - { - while( *s == ' ' ) - s++; - if( !*s ) - break; - c++; - while( *s && *s != ' ' ) - s++; + strcpy( out + outlen, va( " %s", cmds[ i ].cmdName ) ); + outlen += len; } - if( c < n ) - return qfalse; - while( *s == ' ' ) - s++; - if( !*s ) - return qfalse; - //memccpy( buffer, s, ' ', bufferLength ); - while( bc < bufferLength - 1 && *s && *s != ' ' ) - buffer[ bc++ ] = *s++; - buffer[ bc ] = 0; - return qtrue; + + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + G_admin_cmdlist( ent ); } -char *G_SayConcatArgs( int start ) +void G_DecolorString( const char *in, char *out, int len ) { - char *s; - int c = 0; + qboolean decolor = qtrue; - s = ConcatArgs( 0 ); - while( c < start ) - { - while( *s == ' ' ) - s++; - if( !*s ) - break; - c++; - while( *s && *s != ' ' ) - s++; - } - while( *s == ' ' ) - s++; - return s; -} + len--; -void G_DecolorString( char *in, char *out ) -{ - while( *in ) { - if( *in == 27 || *in == '^' ) { + while( *in && len > 0 ) { + if( *in == DECOLOR_OFF || *in == DECOLOR_ON ) + { + decolor = ( *in == DECOLOR_ON ); in++; - if( *in ) - in++; + continue; + } + if( Q_IsColorString( in ) && decolor ) { + in += 2; continue; } *out++ = *in++; + len--; } *out = '\0'; } -void G_ParseEscapedString( char *buffer ) +void G_UnEscapeString( char *in, char *out, int len ) { - int i = 0; - int j = 0; + len--; - while( buffer[i] ) + while( *in && len > 0 ) { - if(!buffer[i]) break; - - if(buffer[i] == '\\') + if( *in >= ' ' || *in == '\n' ) { - if(buffer[i + 1] == '\\') - buffer[j] = buffer[++i]; - else if(buffer[i + 1] == 'n') - { - buffer[j] = '\n'; - i++; - } - else - buffer[j] = buffer[i]; + *out++ = *in; + len--; } - else - buffer[j] = buffer[i]; - - i++; - j++; - } - buffer[j] = 0; -} - -void G_WordWrap( char *buffer, int maxwidth ) -{ - char out[ MAX_STRING_CHARS ]; - int i = 0; - int j = 0; - int k; - int linecount = 0; - int currentcolor = 7; - - while ( buffer[ j ]!='\0' ) - { - if( i == ( MAX_STRING_CHARS - 1 ) ) - break; - - //If it's the start of a new line, copy over the color code, - //but not if we already did it, or if the text at the start of the next line is also a color code - if( linecount == 0 && i>2 && out[ i-2 ] != Q_COLOR_ESCAPE && out[ i-1 ] != Q_COLOR_ESCAPE ) - { - out[ i ] = Q_COLOR_ESCAPE; - out[ i + 1 ] = '0' + currentcolor; - i+=2; - continue; - } - - if( linecount < maxwidth ) - { - out[ i ] = buffer[ j ]; - if( out[ i ] == '\n' ) - { - linecount = 0; - } - else if( Q_IsColorString( &buffer[j] ) ) - { - currentcolor = buffer[j+1] - '0'; - } - else - linecount++; - - //If we're at a space and getting close to a line break, look ahead and make sure that there isn't already a \n or a closer space coming. If not, break here. - if( out[ i ] == ' ' && linecount >= (maxwidth - 10 ) ) - { - qboolean foundbreak = qfalse; - for( k = i+1; k < maxwidth; k++ ) - { - if( !buffer[ k ] ) - continue; - if( buffer[ k ] == '\n' || buffer[ k ] == ' ' ) - foundbreak = qtrue; - } - if( !foundbreak ) - { - out [ i ] = '\n'; - linecount = 0; - } - } - - i++; - j++; - } - else - { - out[ i ] = '\n'; - i++; - linecount = 0; - } + in++; } - out[ i ] = '\0'; - - - strcpy( buffer, out ); + *out = '\0'; } -void G_PrivateMessage( gentity_t *ent ) +void Cmd_PrivateMessage_f( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; - int ignoreids[ MAX_CLIENTS ]; char name[ MAX_NAME_LENGTH ]; char cmd[ 12 ]; - char str[ MAX_STRING_CHARS ]; + char text[ MAX_STRING_CHARS ]; char *msg; char color; - int pcount, matches, ignored = 0; - int i; - int skipargs = 0; + int i, pcount; + int count = 0; qboolean teamonly = qfalse; - gentity_t *tmpent; + char recipients[ MAX_STRING_CHARS ] = ""; if( !g_privateMessages.integer && ent ) { ADMP( "Sorry, but private messages have been disabled\n" ); return; } - - if( g_floodMinTime.integer ) - if ( G_Flood_Limited( ent ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); - return; - } - G_SayArgv( 0, cmd, sizeof( cmd ) ); - if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) - { - skipargs = 1; - G_SayArgv( 1, cmd, sizeof( cmd ) ); - } - if( G_SayArgc( ) < 3+skipargs ) + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( trap_Argc( ) < 3 ) { ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); return; } - if( !Q_stricmp( cmd, "mt" ) || !Q_stricmp( cmd, "/mt" ) ) + if( !Q_stricmp( cmd, "mt" ) ) teamonly = qtrue; - G_SayArgv( 1+skipargs, name, sizeof( name ) ); - msg = G_SayConcatArgs( 2+skipargs ); - pcount = G_ClientNumbersFromString( name, pids ); + trap_Argv( 1, name, sizeof( name ) ); + msg = ConcatArgs( 2 ); + pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); - if( ent ) - { - int count = 0; + G_CensorString( text, msg, sizeof( text ), ent ); - for( i=0; i < pcount; i++ ) + // send the message + for( i = 0; i < pcount; i++ ) + { + if( G_SayTo( ent, &g_entities[ pids[ i ] ], + teamonly ? SAY_TPRIVMSG : SAY_PRIVMSG, text ) ) { - tmpent = &g_entities[ pids[ i ] ]; - - if( teamonly && !OnSameTeam( ent, tmpent ) ) - continue; - - // Ignore sending to invisible players - if( tmpent->client->sess.invisible == qtrue && !G_admin_permission( ent, "invisible" ) ) - continue; - - // Ignore sending to non-invisible-capable players while invisible - if( ent->client->sess.invisible == qtrue && !G_admin_permission( tmpent, "invisible" ) ) - continue; - - if( BG_ClientListTest( &tmpent->client->sess.ignoreList, - ent-g_entities ) ) - { - ignoreids[ ignored++ ] = pids[ i ]; - continue; - } - - pids[ count ] = pids[ i ]; count++; + Q_strcat( recipients, sizeof( recipients ), va( "%s" S_COLOR_WHITE ", ", + level.clients[ pids[ i ] ].pers.netname ) ); } - matches = count; - } - else - { - matches = pcount; } + // report the results color = teamonly ? COLOR_CYAN : COLOR_YELLOW; - if( !Q_stricmp( name, "console" ) ) - { - ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); - ADMP( va( "^%csent to Console.\n", color ) ); - - G_LogPrintf( "privmsg: %s^7: Console: ^6%s^7\n", - ( ent ) ? ent->client->pers.netname : "Console", msg ); - - return; - } - - Q_strncpyz( str, - va( "^%csent to %i player%s: ^7", color, matches, - ( matches == 1 ) ? "" : "s" ), - sizeof( str ) ); - - for( i=0; i < matches; i++ ) - { - tmpent = &g_entities[ pids[ i ] ]; - - if( i > 0 ) - Q_strcat( str, sizeof( str ), "^7, " ); - Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); - trap_SendServerCommand( pids[ i ], va( - "chat \"%s^%c -> ^7%s^7: (%d recipients): ^%c%s^7\" %i", - ( ent ) ? ent->client->pers.netname : "console", - color, - name, - matches, - color, - msg, - ent ? ent-g_entities : -1 ) ); - - trap_SendServerCommand( pids[ i ], va( - "cp \"^%cprivate message from ^7%s^7\"", color, - ( ent ) ? ent->client->pers.netname : "console" ) ); - } - - if( !matches ) + if( !count ) ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", name ) ); else { - if( ent ) - ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); - - ADMP( va( "%s\n", str ) ); + ADMP( va( "^%cPrivate message: ^7%s\n", color, text ) ); + // remove trailing ", " + recipients[ strlen( recipients ) - 2 ] = '\0'; + ADMP( va( "^%csent to %i player%s: " S_COLOR_WHITE "%s\n", color, count, + count == 1 ? "" : "s", recipients ) ); - G_LogPrintf( "%s: %s^7: %s^7: %s\n", - ( teamonly ) ? "tprivmsg" : "privmsg", + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\" \"%s\": ^%c%s\n", + ( teamonly ) ? "TPrivMsg" : "PrivMsg", + (int)( ( ent ) ? ent - g_entities : -1 ), ( ent ) ? ent->client->pers.netname : "console", - name, msg ); - } - - if( ignored ) - { - Q_strncpyz( str, va( "^%cignored by %i player%s: ^7", color, ignored, - ( ignored == 1 ) ? "" : "s" ), sizeof( str ) ); - for( i=0; i < ignored; i++ ) - { - tmpent = &g_entities[ ignoreids[ i ] ]; - if( i > 0 ) - Q_strcat( str, sizeof( str ), "^7, " ); - Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); - } - ADMP( va( "%s\n", str ) ); + name, color, msg ); } } - /* - ================= - Cmd_Builder_f - ================= - */ - void Cmd_Builder_f( gentity_t *ent ) - { - vec3_t forward, right, up; - vec3_t start, end; - trace_t tr; - gentity_t *traceEnt; - char bdnumbchr[21]; - - AngleVectors( ent->client->ps.viewangles, forward, right, up ); - if( ent->client->pers.teamSelection != PTE_NONE ) - CalcMuzzlePoint( ent, forward, right, up, start ); - else - VectorCopy( ent->client->ps.origin, start ); - VectorMA( start, 1000, forward, end ); - - trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; - - Com_sprintf( bdnumbchr, sizeof(bdnumbchr), "%i", traceEnt->bdnumb ); - - if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) ) - { - if( G_admin_permission( ent, "buildlog" ) ) { - trap_SendServerCommand( ent-g_entities, va( - "print \"^5/builder:^7 ^3Building:^7 %s ^3Built By:^7 %s^7 ^3Buildlog Number:^7 %s^7\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), - (traceEnt->bdnumb != -1) ? G_FindBuildLogName( traceEnt->bdnumb ) : "", - (traceEnt->bdnumb != -1) ? bdnumbchr : "none" ) ); - } - else - { - trap_SendServerCommand( ent-g_entities, va( - "print \"^5/builder:^7 ^3Building:^7 %s ^3Built By:^7 %s^7\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), - (traceEnt->bdnumb != -1) ? G_FindBuildLogName( traceEnt->bdnumb ) : "" ) ); - } - } - else - { - trap_SendServerCommand( ent-g_entities, "print \"^5/builder:^7 No structure found in your crosshair. Please face a structure and try again.\n\"" ); - } - } - -void G_CP( gentity_t *ent ) -{ - int i; - char buffer[MAX_STRING_CHARS]; - char prefixes[MAX_STRING_CHARS] = ""; - char wrappedtext[ MAX_STRING_CHARS ] = ""; - char *ptr; - char *text; - qboolean sendAliens = qtrue; - qboolean sendHumans = qtrue; - qboolean sendSpecs = qtrue; - Q_strncpyz( buffer, ConcatArgs( 1 ), sizeof( buffer ) ); - G_ParseEscapedString( buffer ); - - if( strstr( buffer, "!cp" ) ) - { - ptr = buffer; - while( *ptr != '!' ) - ptr++; - ptr+=4; - - Q_strncpyz( buffer, ptr, sizeof(buffer) ); - } - - text = buffer; - - ptr = buffer; - while( *ptr == ' ' ) - ptr++; - if( *ptr == '-' ) - { - sendAliens = qfalse; - sendHumans = qfalse; - sendSpecs = qfalse; - Q_strcat( prefixes, sizeof( prefixes ), " " ); - ptr++; - - while( *ptr && *ptr != ' ' ) - { - if( !sendAliens && ( *ptr == 'a' || *ptr == 'A' ) ) - { - sendAliens = qtrue; - Q_strcat( prefixes, sizeof( prefixes ), "[^1A^7]" ); - } - if( !sendHumans && ( *ptr == 'h' || *ptr == 'H' ) ) - { - sendHumans = qtrue; - Q_strcat( prefixes, sizeof( prefixes ), "[^4H^7]" ); - } - if( !sendSpecs && ( *ptr == 's' || *ptr == 'S' ) ) - { - sendSpecs = qtrue; - Q_strcat( prefixes, sizeof( prefixes ), "[^3S^7]" ); - } - ptr++; - } - if( *ptr ) text = ptr+1; - else text = ptr; - } - - strcpy( wrappedtext, text ); - - if( strlen( text ) == 0 ) return; - - G_WordWrap( wrappedtext, 50 ); - - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) - continue; - - if( ( !sendAliens && level.clients[ i ].pers.teamSelection == PTE_ALIENS ) || - ( !sendHumans && level.clients[ i ].pers.teamSelection == PTE_HUMANS ) || - ( !sendSpecs && level.clients[ i ].pers.teamSelection == PTE_NONE ) ) - { - if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) - { - trap_SendServerCommand( i, va("print \"^6[Admins]^7 CP to other team%s: %s \n\"", prefixes, text ) ); - } - continue; - } - - trap_SendServerCommand( i, va( "cp \"%s\"", wrappedtext ) ); - trap_SendServerCommand( i, va( "print \"%s^7 CP%s: %s\n\"", ( ent ? G_admin_adminPrintName( ent ) : "console" ), prefixes, text ) ); - } - - G_Printf( "cp: %s\n", ConcatArgs( 1 ) ); -} - /* ================= -G_IsMuted +Cmd_AdminMessage_f -Check if a player is muted +Send a message to all active admins ================= */ -qboolean G_IsMuted( gclient_t *client ) +void Cmd_AdminMessage_f( gentity_t *ent ) { - qboolean muteState = qfalse; - - //check if mute has expired - if( client->pers.muteExpires ) { - if( client->pers.muteExpires < level.time ) + // Check permissions and add the appropriate user [prefix] + if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) + { + if( !g_publicAdminMessages.integer ) + { + ADMP( "Sorry, but use of /a by non-admins has been disabled.\n" ); + return; + } + else { - client->pers.muted = qfalse; - client->pers.muteExpires = 0; + ADMP( "Your message has been sent to any available admins " + "and to the server logs.\n" ); } } - if( client->pers.muted ) - muteState = qtrue; - - return muteState; -} - -/* -================== -G_TeamKill_Repent - -Determine whether a players team kill activity is high -================== -*/ - -qboolean G_TeamKill_Repent( gentity_t *ent ) -{ - int millisSinceLastTeamKill; - - // Doesn't work if g_teamKillThreshold isn't set - if( !g_teamKillThreshold.integer || - g_teamKillThreshold.integer == 0 ) - return qfalse; - - // Doesn't work when game is paused - if( level.paused ) - return qfalse; - - millisSinceLastTeamKill = level.time - ent->client->pers.lastTeamKillTime; - if( millisSinceLastTeamKill < 30000 ) - ent->client->pers.teamKillDemerits++; - else + if( trap_Argc( ) < 2 ) { - ent->client->pers.teamKillDemerits--; - if( ent->client->pers.teamKillDemerits < 0 ) - ent->client->pers.teamKillDemerits = 0; + ADMP( "usage: a [message]\n" ); + return; } - ent->client->pers.lastTeamKillTime = level.time; - - if ( ent->client->pers.teamKillDemerits >= ( g_teamKillThreshold.integer + 2 ) ) - trap_SendConsoleCommand( 0, va( "!ban %s 30m team killing\n", ent->client->pers.ip ) ); - else if ( ent->client->pers.teamKillDemerits == ( g_teamKillThreshold.integer + 1 ) ) - trap_SendConsoleCommand( 0, va( "!warn %i team killing\n", ent->client->ps.clientNum ) ); - else if ( ent->client->pers.teamKillDemerits == g_teamKillThreshold.integer ) - G_AdminsPrintf( "Team killer %s^7 has team killed ^6%i^7 times.\n", - ent->client->pers.netname, - ent->client->pers.statscounters.teamkills ); - - return qfalse; + G_AdminMessage( ent, ConcatArgs( 1 ) ); } diff --git a/src/game/g_combat.c b/src/game/g_combat.c index 5350895..cb15147 100644 --- a/src/game/g_combat.c +++ b/src/game/g_combat.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,24 +17,24 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "g_local.h" -damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_LOCDAMAGE_REGIONS ]; +damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_DAMAGE_REGIONS ]; int g_numDamageRegions[ PCL_NUM_CLASSES ]; -armourRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_ARMOUR_REGIONS ]; +damageRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_DAMAGE_REGIONS ]; int g_numArmourRegions[ UP_NUM_UPGRADES ]; /* ============ AddScore -Adds score to both the client and his team +Adds score to the client ============ */ void AddScore( gentity_t *ent, int score ) @@ -41,6 +42,15 @@ void AddScore( gentity_t *ent, int score ) if( !ent->client ) return; + // make alien and human scores equivalent + if ( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + score = rint( ((float)score) / 2.0f ); + } + + // scale values down to fit the scoreboard better + score = rint( ((float)score) / 50.0f ); + ent->client->ps.persistant[ PERS_SCORE ] += score; CalculateRanks( ); } @@ -52,19 +62,13 @@ LookAtKiller */ void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) { - vec3_t dir; if ( attacker && attacker != self ) - VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dir ); + self->client->ps.stats[ STAT_VIEWLOCK ] = attacker - g_entities; else if( inflictor && inflictor != self ) - VectorSubtract( inflictor->s.pos.trBase, self->s.pos.trBase, dir ); + self->client->ps.stats[ STAT_VIEWLOCK ] = inflictor - g_entities; else - { - self->client->ps.stats[ STAT_VIEWLOCK ] = self->s.angles[ YAW ]; - return; - } - - self->client->ps.stats[ STAT_VIEWLOCK ] = vectoyaw( dir ); + self->client->ps.stats[ STAT_VIEWLOCK ] = self - g_entities; } // these are just for logging, the client prints its own messages @@ -104,7 +108,8 @@ char *modNames[ ] = "MOD_LEVEL2_CLAW", "MOD_LEVEL2_ZAP", "MOD_LEVEL4_CLAW", - "MOD_LEVEL4_CHARGE", + "MOD_LEVEL4_TRAMPLE", + "MOD_LEVEL4_CRUSH", "MOD_SLOWBLOB", "MOD_POISON", @@ -118,9 +123,110 @@ char *modNames[ ] = "MOD_ASPAWN", "MOD_ATUBE", "MOD_OVERMIND", - "MOD_SLAP" + "MOD_DECONSTRUCT", + "MOD_REPLACE", + "MOD_NOCREEP" }; +/* +================== +G_RewardAttackers + +Function to distribute rewards to entities that killed this one. +Returns the total damage dealt. +================== +*/ +float G_RewardAttackers( gentity_t *self ) +{ + float value, totalDamage = 0; + int team, i, maxHealth = 0; + int alienCredits = 0, humanCredits = 0; + gentity_t *player; + + // Total up all the damage done by non-teammates + for( i = 0; i < level.maxclients; i++ ) + { + player = g_entities + i; + + if( !OnSameTeam( self, player ) || + self->buildableTeam != player->client->ps.stats[ STAT_TEAM ] ) + totalDamage += (float)self->credits[ i ]; + } + + if( totalDamage <= 0.0f ) + return 0.0f; + + // Only give credits for killing players and buildables + if( self->client ) + { + value = BG_GetValueOfPlayer( &self->client->ps ); + team = self->client->pers.teamSelection; + maxHealth = self->client->ps.stats[ STAT_MAX_HEALTH ]; + } + else if( self->s.eType == ET_BUILDABLE ) + { + value = BG_Buildable( self->s.modelindex )->value; + + // only give partial credits for a buildable not yet completed + if( !self->spawned ) + { + value *= (float)( level.time - self->buildTime ) / + BG_Buildable( self->s.modelindex )->buildTime; + } + + team = self->buildableTeam; + maxHealth = BG_Buildable( self->s.modelindex )->health; + } + else + return totalDamage; + + // Give credits and empty the array + for( i = 0; i < level.maxclients; i++ ) + { + int stageValue = value * self->credits[ i ] / totalDamage; + player = g_entities + i; + + if( player->client->pers.teamSelection != team ) + { + if( totalDamage < maxHealth ) + stageValue *= totalDamage / maxHealth; + + if( !self->credits[ i ] || player->client->ps.stats[ STAT_TEAM ] == team ) + continue; + + AddScore( player, stageValue ); + + // killing buildables earns score, but not credits + if( self->s.eType != ET_BUILDABLE ) + { + G_AddCreditToClient( player->client, stageValue, qtrue ); + + // add to stage counters + if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + alienCredits += stageValue; + else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + humanCredits += stageValue; + } + } + self->credits[ i ] = 0; + } + + if( alienCredits ) + { + trap_Cvar_Set( "g_alienCredits", + va( "%d", g_alienCredits.integer + alienCredits ) ); + trap_Cvar_Update( &g_alienCredits ); + } + if( humanCredits ) + { + trap_Cvar_Set( "g_humanCredits", + va( "%d", g_humanCredits.integer + humanCredits ) ); + trap_Cvar_Update( &g_humanCredits ); + } + + return totalDamage; +} + /* ================== player_die @@ -131,18 +237,11 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int gentity_t *ent; int anim; int killer; - int i, j; - char *killerName, *obit; - float totalTK = 0; - float totalDamage = 0.0f; - float percentDamage = 0.0f; - gentity_t *player; - qboolean tk = qfalse; - + int i; + const char *killerName, *obit; if( self->client->ps.pm_type == PM_DEAD ) return; - if( level.intermissiontime ) return; @@ -155,27 +254,9 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int killer = attacker->s.number; if( attacker->client ) - { killerName = attacker->client->pers.netname; - tk = ( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ] - == self->client->ps.stats[ STAT_PTEAM ] ); - - if( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ] == self->client->ps.stats[ STAT_PTEAM ] ) - { - attacker->client->pers.statscounters.teamkills++; - if( attacker->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.teamkills++; - } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.teamkills++; - } - } - - } else - killerName = ""; + killerName = ""; } else { @@ -183,395 +264,62 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int killerName = ""; } - if( killer < 0 || killer >= MAX_CLIENTS ) - { - killer = ENTITYNUM_WORLD; - killerName = ""; - } - - if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) - obit = ""; + if( meansOfDeath < 0 || meansOfDeath >= ARRAY_LEN( modNames ) ) + // fall back on the number + obit = va( "%d", meansOfDeath ); else obit = modNames[ meansOfDeath ]; - G_LogPrintf("Kill: %i %i %i: %s^7 killed %s^7 by %s\n", - killer, self->s.number, meansOfDeath, killerName, - self->client->pers.netname, obit ); + G_LogPrintf( "Die: %d %d %s: %s" S_COLOR_WHITE " killed %s\n", + killer, + (int)( self - g_entities ), + obit, + killerName, + self->client->pers.netname ); - //TA: deactivate all upgrades + // deactivate all upgrades for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) BG_DeactivateUpgrade( i, self->client->ps.stats ); - if( meansOfDeath == MOD_SLAP ) - { - trap_SendServerCommand( -1, - va( "print \"%s^7 felt %s^7's authority\n\"", - self->client->pers.netname, killerName ) ); - goto finish_dying; - } - // broadcast the death event to everyone - if( !tk ) - { - ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); - ent->s.eventParm = meansOfDeath; - ent->s.otherEntityNum = self->s.number; - ent->s.otherEntityNum2 = killer; - ent->r.svFlags = SVF_BROADCAST; // send to everyone - } - else if( attacker && attacker->client ) - { - // tjw: obviously this is a hack and belongs in the client, but - // this works as a temporary fix. - trap_SendServerCommand( -1, - va( "print \"%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n\"", - self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ) ); - trap_SendServerCommand( attacker - g_entities, - va( "cp \"You killed ^1TEAMMATE^7 %s\"", self->client->pers.netname ) ); - G_LogOnlyPrintf("%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n", - self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ); - G_TeamKill_Repent( attacker ); - } + ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone self->enemy = attacker; - self->client->ps.persistant[ PERS_KILLED ]++; - self->client->pers.statscounters.deaths++; - if( self->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.deaths++; - } - else if( self->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.deaths++; - } if( attacker && attacker->client ) { attacker->client->lastkilled_client = self->s.number; - if( g_killerHP.integer || - ( g_devmapKillerHP.integer && g_cheats.integer ) ) - { - trap_SendServerCommand( self-g_entities, - va( "print \"Your killer, %s^7, had %3i HP.\n\"", - killerName, attacker->health ) ); - } - - if( attacker == self || OnSameTeam( self, attacker ) ) - { - AddScore( attacker, -1 ); - - // Normal teamkill penalty - if( !g_retribution.integer ) - { - if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - G_AddCreditToClient( attacker->client, -FREEKILL_ALIEN, qtrue ); - else if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - G_AddCreditToClient( attacker->client, -FREEKILL_HUMAN, qtrue ); - } - } - else - { - AddScore( attacker, 1 ); - - if( g_gradualFreeFunds.integer < 2 ) - attacker->client->pers.lastFreekillTime = level.time; - attacker->client->pers.statscounters.kills++; - if( attacker->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.kills++; - } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.kills++; - } - } - - if( attacker == self ) + if( ( attacker == self || OnSameTeam( self, attacker ) ) && meansOfDeath != MOD_HSPAWN ) { - attacker->client->pers.statscounters.suicides++; - if( attacker->client->pers.teamSelection == PTE_ALIENS ) + //punish team kills and suicides + if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - level.alienStatsCounters.suicides++; + G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, qtrue ); + AddScore( attacker, -ALIEN_TK_SUICIDE_PENALTY ); } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) + else if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - level.humanStatsCounters.suicides++; + G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, qtrue ); + AddScore( attacker, -HUMAN_TK_SUICIDE_PENALTY ); } } } else if( attacker->s.eType != ET_BUILDABLE ) - AddScore( self, -1 ); - - //total up all the damage done by every client - for( i = 0; i < MAX_CLIENTS; i++ ) { - totalDamage += (float)self->credits[ i ]; - totalTK += (float)self->client->tkcredits[ i ]; + if( self->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + AddScore( self, -ALIEN_TK_SUICIDE_PENALTY ); + else if( self->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + AddScore( self, -HUMAN_TK_SUICIDE_PENALTY ); } - // punish players for damaging teammates - if ( g_retribution.integer && totalTK ) - { - int totalPrice; - int max = HUMAN_MAX_CREDITS; - - if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - totalPrice = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, self->client->ps.stats[ STAT_PCLASS ], ALIEN_MAX_KILLS, 0 ); - max = ALIEN_MAX_KILLS; - } - else - { - totalPrice = BG_GetValueOfEquipment( &self->client->ps ); - } - - if ( self->client->ps.persistant[ PERS_CREDIT ] + totalPrice > max ) - totalPrice = max - self->client->ps.persistant[ PERS_CREDIT ]; - if ( totalPrice > 0 ) - { - totalTK += totalDamage; - if( totalTK < self->client->ps.stats[ STAT_MAX_HEALTH ] ) - totalTK = self->client->ps.stats[ STAT_MAX_HEALTH ]; - - if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - int price; - // no retribution if self damage or enemy damage or building damage or no damage from this client - if ( i == self - g_entities || !g_entities[ i ].client || - !OnSameTeam( &g_entities[ i ], self ) || - !self->client->tkcredits[ i ] ) - continue; - - // calculate retribution price (rounded up) - price = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK + 0.5f; - self->client->tkcredits[ i ] = 0; - - // check for enough credits - if ( g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] < price ) - price = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ]; - if ( price ) - { - G_AddCreditToClient( self->client, price, qtrue ); - G_AddCreditToClient( g_entities[ i ].client, -price, qtrue ); - - trap_SendServerCommand( self->client->ps.clientNum, - va( "print \"Received ^3%d credits ^7from %s ^7in retribution.\n\"", - price, g_entities[ i ].client->pers.netname ) ); - trap_SendServerCommand( g_entities[ i ].client->ps.clientNum, - va( "print \"Transfered ^3%d credits ^7to %s ^7in retribution.\n\"", - price, self->client->pers.netname ) ); - } - } - } - else - { - int toPay[ MAX_CLIENTS ] = { 0 }; - int frags = totalPrice; - int damageForEvo = totalTK / totalPrice; - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - // no retribution if self damage or enemy damage or building damage or no damage from this client - if ( i == self - g_entities || !g_entities[ i ].client || - !OnSameTeam( &g_entities[ i ], self ) || - !self->client->tkcredits[ i ] ) - continue; - - // find out how many full evos this client needs to pay - toPay[ i ] = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK; - if ( toPay[ i ] > g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] ) - toPay[ i ] = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ]; - frags -= toPay[ i ]; - self->client->tkcredits[ i ] -= damageForEvo * toPay[ i ]; - } - - // if we have not met the evo count, continue stealing evos - while ( 1 ) - { - int maximum = 0; - int topClient = 0; - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - if ( self->client->tkcredits[ i ] > maximum && g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] ) - { - maximum = self->client->tkcredits[ i ]; - topClient = i; - } - } - if ( !maximum ) - break; - toPay[ topClient ]++; - self->client->tkcredits[ topClient ] = 0; - frags--; - if ( !frags ) - break; - } - - // now move the evos around - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - if ( !toPay[ i ] ) - continue; - - G_AddCreditToClient( self->client, toPay[ i ], qtrue ); - G_AddCreditToClient( g_entities[ i ].client, -toPay[ i ], qtrue ); - - trap_SendServerCommand( self->client->ps.clientNum, - va( "print \"Received ^3%d ^7evos from %s ^7in retribution.\n\"", - toPay[ i ], g_entities[ i ].client->pers.netname ) ); - trap_SendServerCommand( g_entities[ i ].client->ps.clientNum, - va( "print \"Transfered ^3%d ^7evos to %s ^7in retribution.\n\"", - toPay[ i ], self->client->pers.netname ) ); - } - } - } - } - - // if players did more than DAMAGE_FRACTION_FOR_KILL increment the stage counters - if( !OnSameTeam( self, attacker ) && totalDamage >= ( self->client->ps.stats[ STAT_MAX_HEALTH ] * DAMAGE_FRACTION_FOR_KILL ) ) - { - if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - trap_Cvar_Set( "g_alienKills", va( "%d", g_alienKills.integer + 1 ) ); - if( g_alienStage.integer < 2 ) - { - self->client->pers.statscounters.feeds++; - level.humanStatsCounters.feeds++; - } - } - else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - trap_Cvar_Set( "g_humanKills", va( "%d", g_humanKills.integer + 1 ) ); - if( g_humanStage.integer < 2 ) - { - self->client->pers.statscounters.feeds++; - level.alienStatsCounters.feeds++; - } - } - } - - if( totalDamage > 0.0f ) - { - if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - //nice simple happy bouncy human land - float classValue = BG_FindValueOfClass( self->client->ps.stats[ STAT_PCLASS ] ); - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - player = g_entities + i; - - if( !player->client ) - continue; - - if( player->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) - continue; - - if( !self->credits[ i ] ) - continue; - - percentDamage = (float)self->credits[ i ] / totalDamage; - if( percentDamage > 0 && percentDamage < 1) - { - player->client->pers.statscounters.assists++; - level.humanStatsCounters.assists++; - } - - //add credit - G_AddCreditToClient( player->client, - (int)( classValue * percentDamage ), qtrue ); - } - } - else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - //horribly complex nasty alien land - float humanValue = BG_GetValueOfHuman( &self->client->ps ); - int frags; - int unclaimedFrags = (int)humanValue; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - player = g_entities + i; - - if( !player->client ) - continue; - - if( player->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) - continue; - - //this client did no damage - if( !self->credits[ i ] ) - continue; - - //nothing left to claim - if( !unclaimedFrags ) - break; - - percentDamage = (float)self->credits[ i ] / totalDamage; - if( percentDamage > 0 && percentDamage < 1) - { - player->client->pers.statscounters.assists++; - level.alienStatsCounters.assists++; - } - - frags = (int)floor( humanValue * percentDamage); - - if( frags > 0 ) - { - //add kills - G_AddCreditToClient( player->client, frags, qtrue ); - - //can't revist this account later - self->credits[ i ] = 0; - - //reduce frags left to be claimed - unclaimedFrags -= frags; - } - } - - //there are frags still to be claimed - if( unclaimedFrags ) - { - //the clients remaining at this point do not - //have enough credit to claim even one frag - //so simply give the top clients - //a frag each - - for( i = 0; i < unclaimedFrags; i++ ) - { - int maximum = 0; - int topClient = 0; - - for( j = 0; j < MAX_CLIENTS; j++ ) - { - //this client did no damage - if( !self->credits[ j ] ) - continue; - - if( self->credits[ j ] > maximum ) - { - maximum = self->credits[ j ]; - topClient = j; - } - } - - if( maximum > 0 ) - { - player = g_entities + topClient; - - //add kills - G_AddCreditToClient( player->client, 1, qtrue ); - - //can't revist this account again - self->credits[ topClient ] = 0; - } - } - } - } - } + // give credits for killing this player + G_RewardAttackers( self ); ScoreboardMessage( self ); // show scores @@ -585,29 +333,28 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if( client->pers.connected != CON_CONNECTED ) continue; - if( client->sess.sessionTeam != TEAM_SPECTATOR ) + if( client->sess.spectatorState == SPECTATOR_NOT ) continue; if( client->sess.spectatorClient == self->s.number ) ScoreboardMessage( g_entities + i ); } -finish_dying: // from MOD_SLAP - - VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); + VectorCopy( self->r.currentOrigin, self->client->pers.lastDeathLocation ); self->takedamage = qfalse; // can still be gibbed self->s.weapon = WP_NONE; - self->r.contents = CONTENTS_CORPSE; + if( self->client->noclip ) + self->client->cliprcontents = CONTENTS_CORPSE; + else + self->r.contents = CONTENTS_CORPSE; - self->s.angles[ PITCH ] = 0; - self->s.angles[ ROLL ] = 0; - self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; + self->client->ps.viewangles[ PITCH ] = 0; // zomg + self->client->ps.viewangles[ YAW ] = self->s.apos.trBase[ YAW ]; + self->client->ps.viewangles[ ROLL ] = 0; LookAtKiller( self, inflictor, attacker ); - VectorCopy( self->s.angles, self->client->ps.viewangles ); - self->s.loopSound = 0; self->r.maxs[ 2 ] = -8; @@ -674,57 +421,55 @@ finish_dying: // from MOD_SLAP } trap_LinkEntity( self ); -} - -////////TA: locdamage + self->client->pers.infoChangeTime = level.time; +} /* =============== -G_ParseArmourScript +G_ParseDmgScript =============== */ -void G_ParseArmourScript( char *buf, int upgrade ) +static int G_ParseDmgScript( damageRegion_t *regions, char *buf ) { char *token; + float angleSpan, heightSpan; int count; - count = 0; - - while( 1 ) + for( count = 0; ; count++ ) { token = COM_Parse( &buf ); - - if( !token[0] ) + if( !token[ 0 ] ) break; if( strcmp( token, "{" ) ) { - G_Printf( "Missing { in armour file\n" ); + COM_ParseError( "Missing {" ); break; } - if( count == MAX_ARMOUR_REGIONS ) + if( count >= MAX_DAMAGE_REGIONS ) { - G_Printf( "Max armour regions exceeded in locdamage file\n" ); + COM_ParseError( "Max damage regions exceeded" ); break; } - //default - g_armourRegions[ upgrade ][ count ].minHeight = 0.0; - g_armourRegions[ upgrade ][ count ].maxHeight = 1.0; - g_armourRegions[ upgrade ][ count ].minAngle = 0; - g_armourRegions[ upgrade ][ count ].maxAngle = 360; - g_armourRegions[ upgrade ][ count ].modifier = 1.0; - g_armourRegions[ upgrade ][ count ].crouch = qfalse; + // defaults + regions[ count ].name[ 0 ] = '\0'; + regions[ count ].minHeight = 0.0f; + regions[ count ].maxHeight = 1.0f; + regions[ count ].minAngle = 0.0f; + regions[ count ].maxAngle = 360.0f; + regions[ count ].modifier = 1.0f; + regions[ count ].crouch = qfalse; while( 1 ) { token = COM_ParseExt( &buf, qtrue ); - if( !token[0] ) + if( !token[ 0 ] ) { - G_Printf( "Unexpected end of armour file\n" ); + COM_ParseError( "Unexpected end of file" ); break; } @@ -732,172 +477,260 @@ void G_ParseArmourScript( char *buf, int upgrade ) { break; } + else if( !strcmp( token, "name" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( token[ 0 ] ) + Q_strncpyz( regions[ count ].name, token, + sizeof( regions[ count ].name ) ); + } else if( !strcmp( token, "minHeight" ) ) { token = COM_ParseExt( &buf, qfalse ); - - if ( !token[0] ) + if( !token[ 0 ] ) strcpy( token, "0" ); - - g_armourRegions[ upgrade ][ count ].minHeight = atof( token ); + regions[ count ].minHeight = atof( token ); } else if( !strcmp( token, "maxHeight" ) ) { token = COM_ParseExt( &buf, qfalse ); - - if ( !token[0] ) + if( !token[ 0 ] ) strcpy( token, "100" ); - - g_armourRegions[ upgrade ][ count ].maxHeight = atof( token ); + regions[ count ].maxHeight = atof( token ); } else if( !strcmp( token, "minAngle" ) ) { token = COM_ParseExt( &buf, qfalse ); - - if ( !token[0] ) + if( !token[ 0 ] ) strcpy( token, "0" ); - - g_armourRegions[ upgrade ][ count ].minAngle = atoi( token ); + regions[ count ].minAngle = atoi( token ); } else if( !strcmp( token, "maxAngle" ) ) { token = COM_ParseExt( &buf, qfalse ); - - if ( !token[0] ) + if( !token[ 0 ] ) strcpy( token, "360" ); - - g_armourRegions[ upgrade ][ count ].maxAngle = atoi( token ); + regions[ count ].maxAngle = atoi( token ); } else if( !strcmp( token, "modifier" ) ) { token = COM_ParseExt( &buf, qfalse ); - - if ( !token[0] ) + if( !token[ 0 ] ) strcpy( token, "1.0" ); - - g_armourRegions[ upgrade ][ count ].modifier = atof( token ); + regions[ count ].modifier = atof( token ); } else if( !strcmp( token, "crouch" ) ) { - g_armourRegions[ upgrade ][ count ].crouch = qtrue; + regions[ count ].crouch = qtrue; + } + else + { + COM_ParseWarning("Unknown token \"%s\"", token); } } - - g_numArmourRegions[ upgrade ]++; - count++; + + // Angle portion covered + angleSpan = regions[ count ].maxAngle - regions[ count ].minAngle; + if( angleSpan < 0.0f ) + angleSpan += 360.0f; + angleSpan /= 360.0f; + + // Height portion covered + heightSpan = regions[ count ].maxHeight - regions[ count ].minHeight; + if( heightSpan < 0.0f ) + heightSpan = -heightSpan; + if( heightSpan > 1.0f ) + heightSpan = 1.0f; + + regions[ count ].area = angleSpan * heightSpan; + if( !regions[ count ].area ) + regions[ count ].area = 0.00001f; } + + return count; } - /* -=============== -G_ParseDmgScript -=============== +============ +GetRegionDamageModifier +============ */ -void G_ParseDmgScript( char *buf, int class ) +static float GetRegionDamageModifier( gentity_t *targ, int class, int piece ) { - char *token; - int count; - - count = 0; - - while( 1 ) + damageRegion_t *regions, *overlap; + float modifier = 0.0f, areaSum = 0.0f; + int j, i; + qboolean crouch; + + crouch = targ->client->ps.pm_flags & PMF_DUCKED; + overlap = &g_damageRegions[ class ][ piece ]; + + if( g_debugDamage.integer > 2 ) + G_Printf( "GetRegionDamageModifier():\n" + ". bodyRegion = [%d %d %f %f] (%s)\n" + ". modifier = %f\n", + overlap->minAngle, overlap->maxAngle, + overlap->minHeight, overlap->maxHeight, + overlap->name, overlap->modifier ); + + // Find the armour layer modifier, assuming that none of the armour regions + // overlap and that any areas that are not covered have a modifier of 1.0 + for( j = UP_NONE + 1; j < UP_NUM_UPGRADES; j++ ) { - token = COM_Parse( &buf ); - - if( !token[0] ) - break; - - if( strcmp( token, "{" ) ) - { - G_Printf( "Missing { in locdamage file\n" ); - break; - } - - if( count == MAX_LOCDAMAGE_REGIONS ) - { - G_Printf( "Max damage regions exceeded in locdamage file\n" ); - break; - } - - //default - g_damageRegions[ class ][ count ].minHeight = 0.0; - g_damageRegions[ class ][ count ].maxHeight = 1.0; - g_damageRegions[ class ][ count ].minAngle = 0; - g_damageRegions[ class ][ count ].maxAngle = 360; - g_damageRegions[ class ][ count ].modifier = 1.0; - g_damageRegions[ class ][ count ].crouch = qfalse; - - while( 1 ) + if( !BG_InventoryContainsUpgrade( j, targ->client->ps.stats ) || + !g_numArmourRegions[ j ] ) + continue; + regions = g_armourRegions[ j ]; + + for( i = 0; i < g_numArmourRegions[ j ]; i++ ) { - token = COM_ParseExt( &buf, qtrue ); - - if( !token[0] ) + float overlapMaxA, regionMinA, regionMaxA, angleSpan, heightSpan, area; + + if( regions[ i ].crouch != crouch ) + continue; + + // Convert overlap angle to 0 to max + overlapMaxA = overlap->maxAngle - overlap->minAngle; + if( overlapMaxA < 0.0f ) + overlapMaxA += 360.0f; + + // Convert region angles to match overlap + regionMinA = regions[ i ].minAngle - overlap->minAngle; + if( regionMinA < 0.0f ) + regionMinA += 360.0f; + regionMaxA = regions[ i ].maxAngle - overlap->minAngle; + if( regionMaxA < 0.0f ) + regionMaxA += 360.0f; + + // Overlapping Angle portion + if( regionMinA <= regionMaxA ) { - G_Printf( "Unexpected end of locdamage file\n" ); - break; + angleSpan = 0.0f; + if( regionMinA < overlapMaxA ) + { + if( regionMaxA > overlapMaxA ) + regionMaxA = overlapMaxA; + angleSpan = regionMaxA - regionMinA; + } } - - if( !Q_stricmp( token, "}" ) ) + else { - break; + if( regionMaxA > overlapMaxA ) + regionMaxA = overlapMaxA; + angleSpan = regionMaxA; + if( regionMinA < overlapMaxA ) + angleSpan += overlapMaxA - regionMinA; } - else if( !strcmp( token, "minHeight" ) ) - { - token = COM_ParseExt( &buf, qfalse ); + angleSpan /= 360.0f; + + // Overlapping height portion + heightSpan = MIN( overlap->maxHeight, regions[ i ].maxHeight ) - + MAX( overlap->minHeight, regions[ i ].minHeight ); + if( heightSpan < 0.0f ) + heightSpan = 0.0f; + if( heightSpan > 1.0f ) + heightSpan = 1.0f; + + if( g_debugDamage.integer > 2 ) + G_Printf( ". armourRegion = [%d %d %f %f] (%s)\n" + ". . modifier = %f\n" + ". . angleSpan = %f\n" + ". . heightSpan = %f\n", + regions[ i ].minAngle, regions[ i ].maxAngle, + regions[ i ].minHeight, regions[ i ].maxHeight, + regions[ i ].name, regions[ i ].modifier, + angleSpan, heightSpan ); + + areaSum += area = angleSpan * heightSpan; + modifier += regions[ i ].modifier * area; + } + } - if ( !token[0] ) - strcpy( token, "0" ); + if( g_debugDamage.integer > 2 ) + G_Printf( ". areaSum = %f\n" + ". armourModifier = %f\n", areaSum, modifier ); - g_damageRegions[ class ][ count ].minHeight = atof( token ); - } - else if( !strcmp( token, "maxHeight" ) ) - { - token = COM_ParseExt( &buf, qfalse ); + return overlap->modifier * ( overlap->area + modifier - areaSum ); +} - if ( !token[0] ) - strcpy( token, "100" ); +/* +============ +GetNonLocDamageModifier +============ +*/ +static float GetNonLocDamageModifier( gentity_t *targ, int class ) +{ + float modifier = 0.0f, area = 0.0f, scale = 0.0f; + int i; + qboolean crouch; + + // For every body region, use stretch-armor formula to apply armour modifier + // for any overlapping area that armour shares with the body region + crouch = targ->client->ps.pm_flags & PMF_DUCKED; + for( i = 0; i < g_numDamageRegions[ class ]; i++ ) + { + damageRegion_t *region; - g_damageRegions[ class ][ count ].maxHeight = atof( token ); - } - else if( !strcmp( token, "minAngle" ) ) - { - token = COM_ParseExt( &buf, qfalse ); + region = &g_damageRegions[ class ][ i ]; - if ( !token[0] ) - strcpy( token, "0" ); + if( region->crouch != crouch ) + continue; - g_damageRegions[ class ][ count ].minAngle = atoi( token ); - } - else if( !strcmp( token, "maxAngle" ) ) - { - token = COM_ParseExt( &buf, qfalse ); + modifier += GetRegionDamageModifier( targ, class, i ); - if ( !token[0] ) - strcpy( token, "360" ); + scale += region->modifier * region->area; + area += region->area; - g_damageRegions[ class ][ count ].maxAngle = atoi( token ); - } - else if( !strcmp( token, "modifier" ) ) - { - token = COM_ParseExt( &buf, qfalse ); + } - if ( !token[0] ) - strcpy( token, "1.0" ); + modifier = !scale ? 1.0f : 1.0f + ( modifier / scale - 1.0f ) * area; + + if( g_debugDamage.integer > 1 ) + G_Printf( "GetNonLocDamageModifier() modifier:%f, area:%f, scale:%f\n", + modifier, area, scale ); + + return modifier; +} - g_damageRegions[ class ][ count ].modifier = atof( token ); - } - else if( !strcmp( token, "crouch" ) ) - { - g_damageRegions[ class ][ count ].crouch = qtrue; - } - } +/* +============ +GetPointDamageModifier + +Returns the damage region given an angle and a height proportion +============ +*/ +static float GetPointDamageModifier( gentity_t *targ, damageRegion_t *regions, + int len, float angle, float height ) +{ + float modifier = 1.0f; + int i; - g_numDamageRegions[ class ]++; - count++; + for( i = 0; i < len; i++ ) + { + if( regions[ i ].crouch != ( targ->client->ps.pm_flags & PMF_DUCKED ) ) + continue; + + // Angle must be within range + if( ( regions[ i ].minAngle <= regions[ i ].maxAngle && + ( angle < regions[ i ].minAngle || + angle > regions[ i ].maxAngle ) ) || + ( regions[ i ].minAngle > regions[ i ].maxAngle && + angle > regions[ i ].maxAngle && angle < regions[ i ].minAngle ) ) + continue; + + // Height must be within range + if( height < regions[ i ].minHeight || height > regions[ i ].maxHeight ) + continue; + + modifier *= regions[ i ].modifier; } -} + if( g_debugDamage.integer ) + G_Printf( "GetDamageRegionModifier(angle = %f, height = %f): %f\n", + angle, height, modifier ); + + return modifier; +} /* ============ @@ -906,34 +739,33 @@ G_CalcDamageModifier */ static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags ) { - vec3_t targOrigin; - vec3_t bulletPath; - vec3_t bulletAngle; - vec3_t pMINUSfloor, floor, normal; - - float clientHeight, hitRelative, hitRatio; - int bulletRotation, clientRotation, hitRotation; - float modifier = 1.0f; - int i, j; + vec3_t targOrigin, bulletPath, bulletAngle, pMINUSfloor, floor, normal; + float clientHeight, hitRelative, hitRatio, modifier; + int hitRotation, i; if( point == NULL ) return 1.0f; + // Don't need to calculate angles and height for non-locational damage + if( dflags & DAMAGE_NO_LOCDAMAGE ) + return GetNonLocDamageModifier( targ, class ); + + // Get the point location relative to the floor under the target if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used ) VectorCopy( targ->client->unlaggedCalc.origin, targOrigin ); else VectorCopy( targ->r.currentOrigin, targOrigin ); - clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; - - if( targ->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - VectorCopy( targ->client->ps.grapplePoint, normal ); - else - VectorSet( normal, 0, 0, 1 ); - + BG_GetClientNormal( &targ->client->ps, normal ); VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor ); VectorSubtract( point, floor, pMINUSfloor ); + // Get the proportion of the target height where the hit landed + clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; + + if( !clientHeight ) + clientHeight = 1.0f; + hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal ); if( hitRelative < 0.0f ) @@ -944,105 +776,25 @@ static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *att hitRatio = hitRelative / clientHeight; - VectorSubtract( targOrigin, point, bulletPath ); + // Get the yaw of the attack relative to the target's view yaw + VectorSubtract( point, targOrigin, bulletPath ); vectoangles( bulletPath, bulletAngle ); - clientRotation = targ->client->ps.viewangles[ YAW ]; - bulletRotation = bulletAngle[ YAW ]; - - hitRotation = abs( clientRotation - bulletRotation ); + hitRotation = AngleNormalize360( targ->client->ps.viewangles[ YAW ] - + bulletAngle[ YAW ] ); - hitRotation = hitRotation % 360; // Keep it in the 0-359 range + // Get modifiers from the target's damage regions + modifier = GetPointDamageModifier( targ, g_damageRegions[ class ], + g_numDamageRegions[ class ], + hitRotation, hitRatio ); - if( dflags & DAMAGE_NO_LOCDAMAGE ) + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) { - float totalModifier = 0.0f; - float averageModifier = 1.0f; - - //average all of this upgrade's armour regions together - if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) - { - for( j = 0; j < g_numArmourRegions[ i ]; j++ ) - totalModifier += g_armourRegions[ i ][ j ].modifier; - - if( g_numArmourRegions[ i ] ) - averageModifier = totalModifier / g_numArmourRegions[ i ]; - else - averageModifier = 1.0f; - } - - modifier *= averageModifier; - } - } - else - { - if( attacker && attacker->client ) - { - attacker->client->pers.statscounters.hitslocational++; - level.alienStatsCounters.hitslocational++; - } - for( i = 0; i < g_numDamageRegions[ class ]; i++ ) - { - qboolean rotationBound; - - if( g_damageRegions[ class ][ i ].minAngle > - g_damageRegions[ class ][ i ].maxAngle ) - { - rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle && - hitRotation <= 360 ) || ( hitRotation >= 0 && - hitRotation <= g_damageRegions[ class ][ i ].maxAngle ); - } - else - { - rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle && - hitRotation <= g_damageRegions[ class ][ i ].maxAngle ); - } - - if( rotationBound && - hitRatio >= g_damageRegions[ class ][ i ].minHeight && - hitRatio <= g_damageRegions[ class ][ i ].maxHeight && - ( g_damageRegions[ class ][ i ].crouch == - ( targ->client->ps.pm_flags & PMF_DUCKED ) ) ) - modifier *= g_damageRegions[ class ][ i ].modifier; - } - - if( attacker && attacker->client && modifier == 2 ) - { - attacker->client->pers.statscounters.headshots++; - level.alienStatsCounters.headshots++; - } - - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) - { - for( j = 0; j < g_numArmourRegions[ i ]; j++ ) - { - qboolean rotationBound; - - if( g_armourRegions[ i ][ j ].minAngle > - g_armourRegions[ i ][ j ].maxAngle ) - { - rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle && - hitRotation <= 360 ) || ( hitRotation >= 0 && - hitRotation <= g_armourRegions[ i ][ j ].maxAngle ); - } - else - { - rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle && - hitRotation <= g_armourRegions[ i ][ j ].maxAngle ); - } - - if( rotationBound && - hitRatio >= g_armourRegions[ i ][ j ].minHeight && - hitRatio <= g_armourRegions[ i ][ j ].maxHeight && - ( g_armourRegions[ i ][ j ].crouch == - ( targ->client->ps.pm_flags & PMF_DUCKED ) ) ) - modifier *= g_armourRegions[ i ][ j ].modifier; - } - } + modifier *= GetPointDamageModifier( targ, g_armourRegions[ i ], + g_numArmourRegions[ i ], + hitRotation, hitRatio ); } } @@ -1062,37 +814,41 @@ void G_InitDamageLocations( void ) int i; int len; fileHandle_t fileHandle; - char buffer[ MAX_LOCDAMAGE_TEXT ]; + char buffer[ MAX_DAMAGE_REGION_TEXT ]; for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) { - modelName = BG_FindModelNameForClass( i ); - Com_sprintf( filename, sizeof( filename ), "models/players/%s/locdamage.cfg", modelName ); + modelName = BG_ClassConfig( i )->modelName; + Com_sprintf( filename, sizeof( filename ), + "models/players/%s/locdamage.cfg", modelName ); len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); if ( !fileHandle ) { - G_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + G_Printf( S_COLOR_RED "file not found: %s\n", filename ); continue; } - if( len >= MAX_LOCDAMAGE_TEXT ) + if( len >= MAX_DAMAGE_REGION_TEXT ) { - G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) ); + G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", + filename, len, MAX_DAMAGE_REGION_TEXT ); trap_FS_FCloseFile( fileHandle ); continue; } + COM_BeginParseSession( filename ); + trap_FS_Read( buffer, len, fileHandle ); buffer[len] = 0; trap_FS_FCloseFile( fileHandle ); - G_ParseDmgScript( buffer, i ); + g_numDamageRegions[ i ] = G_ParseDmgScript( g_damageRegions[ i ], buffer ); } for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { - modelName = BG_FindNameForUpgrade( i ); + modelName = BG_Upgrade( i )->name; Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName ); len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); @@ -1101,23 +857,24 @@ void G_InitDamageLocations( void ) if ( !fileHandle ) continue; - if( len >= MAX_LOCDAMAGE_TEXT ) + if( len >= MAX_DAMAGE_REGION_TEXT ) { - G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) ); + G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", + filename, len, MAX_DAMAGE_REGION_TEXT ); trap_FS_FCloseFile( fileHandle ); continue; } + COM_BeginParseSession( filename ); + trap_FS_Read( buffer, len, fileHandle ); buffer[len] = 0; trap_FS_FCloseFile( fileHandle ); - G_ParseArmourScript( buffer, i ); + g_numArmourRegions[ i ] = G_ParseDmgScript( g_armourRegions[ i ], buffer ); } } -////////TA: locdamage - /* ============ @@ -1138,16 +895,16 @@ inflictor, attacker, dir, and point can be NULL for environmental effects 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_KNOCKBACK affect velocity, not just view angles - DAMAGE_NO_PROTECTION kills godmode, armor, everything + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_NO_PROTECTION kills everything except godmode ============ */ -//TA: team is the team that is immune to this damage +// team is the team that is immune to this damage void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team ) { - if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] ) ) + if( targ->client && ( team != targ->client->ps.stats[ STAT_TEAM ] ) ) G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod ); } @@ -1156,18 +913,11 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, { gclient_t *client; int take; - int save; int asave = 0; - int knockback = 0; - float damagemodifier=0.0; - int takeNoOverkill; - - if( !targ->takedamage ) - return; + int knockback; - // the intermission has allready been qualified for, so don't - // allow any extra scoring - if( level.intermissionQueued ) + // Can't deal damage sometimes + if( !targ->takedamage || targ->health <= 0 || level.intermissionQueued ) return; if( !inflictor ) @@ -1176,9 +926,6 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, if( !attacker ) attacker = &g_entities[ ENTITYNUM_WORLD ]; - if( attacker->client && attacker->client->pers.paused ) - return; - // shootable doors / buttons don't actually have any health if( targ->s.eType == ET_MOVER ) { @@ -1190,42 +937,41 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } client = targ->client; - - if( client ) - { - if( client->noclip && !g_devmapNoGod.integer) - return; - if( client->pers.paused ) - return; - } + if( client && client->noclip ) + return; if( !dir ) - dflags &= ~DAMAGE_KNOCKBACK; + dflags |= DAMAGE_NO_KNOCKBACK; else VectorNormalize( dir ); - if( dflags & DAMAGE_KNOCKBACK ) + knockback = damage; + + if( inflictor->s.weapon != WP_NONE ) + { + knockback = (int)( (float)knockback * + BG_Weapon( inflictor->s.weapon )->knockbackScale ); + } + + if( targ->client ) { - knockback = damage; + knockback = (int)( (float)knockback * + BG_Class( targ->client->ps.stats[ STAT_CLASS ] )->knockbackScale ); + } - if( inflictor->s.weapon != WP_NONE ) - { - knockback = (int)( (float)knockback * - BG_FindKnockbackScaleForWeapon( inflictor->s.weapon ) ); - } + // Too much knockback from falling really far makes you "bounce" and + // looks silly. However, none at all also looks bad. Cap it. + if( mod == MOD_FALLING && knockback > 50 ) + knockback = 50; - if( targ->client ) - { - knockback = (int)( (float)knockback * - BG_FindKnockbackScaleForClass( targ->client->ps.stats[ STAT_PCLASS ] ) ); - } + if( knockback > 200 ) + knockback = 200; - if( knockback > 200 ) - knockback = 200; + if( targ->flags & FL_NO_KNOCKBACK ) + knockback = 0; - if( targ->flags & FL_NO_KNOCKBACK ) - knockback = 0; - } + if( dflags & DAMAGE_NO_KNOCKBACK ) + knockback = 0; // figure momentum add, even if the damage won't be taken if( knockback && targ->client ) @@ -1256,6 +1002,18 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } } + // check for godmode + if( targ->flags & FL_GODMODE ) + return; + + // don't do friendly fire on movement attacks + if( ( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || + mod == MOD_LEVEL4_CRUSH ) && + targ->s.eType == ET_BUILDABLE && targ->buildableTeam == TEAM_ALIENS ) + { + return; + } + // check for completely getting out of the damage if( !( dflags & DAMAGE_NO_PROTECTION ) ) { @@ -1264,8 +1022,14 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, // if the attacker was on the same team if( targ != attacker && OnSameTeam( targ, attacker ) ) { + // don't do friendly fire on movement attacks + if( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || + mod == MOD_LEVEL4_CRUSH ) + return; + + // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage if( g_dretchPunt.integer && - targ->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL0 ) + targ->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 ) { vec3_t dir, push; @@ -1275,67 +1039,33 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, push[2] = 64.0f; VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); return; - } - else if(mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE ) - { // don't do friendly fire on movement attacks - if( g_friendlyFireMovementAttacks.value <= 0 || ( g_friendlyFire.value<=0 && g_friendlyFireAliens.value<=0 ) ) - return; - else if( g_friendlyFireMovementAttacks.value > 0 && g_friendlyFireMovementAttacks.value < 1 ) - damage =(int)(0.5 + g_friendlyFireMovementAttacks.value * (float) damage); - } - else if( g_friendlyFire.value <=0) - { - if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - if(g_friendlyFireHumans.value<=0) - return; - else if( g_friendlyFireHumans.value > 0 && g_friendlyFireHumans.value < 1 ) - damage =(int)(0.5 + g_friendlyFireHumans.value * (float) damage); - } - if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - if(g_friendlyFireAliens.value==0) - return; - else if( g_friendlyFireAliens.value > 0 && g_friendlyFireAliens.value < 1 ) - damage =(int)(0.5 + g_friendlyFireAliens.value * (float) damage); - } } - else if( g_friendlyFire.value > 0 && g_friendlyFire.value < 1 ) + + // check if friendly fire has been disabled + if( !g_friendlyFire.integer ) { - damage =(int)(0.5 + g_friendlyFire.value * (float) damage); + return; } } - // If target is buildable on the same team as the attacking client if( targ->s.eType == ET_BUILDABLE && attacker->client && - targ->biteam == attacker->client->pers.teamSelection ) + mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && + mod != MOD_REPLACE && mod != MOD_NOCREEP ) { - if(mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE ) - { - if(g_friendlyFireMovementAttacks.value <= 0) - return; - else if(g_friendlyFireMovementAttacks.value > 0 && g_friendlyFireMovementAttacks.value < 1) - damage =(int)(0.5 + g_friendlyFireMovementAttacks.value * (float) damage); - } - if( g_friendlyBuildableFire.value <= 0 ) + if( targ->buildableTeam == attacker->client->pers.teamSelection && + !g_friendlyBuildableFire.integer ) { return; } - else if( g_friendlyBuildableFire.value > 0 && g_friendlyBuildableFire.value < 1 ) + + // base is under attack warning if DCC'd + if( targ->buildableTeam == TEAM_HUMANS && G_FindDCC( targ ) && + level.time > level.humanBaseAttackTimer ) { - damage =(int)(0.5 + g_friendlyBuildableFire.value * (float) damage); + level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD; + G_BroadcastEvent( EV_DCC_ATTACK, 0 ); } } - - // check for godmode - if ( targ->flags & FL_GODMODE && !g_devmapNoGod.integer) - return; - - if( level.paused ) - return; - - if(targ->s.eType == ET_BUILDABLE && g_cheats.integer && g_devmapNoStructDmg.integer) - return; } // add to the attacker's hit counter @@ -1350,7 +1080,6 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } take = damage; - save = 0; // add to the damage inflicted on a player this frame // the total will be turned into screen blends and view angle kicks @@ -1380,23 +1109,21 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, // set the last client who damaged the target targ->client->lasthurt_client = attacker->s.number; targ->client->lasthurt_mod = mod; - - damagemodifier = G_CalcDamageModifier( point, targ, attacker, client->ps.stats[ STAT_PCLASS ], dflags ); - take = (int)( (float)take * damagemodifier ); + take = (int)( take * G_CalcDamageModifier( point, targ, attacker, + client->ps.stats[ STAT_CLASS ], + dflags ) + 0.5f ); //if boosted poison every attack if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) { - if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && - !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) && - mod != MOD_LEVEL2_ZAP && - targ->client->poisonImmunityTime < level.time ) + if( targ->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + mod != MOD_LEVEL2_ZAP && mod != MOD_POISON && + mod != MOD_LEVEL1_PCLOUD && mod != MOD_HSPAWN && + mod != MOD_ASPAWN && targ->client->poisonImmunityTime < level.time ) { targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED; targ->client->lastPoisonTime = level.time; targ->client->lastPoisonClient = attacker; - attacker->client->pers.statscounters.repairspoisons++; - level.alienStatsCounters.repairspoisons++; } } } @@ -1410,90 +1137,23 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, targ->health, take, asave ); } - takeNoOverkill = take; - if( takeNoOverkill > targ->health ) - { - if(targ->health > 0) - takeNoOverkill = targ->health; - else - takeNoOverkill = 0; - } - + // do the damage if( take ) { - //Increment some stats counters - if( attacker && attacker->client ) - { - if( targ->biteam == attacker->client->pers.teamSelection || OnSameTeam( targ, attacker ) ) - { - attacker->client->pers.statscounters.ffdmgdone += takeNoOverkill; - if( attacker->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.ffdmgdone+=takeNoOverkill; - } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.ffdmgdone+=takeNoOverkill; - } - } - else if( targ->s.eType == ET_BUILDABLE ) - { - attacker->client->pers.statscounters.structdmgdone += takeNoOverkill; - - if( attacker->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.structdmgdone+=takeNoOverkill; - } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.structdmgdone+=takeNoOverkill; - } - - if( targ->health > 0 && ( targ->health - take ) <=0 ) - { - attacker->client->pers.statscounters.structskilled++; - if( attacker->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.structskilled++; - } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.structskilled++; - } - } - } - else if( targ->client ) - { - attacker->client->pers.statscounters.dmgdone +=takeNoOverkill; - attacker->client->pers.statscounters.hits++; - if( attacker->client->pers.teamSelection == PTE_ALIENS ) - { - level.alienStatsCounters.dmgdone+=takeNoOverkill; - } - else if( attacker->client->pers.teamSelection == PTE_HUMANS ) - { - level.humanStatsCounters.dmgdone+=takeNoOverkill; - } - } - } - - - //Do the damage targ->health = targ->health - take; if( targ->client ) + { targ->client->ps.stats[ STAT_HEALTH ] = targ->health; + targ->client->pers.infoChangeTime = level.time; + } targ->lastDamageTime = level.time; + targ->nextRegenTime = level.time + ALIEN_REGEN_DAMAGE_TIME; - //TA: add to the attackers "account" on the target - if( targ->client && attacker->client ) - { - if( attacker != targ && !OnSameTeam( targ, attacker ) ) - targ->credits[ attacker->client->ps.clientNum ] += take; - else if( attacker != targ && OnSameTeam( targ, attacker ) ) - targ->client->tkcredits[ attacker->client->ps.clientNum ] += takeNoOverkill; - } + // add to the attackers "account" on the target + if( attacker->client && attacker != targ ) + targ->credits[ attacker->client->ps.clientNum ] += take; if( targ->health <= 0 ) { @@ -1501,7 +1161,11 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, targ->flags |= FL_NO_KNOCKBACK; if( targ->health < -999 ) + { targ->health = -999; + if( targ->client ) + targ->client->ps.stats[ STAT_HEALTH ] = -999; + } targ->enemy = attacker; targ->die( targ, inflictor, attacker, take, mod ); @@ -1570,8 +1234,6 @@ qboolean CanDamage( gentity_t *targ, vec3_t origin ) return qfalse; } - -//TA: /* ============ G_SelectiveRadiusDamage @@ -1611,6 +1273,9 @@ qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float dama if( !ent->takedamage ) continue; + if( ent->flags & FL_NOTARGET ) + continue; + // find the distance from the edge of the bounding box for( i = 0 ; i < 3 ; i++ ) { @@ -1628,14 +1293,16 @@ qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float dama points = damage * ( 1.0 - dist / radius ); - if( CanDamage( ent, origin ) ) + if( CanDamage( ent, origin ) && ent->client && + ent->client->ps.stats[ STAT_TEAM ] != team ) { VectorSubtract( ent->r.currentOrigin, origin, dir ); // push the center of mass higher than the origin so players // get knocked into the air more dir[ 2 ] += 24; - G_SelectiveDamage( ent, NULL, attacker, dir, origin, - (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod, team ); + hitClient = qtrue; + G_Damage( ent, NULL, attacker, dir, origin, + (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); } } @@ -1649,7 +1316,7 @@ G_RadiusDamage ============ */ qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, - float radius, gentity_t *ignore, int dflags, int mod ) + float radius, gentity_t *ignore, int mod ) { float points, dist; gentity_t *ent; @@ -1705,8 +1372,9 @@ qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, // push the center of mass higher than the origin so players // get knocked into the air more dir[ 2 ] += 24; + hitClient = qtrue; G_Damage( ent, NULL, attacker, dir, origin, - (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE|dflags, mod ); + (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); } } @@ -1714,46 +1382,71 @@ qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, } /* -============ -G_Knockback -============ +================ +G_LogDestruction + +Log deconstruct/destroy events +================ */ -void G_Knockback( gentity_t *targ, vec3_t dir, int knockback ) +void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod ) { - if( knockback && targ->client ) - { - vec3_t kvel; - float mass; + buildFate_t fate; - mass = 200; - - // Halve knockback for bsuits - if( targ->client && - targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && - BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) ) - mass += 400; - - // Halve knockback for crouching players - if(targ->client->ps.pm_flags&PMF_DUCKED) knockback /= 2; - - VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); - VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + switch( mod ) + { + case MOD_DECONSTRUCT: + fate = BF_DECONSTRUCT; + break; + case MOD_REPLACE: + fate = BF_REPLACE; + break; + case MOD_NOCREEP: + fate = ( actor->client ) ? BF_UNPOWER : BF_AUTO; + break; + default: + if( actor->client ) + { + if( actor->client->pers.teamSelection == + BG_Buildable( self->s.modelindex )->team ) + { + fate = BF_TEAMKILL; + } + else + fate = BF_DESTROY; + } + else + fate = BF_AUTO; + break; + } + G_BuildLogAuto( actor, self, fate ); - // set the timer so that the other client can't cancel - // out the movement immediately - if( !targ->client->ps.pm_time ) - { - int t; + // don't log when marked structures are removed + if( mod == MOD_REPLACE ) + return; - t = knockback * 2; - if( t < 50 ) - t = 50; + G_LogPrintf( S_COLOR_YELLOW "Deconstruct: %d %d %s %s: %s %s by %s\n", + (int)( actor - g_entities ), + (int)( self - g_entities ), + BG_Buildable( self->s.modelindex )->name, + modNames[ mod ], + BG_Buildable( self->s.modelindex )->humanName, + mod == MOD_DECONSTRUCT ? "deconstructed" : "destroyed", + actor->client ? actor->client->pers.netname : "" ); + + // No-power deaths for humans come after some minutes and it's confusing + // when the messages appear attributed to the deconner. Just don't print them. + if( mod == MOD_NOCREEP && actor->client && + actor->client->pers.teamSelection == TEAM_HUMANS ) + return; - if( t > 200 ) - t = 200; - targ->client->ps.pm_time = t; - targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - } + if( actor->client && actor->client->pers.teamSelection == + BG_Buildable( self->s.modelindex )->team ) + { + G_TeamCommand( actor->client->ps.stats[ STAT_TEAM ], + va( "print \"%s ^3%s^7 by %s\n\"", + BG_Buildable( self->s.modelindex )->humanName, + mod == MOD_DECONSTRUCT ? "DECONSTRUCTED" : "DESTROYED", + actor->client->pers.netname ) ); } -} +} diff --git a/src/game/g_local.h b/src/game/g_local.h index df16a84..f894ec2 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,14 +17,14 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // g_local.h -- local definitions for game module -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h" #include "bg_public.h" #include "g_public.h" @@ -37,11 +38,8 @@ typedef struct gclient_s gclient_t; #define INFINITE 1000000 #define FRAMETIME 100 // msec -#define CARNAGE_REWARD_TIME 3000 -#define REWARD_SPRITE_TIME 2000 #define INTERMISSION_DELAY_TIME 1000 -#define SP_INTERMISSION_DELAY_TIME 5000 // gentity->flags #define FL_GODMODE 0x00000010 @@ -53,19 +51,6 @@ typedef struct gclient_s gclient_t; #define FL_NO_HUMANS 0x00004000 // spawn point just for bots #define FL_FORCE_GESTURE 0x00008000 // spawn point just for bots -typedef struct -{ - qboolean isNB; - float Area; - float Height; -} noBuild_t; - -typedef struct -{ - gentity_t *Marker; - vec3_t Origin; -} nbMarkers_t; - // movers are things like doors, plats, buttons, etc typedef enum { @@ -87,6 +72,12 @@ typedef enum #define SP_PODIUM_MODEL "models/mapobjects/podium/podium4.md3" +typedef struct gitem_s +{ + int ammo; // ammo held + int clips; // clips held +} gitem_t; + //============================================================================ struct gentity_s @@ -99,6 +90,7 @@ struct gentity_s //================================ struct gclient_s *client; // NULL if not a client + gitem_t item; qboolean inuse; @@ -134,7 +126,6 @@ struct gentity_s int soundLoop; gentity_t *parent; gentity_t *nextTrain; - gentity_t *prevTrain; vec3_t pos1, pos2; float rotatorAngle; gentity_t *clipBrush; // clipping brush for model doors @@ -143,7 +134,6 @@ struct gentity_s int timestamp; // body queue sinking, etc - float angle; // set in editor, -1 = up, -2 = down char *target; char *targetname; char *team; @@ -172,7 +162,6 @@ struct gentity_s void (*die)( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); int pain_debounce_time; - int fly_sound_debounce_time; // wind tunnel int last_move_time; int health; @@ -181,12 +170,10 @@ struct gentity_s qboolean takedamage; int damage; - int dflags; int splashDamage; // quad will increase this without increasing radius int splashRadius; int methodOfDeath; int splashMethodOfDeath; - int chargeRepeat; int count; @@ -205,18 +192,18 @@ struct gentity_s float wait; float random; - pTeam_t stageTeam; + team_t stageTeam; stage_t stageStage; - int biteam; // buildable item team + team_t buildableTeam; // buildable item team gentity_t *parentNode; // for creep and defence/spawn dependencies + gentity_t *rangeMarker; qboolean active; // for power repeater, but could be useful elsewhere qboolean powered; // for human buildables - int builtBy; // clientNum of person that built this - gentity_t *dccNode; // controlling dcc - gentity_t *overmindNode; // controlling overmind - qboolean dcced; // controlled by a dcc or not? + struct namelog_s *builtBy; // person who built this + int dcc; // number of controlling dccs qboolean spawned; // whether or not this buildable has finished spawning + int shrunkTime; // time when a barricade shrunk or zero int buildTime; // when this buildable was built int animTime; // last animation change int time1000; // timer evaluated every second @@ -228,25 +215,20 @@ struct gentity_s int nextPhysicsTime; // buildables don't need to check what they're sitting on // every single frame.. so only do it periodically int clientSpawnTime; // the time until this spawn can spawn a client - qboolean lev1Grabbed; // for turrets interacting with lev1s - int lev1GrabTime; // for turrets interacting with lev1s - int spawnBlockTime; int credits[ MAX_CLIENTS ]; // human credits for each client - qboolean creditsHash[ MAX_CLIENTS ]; // track who has claimed credit int killedBy; // clientNum of killer - gentity_t *targeted; // true if the player is currently a valid target of a turret vec3_t turretAim; // aim vector for turrets + vec3_t turretAimRate; // track turn speed for norfenturrets + int turretSpinupTime; // spinup delay for norfenturrets vec4_t animation; // animated map objects - gentity_t *builder; // occupant of this hovel - qboolean nonSegModel; // this entity uses a nonsegmented player model buildable_t bTriggers[ BA_NUM_BUILDABLES ]; // which buildables are triggers - pClass_t cTriggers[ PCL_NUM_CLASSES ]; // which classes are triggers + class_t cTriggers[ PCL_NUM_CLASSES ]; // which classes are triggers weapon_t wTriggers[ WP_NUM_WEAPONS ]; // which weapons are triggers upgrade_t uTriggers[ UP_NUM_UPGRADES ]; // which upgrades are triggers @@ -255,11 +237,12 @@ struct gentity_s int suicideTime; // when the client will suicide int lastDamageTime; - - int bdnumb; // buildlog entry ID - - // For nobuild! - noBuild_t noBuild; + int nextRegenTime; + + qboolean pointAgainstWorld; // don't use the bbox for map collisions + + int buildPointZone; // index for zone + int usesBuildPointZone; // does it use a zone? }; typedef enum @@ -269,125 +252,46 @@ typedef enum CON_CONNECTED } clientConnected_t; -typedef enum -{ - SPECTATOR_NOT, - SPECTATOR_FREE, - SPECTATOR_LOCKED, - SPECTATOR_FOLLOW, - SPECTATOR_SCOREBOARD -} spectatorState_t; - -typedef enum -{ - TEAM_BEGIN, // Beginning a team game, spawn at base - TEAM_ACTIVE // Now actively playing -} playerTeamStateState_t; - -typedef struct -{ - playerTeamStateState_t state; - - int location; - - int captures; - int basedefense; - int carrierdefense; - int flagrecovery; - int fragcarrier; - int assists; - - float lasthurtcarrier; - float lastreturnedflag; - float flagsince; - float lastfraggedcarrier; -} playerTeamState_t; - -// the auto following clients don't follow a specific client -// number, but instead follow the first two active players -#define FOLLOW_ACTIVE1 -1 -#define FOLLOW_ACTIVE2 -2 - // client data that stays across multiple levels or tournament restarts // this is achieved by writing all the data to cvar strings at game shutdown // time and reading them back at connection time. Anything added here // MUST be dealt with in G_InitSessionData() / G_ReadSessionData() / G_WriteSessionData() typedef struct { - team_t sessionTeam; - pTeam_t restartTeam; //for !restart keepteams and !restart switchteams int spectatorTime; // for determining next-in-line to play spectatorState_t spectatorState; int spectatorClient; // for chasecam and follow mode - int wins, losses; // tournament stats - qboolean invisible; // for being invisible on the server - ghosts! - qboolean teamLeader; // true when this client is a team leader + team_t restartTeam; //for !restart keepteams and !restart switchteams clientList_t ignoreList; } clientSession_t; -#define MAX_NETNAME 36 - -// data to store details of clients that have abnormally disconnected -typedef struct connectionRecord_s +// namelog +#define MAX_NAMELOG_NAMES 5 +#define MAX_NAMELOG_ADDRS 5 +typedef struct namelog_s { - int clientNum; - pTeam_t clientTeam; - int clientCredit; - int clientScore; - int clientEnterTime; + struct namelog_s *next; + char name[ MAX_NAMELOG_NAMES ][ MAX_NAME_LENGTH ]; + addr_t ip[ MAX_NAMELOG_ADDRS ]; + char guid[ 33 ]; + qboolean guidless; + int slot; + qboolean banned; - int ptrCode; -} connectionRecord_t; + int nameOffset; + int nameChangeTime; + int nameChanges; + int voteCount; -typedef struct -{ - short kills; - short deaths; - short feeds; - short suicides; - short assists; - int dmgdone; - int ffdmgdone; - int structdmgdone; - short structsbuilt; - short repairspoisons; - short structskilled; - int timealive; - int timeinbase; - short headshots; - int hits; - int hitslocational; - short teamkills; - int dretchbasytime; - int jetpackusewallwalkusetime; - int timeLastViewed; - int AllstatstimeLastViewed; - int spreebleeds; -} statsCounters_t; + qboolean muted; + qboolean denyBuild; -typedef struct -{ - int kills; - int deaths; - int feeds; - int suicides; - int assists; - long dmgdone; - long ffdmgdone; - long structdmgdone; - int structsbuilt; - int repairspoisons; - int structskilled; - long timealive; - long timeinbase; - int headshots; - long hits; - long hitslocational; - int teamkills; - long dretchbasytime; - long jetpackusewallwalkusetime; - long timeLastViewed; -} statsCounters_level; + int score; + int credits; + team_t team; + + int id; +} namelog_t; // client data that stays across multiple respawns, but is cleared // on each level change or team change at ClientBegin() @@ -396,66 +300,51 @@ typedef struct clientConnected_t connected; usercmd_t cmd; // we would lose angles if not persistant qboolean localClient; // true if "ip" info key is "localhost" - qboolean initialSpawn; // the first spawn should be at a cool location - qboolean predictItemPickup; // based on cg_predictItems userinfo + qboolean stickySpec; // don't stop spectating a player after they get killed qboolean pmoveFixed; // - char netname[ MAX_NETNAME ]; - int maxHealth; // for handicapping + char netname[ MAX_NAME_LENGTH ]; int enterTime; // level.time the client entered the game - playerTeamState_t teamState; // status in teamplay games - int voteCount; // to prevent people from constantly calling votes - qboolean teamInfo; // send team overlay updates? + int location; // player locations + int teamInfo; // level.time of team overlay update (disabled = 0) + float flySpeed; // for spectator/noclip moves + qboolean disableBlueprintErrors; // should the buildable blueprint never be hidden from the players? + int buildableRangeMarkerMask; - pClass_t classSelection; // player class (copied to ent->client->ps.stats[ STAT_PCLASS ] once spawned) + class_t classSelection; // player class (copied to ent->client->ps.stats[ STAT_CLASS ] once spawned) float evolveHealthFraction; weapon_t humanItemSelection; // humans have a starting item - pTeam_t teamSelection; // player team (copied to ps.stats[ STAT_PTEAM ]) + team_t teamSelection; // player team (copied to ps.stats[ STAT_TEAM ]) int teamChangeTime; // level.time of last team change - qboolean joinedATeam; // used to tell when a PTR code is valid - connectionRecord_t *connection; + namelog_t *namelog; + g_admin_admin_t *admin; - int nameChangeTime; - int nameChanges; + int secondsAlive; // time player has been alive in seconds + qboolean hasHealed; // has healed a player (basi regen aura) in the last 10sec (for score use) - // used to save playerState_t values while in SPECTATOR_FOLLOW mode - int score; + // used to save persistant[] values while in SPECTATOR_FOLLOW mode int credit; - int ping; - - int lastTeamStatus; - int lastFreekillTime; - int lastFloodTime; // level.time of last flood-limited command - int floodDemerits; // number of flood demerits accumulated + int voted; + int vote; - char lastMessage[ MAX_SAY_TEXT ]; // last message said by this player - int lastMessageTime; // level.time of last message said by this player - - int lastTeamKillTime; // level.time of last team kill - int teamKillDemerits; // number of team kill demerits accumulated + // flood protection + int floodDemerits; + int floodTime; vec3_t lastDeathLocation; + int alternateProtocol; char guid[ 33 ]; - char ip[ 16 ]; - qboolean paused; - qboolean muted; - int muteExpires; // level.time at which a player is unmuted - qboolean ignoreAdminWarnings; - qboolean denyBuild; - int specExpires; // level.time at which a player can join a team again after being forced into spectator - int denyHumanWeapons; - int denyAlienClasses; - int adminLevel; - char adminName[ MAX_NETNAME ]; - qboolean designatedBuilder; - qboolean firstConnect; // This is the first map since connect + qboolean guidless; + addr_t ip; + char voice[ MAX_VOICE_NAME_LEN ]; qboolean useUnlagged; - statsCounters_t statscounters; - int bubbleTime; + + // level.time when teamoverlay info changed so we know to tell other players + int infoChangeTime; } clientPersistant_t; -#define MAX_UNLAGGED_MARKERS 10 +#define MAX_UNLAGGED_MARKERS 256 typedef struct unlagged_s { vec3_t origin; vec3_t mins; @@ -463,6 +352,7 @@ typedef struct unlagged_s { qboolean used; } unlagged_t; +#define MAX_TRAMPLE_BUILDABLES_TRACKED 20 // this structure is cleared on each ClientSpawn(), // except for 'client->pers' and 'client->sess' struct gclient_s @@ -480,6 +370,7 @@ struct gclient_s qboolean readyToExit; // wishes to leave the intermission qboolean noclip; + int cliprcontents; // the backup layer of ent->r.contents for when noclipping int lastCmdTime; // level.time of last usercmd_t, for EF_CONNECTION // we can't just use pers.lastCommand.time, because @@ -508,6 +399,7 @@ struct gclient_s int inactivityTime; // kick players when time > this qboolean inactivityWarning;// qtrue if the five seoond warning has been given int rewardTime; // clear the EF_AWARD_IMPRESSIVE, etc when time > this + int boostedTime; // last time we touched a booster int airOutTime; @@ -517,53 +409,43 @@ struct gclient_s int switchTeamTime; // time the player switched teams - // timeResidual is used to handle events that happen every second - // like health / armor countdowns and regeneration - // two timers, one every 100 msecs, another every sec - int time100; - int time1000; - int time10000; + int time100; // timer for 100ms interval events + int time1000; // timer for one second interval events + int time10000; // timer for ten second interval events char *areabits; - gentity_t *hovel; - int lastPoisonTime; int poisonImmunityTime; gentity_t *lastPoisonClient; int lastPoisonCloudedTime; - gentity_t *lastPoisonCloudedClient; int grabExpiryTime; int lastLockTime; int lastSlowTime; - int lastBoostedTime; int lastMedKitTime; int medKitHealthToRestore; int medKitIncrementTime; int lastCreepSlowTime; // time until creep can be removed - qboolean allowedToPounce; - qboolean charging; - float jetpackfuel; - - vec3_t hovelOrigin; // player origin before entering hovel - int lastFlameBall; // s.number of the last flame ball fired -#define RAM_FRAMES 1 // number of frames to wait before retriggering - int retriggerArmouryMenu; // frame number to retrigger the armoury menu - unlagged_t unlaggedHist[ MAX_UNLAGGED_MARKERS ]; unlagged_t unlaggedBackup; unlagged_t unlaggedCalc; int unlaggedTime; - - int tkcredits[ MAX_CLIENTS ]; -}; + float voiceEnthusiasm; + char lastVoiceCmd[ MAX_VOICE_CMD_LEN ]; + int lcannonStartTime; + int trampleBuildablesHitPos; + int trampleBuildablesHit[ MAX_TRAMPLE_BUILDABLES_TRACKED ]; + + int lastCrushTime; // Tyrant crush + int lastDropTime; // Weapon drop with /drop +}; typedef struct spawnQueue_s { @@ -583,35 +465,30 @@ qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum ); qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum ); qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum ); int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum ); +void G_PrintSpawnQueue( spawnQueue_t *sq ); -#define MAX_LOCDAMAGE_TEXT 8192 -#define MAX_LOCDAMAGE_REGIONS 16 +#define MAX_DAMAGE_REGION_TEXT 8192 +#define MAX_DAMAGE_REGIONS 16 -// store locational damage regions -typedef struct damageRegion_s +// build point zone +typedef struct { - float minHeight, maxHeight; - int minAngle, maxAngle; + int active; - float modifier; + int totalBuildPoints; + int queuedBuildPoints; + int nextQueueTime; +} buildPointZone_t; - qboolean crouch; -} damageRegion_t; - -#define MAX_ARMOUR_TEXT 8192 -#define MAX_ARMOUR_REGIONS 16 - -// store locational armour regions -typedef struct armourRegion_s +// store locational damage regions +typedef struct damageRegion_s { - float minHeight, maxHeight; + char name[ 32 ]; + float area, modifier, minHeight, maxHeight; int minAngle, maxAngle; - - float modifier; - qboolean crouch; -} armourRegion_t; +} damageRegion_t; //status of the warning of certain events typedef enum @@ -621,37 +498,43 @@ typedef enum TW_PASSED } timeWarning_t; +// fate of a buildable typedef enum { - BF_BUILT, - BF_DECONNED, - BF_DESTROYED, - BF_TEAMKILLED -} buildableFate_t; - -// record all changes to the buildable layout - build, decon, destroy - and -// enough information to revert that change -typedef struct buildHistory_s buildHistory_t; -struct buildHistory_s + BF_CONSTRUCT, + BF_DECONSTRUCT, + BF_REPLACE, + BF_DESTROY, + BF_TEAMKILL, + BF_UNPOWER, + BF_AUTO +} buildFate_t; + +// data needed to revert a change in layout +typedef struct { - int ID; // persistent ID to aid in specific reverting - gentity_t *ent; // who, NULL if they've disconnected (or aren't an ent) - char name[ MAX_NETNAME ]; // who, saves name if ent is NULL - int buildable; // what - vec3_t origin; // where - vec3_t angles; // which way round - vec3_t origin2; // I don't know what the hell these are, but layoutsave saves - vec3_t angles2; // them so I will do the same - buildableFate_t fate; // was it built, destroyed or deconned - buildHistory_t *next; // next oldest change - buildHistory_t *marked; // linked list of markdecon buildings taken -}; + int time; + buildFate_t fate; + namelog_t *actor; + namelog_t *builtBy; + buildable_t modelindex; + qboolean deconstruct; + int deconstructTime; + vec3_t origin; + vec3_t angles; + vec3_t origin2; + vec3_t angles2; + buildable_t powerSource; + int powerValue; +} buildLog_t; // // this structure is cleared as each map is entered // #define MAX_SPAWN_VARS 64 #define MAX_SPAWN_VARS_CHARS 4096 +#define MAX_BUILDLOG 128 +#define MAX_PLAYER_MODEL 256 typedef struct { @@ -659,7 +542,9 @@ typedef struct struct gentity_s *gentities; int gentitySize; - int num_entities; // current number, <= MAX_GENTITIES + int num_entities; // MAX_CLIENTS <= num_entities <= ENTITYNUM_MAX_NORMAL + + int warmupTime; // restart match at this time fileHandle_t logFile; @@ -673,7 +558,7 @@ typedef struct int startTime; // level.time the map was started - int teamScores[ TEAM_NUM_TEAMS ]; + int teamScores[ NUM_TEAMS ]; int lastTeamLocationTime; // last time of client team location update qboolean newSession; // don't use any old session data, because @@ -686,27 +571,20 @@ typedef struct int numPlayingClients; // connected, non-spectators int sortedClients[MAX_CLIENTS]; // sorted by score - int numNewbies; // number of UnnamedPlayers who have been renamed this round. - int snd_fry; // sound index for standing in lava + int warmupModificationCount; // for detecting if g_warmup is changed + // voting state - char voteString[MAX_STRING_CHARS]; - char voteDisplayString[MAX_STRING_CHARS]; - int votePassThreshold; - int voteTime; // level.time vote was called - int voteExecuteTime; // time the vote is executed - int voteYes; - int voteNo; - int numVotingClients; // set by CalculateRanks - - // team voting state - char teamVoteString[ 2 ][ MAX_STRING_CHARS ]; - char teamVoteDisplayString[ 2 ][ MAX_STRING_CHARS ]; - int teamVoteTime[ 2 ]; // level.time vote was called - int teamVoteYes[ 2 ]; - int teamVoteNo[ 2 ]; - int numteamVotingClients[ 2 ]; // set by CalculateRanks + int voteThreshold[ NUM_TEAMS ]; // need at least this percent to pass + char voteString[ NUM_TEAMS ][ MAX_STRING_CHARS ]; + char voteDisplayString[ NUM_TEAMS ][ MAX_STRING_CHARS ]; + int voteTime[ NUM_TEAMS ]; // level.time vote was called + int voteExecuteTime[ NUM_TEAMS ]; // time the vote is executed + int voteDelay[ NUM_TEAMS ]; // it doesn't make sense to always delay vote execution + int voteYes[ NUM_TEAMS ]; + int voteNo[ NUM_TEAMS ]; + int numVotingClients[ NUM_TEAMS ];// set by CalculateRanks // spawn variables qboolean spawning; // the G_Spawn*() functions are valid @@ -716,6 +594,7 @@ typedef struct char spawnVarChars[ MAX_SPAWN_VARS_CHARS ]; // intermission state + qboolean exited; int intermissionQueued; // intermission was qualified, but // wait INTERMISSION_DELAY_TIME before // actually going there so the last @@ -728,7 +607,6 @@ typedef struct vec3_t intermission_origin; // also used for spectator spawns vec3_t intermission_angle; - qboolean locationLinked; // target_locations get linked gentity_t *locationHead; // head of the location list int numAlienSpawns; @@ -742,12 +620,17 @@ typedef struct float averageNumHumanClients; int numHumanSamples; - int numLiveAlienClients; - int numLiveHumanClients; + int numAlienClientsAlive; + int numHumanClientsAlive; int alienBuildPoints; + int alienBuildPointQueue; + int alienNextQueueTime; int humanBuildPoints; - int humanBuildPointsPowered; + int humanBuildPointQueue; + int humanNextQueueTime; + + buildPointZone_t *buildPointZones; gentity_t *markedBuildables[ MAX_GENTITIES ]; int numBuildablesForRemoval; @@ -755,21 +638,15 @@ typedef struct int alienKills; int humanKills; - qboolean reactorPresent; - qboolean overmindPresent; qboolean overmindMuted; int humanBaseAttackTimer; - pTeam_t lastWin; + team_t lastWin; - int suddenDeathABuildPoints; - int suddenDeathHBuildPoints; - qboolean suddenDeath; int suddenDeathBeginTime; timeWarning_t suddenDeathWarning; timeWarning_t timelimitWarning; - int extend_vote_count; spawnQueue_t alienSpawnQueue; spawnQueue_t humanSpawnQueue; @@ -783,48 +660,41 @@ typedef struct qboolean uncondHumanWin; qboolean alienTeamLocked; qboolean humanTeamLocked; - qboolean paused; - int pauseTime; - float pause_speed; - float pause_gravity; - float pause_knockback; - int pause_ff; - int pause_ffb; - - int lastCreditedAlien; - int lastCreditedHuman; + int pausedTime; int unlaggedIndex; int unlaggedTimes[ MAX_UNLAGGED_MARKERS ]; char layout[ MAX_QPATH ]; - pTeam_t surrenderTeam; - buildHistory_t *buildHistory; - int lastBuildID; - int lastTeamUnbalancedTime; - int numTeamWarnings; - int lastMsgTime; - int mapRotationVoteTime; - - statsCounters_level alienStatsCounters; - statsCounters_level humanStatsCounters; - - qboolean noBuilding; - float nbArea; - float nbHeight; + team_t surrenderTeam; + int lastTeamImbalancedTime; + int numTeamImbalanceWarnings; + + voice_t *voices; + + emoticon_t emoticons[ MAX_EMOTICONS ]; + int emoticonCount; - nbMarkers_t nbMarkers[ MAX_GENTITIES ]; + char *playerModel[ MAX_PLAYER_MODEL ]; + int playerModelCount; + + namelog_t *namelogs; + + buildLog_t buildLog[ MAX_BUILDLOG ]; + int buildId; + int numBuildLogs; } level_locals_t; -#define CMD_CHEAT 0x01 -#define CMD_MESSAGE 0x02 // sends message to others (skip when muted) -#define CMD_TEAM 0x04 // must be on a team -#define CMD_NOTEAM 0x08 // must not be on a team -#define CMD_ALIEN 0x10 -#define CMD_HUMAN 0x20 -#define CMD_LIVING 0x40 -#define CMD_INTERMISSION 0x80 // valid during intermission +#define CMD_CHEAT 0x0001 +#define CMD_CHEAT_TEAM 0x0002 // is a cheat when used on a team +#define CMD_MESSAGE 0x0004 // sends message to others (skip when muted) +#define CMD_TEAM 0x0008 // must be on a team +#define CMD_SPEC 0x0010 // must be a spectator +#define CMD_ALIEN 0x0020 +#define CMD_HUMAN 0x0040 +#define CMD_ALIVE 0x0080 +#define CMD_INTERMISSION 0x0100 // valid during intermission typedef struct { @@ -847,33 +717,31 @@ char *G_NewString( const char *string ); // // g_cmds.c // -void Cmd_Score_f( gentity_t *ent ); -qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin ); -void G_StopFromFollowing( gentity_t *ent ); + +#define DECOLOR_OFF '\16' +#define DECOLOR_ON '\17' + void G_StopFollowing( gentity_t *ent ); +void G_StopFromFollowing( gentity_t *ent ); +void G_FollowLockView( gentity_t *ent ); qboolean G_FollowNewClient( gentity_t *ent, int dir ); void G_ToggleFollow( gentity_t *ent ); -qboolean G_MatchOnePlayer( int *plist, char *err, int len ); -int G_ClientNumbersFromString( char *s, int *plist ); -void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ); -int G_SayArgc( void ); -qboolean G_SayArgv( int n, char *buffer, int bufferLength ); -char *G_SayConcatArgs( int start ); -void G_DecolorString( char *in, char *out ); -void G_ParseEscapedString( char *buffer ); -void G_LeaveTeam( gentity_t *self ); -void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ); +int G_ClientNumberFromString( char *s, char *err, int len ); +int G_ClientNumbersFromString( char *s, int *plist, int max ); +char *ConcatArgs( int start ); +char *ConcatArgsPrintable( int start ); +void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ); +void G_DecolorString( const char *in, char *out, int len ); +void G_UnEscapeString( char *in, char *out, int len ); void G_SanitiseString( char *in, char *out, int len ); -void G_PrivateMessage( gentity_t *ent ); -char *G_statsString( statsCounters_t *sc, pTeam_t *pt ); -void Cmd_Share_f( gentity_t *ent ); -void Cmd_Donate_f( gentity_t *ent ); -void Cmd_TeamVote_f( gentity_t *ent ); -void Cmd_Builder_f( gentity_t *ent ); -void G_WordWrap( char *buffer, int maxwidth ); -void G_CP( gentity_t *ent ); -qboolean G_IsMuted( gclient_t *ent ); -qboolean G_TeamKill_Repent( gentity_t *ent ); +void Cmd_PrivateMessage_f( gentity_t *ent ); +void Cmd_ListMaps_f( gentity_t *ent ); +void Cmd_Test_f( gentity_t *ent ); +void Cmd_AdminMessage_f( gentity_t *ent ); +int G_FloodLimited( gentity_t *ent ); +void G_ListCommands( gentity_t *ent ); +void G_LoadCensors( void ); +void G_CensorString( char *out, const char *in, int len, gentity_t *ent ); // // g_physics.c @@ -891,69 +759,80 @@ typedef enum IBE_NONE, IBE_NOOVERMIND, - IBE_OVERMIND, - IBE_NOASSERT, - IBE_SPWNWARN, + IBE_ONEOVERMIND, + IBE_NOALIENBP, + IBE_SPWNWARN, // not currently used IBE_NOCREEP, - IBE_HOVEL, - IBE_HOVELEXIT, - - IBE_REACTOR, - IBE_REPEATER, - IBE_TNODEWARN, - IBE_RPTWARN, - IBE_RPTWARN2, - IBE_NOPOWER, + + IBE_ONEREACTOR, + IBE_NOPOWERHERE, + IBE_TNODEWARN, // not currently used + IBE_RPTNOREAC, + IBE_RPTPOWERHERE, + IBE_NOHUMANBP, IBE_NODCC, - IBE_NORMAL, + IBE_NORMAL, // too steep IBE_NOROOM, IBE_PERMISSION, + IBE_LASTSPAWN, IBE_MAXERRORS } itemBuildError_t; -qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit ); -gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, - buildable_t spawn, vec3_t spawnOrigin ); +gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, + const vec3_t normal, buildable_t spawn, + vec3_t spawnOrigin ); -qboolean G_IsPowered( vec3_t origin ); +buildable_t G_IsPowered( vec3_t origin ); qboolean G_IsDCCBuilt( void ); -qboolean G_IsOvermindBuilt( void ); +int G_FindDCC( gentity_t *self ); +gentity_t *G_Reactor( void ); +gentity_t *G_Overmind( void ); +qboolean G_FindCreep( gentity_t *self ); void G_BuildableThink( gentity_t *ent, int msec ); qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ); -itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ); -qboolean G_BuildingExists( int bclass ) ; +void G_ClearDeconMarks( void ); +itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, + vec3_t origin, vec3_t normal, int *groundEntNum ); qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ); void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ); void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ); void G_SpawnBuildable(gentity_t *ent, buildable_t buildable); void FinishSpawningBuildable( gentity_t *ent ); -void G_CheckDBProtection( void ); void G_LayoutSave( char *name ); int G_LayoutList( const char *map, char *list, int len ); void G_LayoutSelect( void ); -void G_LayoutLoad( void ); -void G_BaseSelfDestruct( pTeam_t team ); -gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ); -void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ); -void G_CommitRevertedBuildable( gentity_t *ent ); -qboolean G_RevertCanFit( buildHistory_t *bh ); -int G_LogBuild( buildHistory_t *new ); -int G_CountBuildLog( void ); -char *G_FindBuildLogName( int id ); -void G_NobuildSave( void ); -void G_NobuildLoad( void ); +void G_LayoutLoad( char *lstr ); +void G_BaseSelfDestruct( team_t team ); +int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate ); +void G_QueueBuildPoints( gentity_t *self ); +int G_GetBuildPoints( const vec3_t pos, team_t team ); +int G_GetMarkedBuildPoints( const vec3_t pos, team_t team ); +qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned ); +gentity_t *G_PowerEntityForPoint( const vec3_t origin ); +gentity_t *G_PowerEntityForEntity( gentity_t *ent ); +gentity_t *G_RepeaterEntityForPoint( vec3_t origin ); +gentity_t *G_InPowerZone( gentity_t *self ); +buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate ); +void G_BuildLogSet( buildLog_t *log, gentity_t *ent ); +void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ); +void G_BuildLogRevert( int id ); +void G_RemoveRangeMarkerFrom( gentity_t *self ); +void G_UpdateBuildableRangeMarkers( void ); // // g_utils.c // -int G_ParticleSystemIndex( char *name ); -int G_ShaderIndex( char *name ); -int G_ModelIndex( char *name ); -int G_SoundIndex( char *name ); -void G_TeamCommand( pTeam_t team, char *cmd ); +//addr_t in g_admin.h for g_admin_ban_t +qboolean G_AddressParse( const char *str, addr_t *addr ); +qboolean G_AddressCompare( const addr_t *a, const addr_t *b ); + +int G_ParticleSystemIndex( const char *name ); +int G_ShaderIndex( const char *name ); +int G_ModelIndex( const char *name ); +int G_SoundIndex( const char *name ); void G_KillBox (gentity_t *ent); gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match); gentity_t *G_PickTarget (char *targetname); @@ -962,15 +841,14 @@ void G_SetMovedir ( vec3_t angles, vec3_t movedir); void G_InitGentity( gentity_t *e ); gentity_t *G_Spawn( void ); -gentity_t *G_TempEntity( vec3_t origin, int event ); +gentity_t *G_TempEntity( const vec3_t origin, int event ); void G_Sound( gentity_t *ent, int channel, int soundIndex ); void G_FreeEntity( gentity_t *e ); +void G_RemoveEntity( gentity_t *ent ); qboolean G_EntitiesFree( void ); void G_TouchTriggers( gentity_t *ent ); -void G_TouchSolids( gentity_t *ent ); -float *tv( float x, float y, float z ); char *vtos( const vec3_t v ); float vectoyaw( const vec3_t vec ); @@ -978,7 +856,7 @@ float vectoyaw( const vec3_t vec ); void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ); void G_AddEvent( gentity_t *ent, int event, int eventParm ); void G_BroadcastEvent( int event, int eventParm ); -void G_SetOrigin( gentity_t *ent, vec3_t origin ); +void G_SetOrigin( gentity_t *ent, const vec3_t origin ); void AddRemap(const char *oldShader, const char *newShader, float timeOffset); const char *BuildShaderStateConfig( void ); @@ -986,9 +864,10 @@ const char *BuildShaderStateConfig( void ); qboolean G_ClientIsLagging( gclient_t *client ); void G_TriggerMenu( int clientNum, dynMenu_t menu ); +void G_TriggerMenuArgs( int clientNum, dynMenu_t menu, int arg ); void G_CloseMenus( int clientNum ); -qboolean G_Visible( gentity_t *ent1, gentity_t *ent2 ); +qboolean G_Visible( gentity_t *ent1, gentity_t *ent2, int contents ); gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities ); // @@ -1000,31 +879,32 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team ); qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, - gentity_t *ignore, int dflags, int mod ); + gentity_t *ignore, int mod ); qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod, int team ); -void G_Knockback( gentity_t *targ, vec3_t dir, int knockback ); -void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); +float G_RewardAttackers( gentity_t *self ); void AddScore( gentity_t *ent, int score ); +void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod ); void G_InitDamageLocations( void ); // damage flags #define DAMAGE_RADIUS 0x00000001 // damage was indirect #define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage -#define DAMAGE_KNOCKBACK 0x00000004 // affect velocity, not just view angles -#define DAMAGE_NO_PROTECTION 0x00000008 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_KNOCKBACK 0x00000004 // do not affect velocity, just view angles +#define DAMAGE_NO_PROTECTION 0x00000008 // kills everything except godmode #define DAMAGE_NO_LOCDAMAGE 0x00000010 // do not apply locational damage // // g_missile.c // +void G_BounceMissile( gentity_t *ent, trace_t *trace ); void G_RunMissile( gentity_t *ent ); gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t aimdir ); gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir ); gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir ); -gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius ); +gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius, int speed ); gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir ); gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir ); gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir ); @@ -1044,35 +924,44 @@ void manualTriggerSpectator( gentity_t *trigger, gentity_t *player ); // g_trigger.c // void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace ); -void G_Checktrigger_stages( pTeam_t team, stage_t stage ); +void G_Checktrigger_stages( team_t team, stage_t stage ); // // g_misc.c // -void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); -void ShineTorch( gentity_t *self ); +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, float speed ); // -// g_weapon.c +// g_playermodel.c // +void G_InitPlayerModel(void); +qboolean G_IsValidPlayerModel(const char *model); +void G_FreePlayerModel(void); +void G_GetPlayerModelSkins( const char *modelname, char skins[][ 64 ], int maxskins, int *numskins ); +char *GetSkin( char *modelname, char *wish ); -#define MAX_ZAP_TARGETS LEVEL2_AREAZAP_MAX_TARGETS +// +// g_weapon.c +// typedef struct zap_s { qboolean used; gentity_t *creator; - gentity_t *targets[ MAX_ZAP_TARGETS ]; + gentity_t *targets[ LEVEL2_AREAZAP_MAX_TARGETS ]; int numTargets; + float distances[ LEVEL2_AREAZAP_MAX_TARGETS ]; int timeToLive; - int damageUsed; gentity_t *effectChannel; } zap_t; +#define MAX_ZAPS MAX_CLIENTS +extern zap_t zaps[ MAX_ZAPS ]; + void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon ); void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ); void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); @@ -1080,35 +969,34 @@ void SnapVectorTowards( vec3_t v, vec3_t to ); qboolean CheckVenomAttack( gentity_t *ent ); void CheckGrabAttack( gentity_t *ent ); qboolean CheckPounceAttack( gentity_t *ent ); -void ChargeAttack( gentity_t *ent, gentity_t *victim ); +void CheckCkitRepair( gentity_t *ent ); +void G_ChargeAttack( gentity_t *ent, gentity_t *victim ); +void G_CrushAttack( gentity_t *ent, gentity_t *victim ); void G_UpdateZaps( int msec ); +void G_ClearPlayerZapEffects( gentity_t *player ); // // g_client.c // void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ); -team_t TeamCount( int ignoreClientNum, int team ); -void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ); -gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles ); +void G_SetClientViewAngle( gentity_t *ent, const vec3_t angle ); +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ); gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ); gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ); gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ); -void SpawnCorpse( gentity_t *ent ); void respawn( gentity_t *ent ); void BeginIntermission( void ); -void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ); +void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ); void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); qboolean SpotWouldTelefrag( gentity_t *spot ); -char *G_NextNewbieName( gentity_t *ent ); -void G_LogAutobahn( gentity_t *ent, const char *userinfo, int rating, qboolean onConnect ); // // g_svcmds.c // qboolean ConsoleCommand( void ); -void G_ProcessIPBans( void ); -qboolean G_FilterPacket( char *from ); +void G_RegisterCommands( void ); +void G_UnregisterCommands( void ); // // g_weapon.c @@ -1118,8 +1006,11 @@ void FireWeapon2( gentity_t *ent ); void FireWeapon3( gentity_t *ent ); // -// g_cmds.c +// g_weapondrop.c // +gentity_t *LaunchWeapon( gentity_t *client, weapon_t weap, vec3_t origin, vec3_t velocity ); +gentity_t *G_DropWeapon( gentity_t *ent, weapon_t w, float angle ); +void G_RunWeaponDrop(gentity_t *ent); // // g_main.c @@ -1130,27 +1021,22 @@ void G_MapConfigs( const char *mapname ); void CalculateRanks( void ); void FindIntermissionPoint( void ); void G_RunThink( gentity_t *ent ); -void QDECL G_LogPrintf( const char *fmt, ... ); -void QDECL G_LogPrintfColoured( const char *fmt, ... ); -void QDECL G_LogOnlyPrintf( const char *fmt, ... ); -void QDECL G_AdminsPrintf( const char *fmt, ... ); -void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... ); -void QDECL G_LogOnlyPrintf( const char *fmt, ... ); +void G_AdminMessage( gentity_t *ent, const char *string ); +void QDECL G_LogPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); void SendScoreboardMessageToAllClients( void ); -void QDECL G_Printf( const char *fmt, ... ); -void QDECL G_Error( const char *fmt, ... ); -void CheckVote( void ); -void CheckTeamVote( int teamnum ); +void QDECL G_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL G_Error( const char *fmt, ... ) __attribute__ ((noreturn, format (printf, 1, 2))); +void G_Vote( gentity_t *ent, team_t team, qboolean voting ); +void G_ExecuteVote( team_t team ); +void G_CheckVote( team_t team ); void LogExit( const char *string ); int G_TimeTilSuddenDeath( void ); -void CheckMsgTimer( void ); -qboolean G_Flood_Limited( gentity_t *ent ); // // g_client.c // -char *ClientConnect( int clientNum, qboolean firstTime ); -void ClientUserinfoChanged( int clientNum, qboolean forceName ); +const char *ClientConnect( int clientNum, qboolean firstTime ); +char *ClientUserinfoChanged( int clientNum, qboolean forceName ); void ClientDisconnect( int clientNum ); void ClientBegin( int clientNum ); void ClientCommand( int clientNum ); @@ -1170,20 +1056,15 @@ void G_RunClient( gentity_t *ent ); // // g_team.c // +team_t G_TeamFromString( char *str ); +void G_TeamCommand( team_t team, const char *cmd ); qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ); +void G_LeaveTeam( gentity_t *self ); +void G_ChangeTeam( gentity_t *ent, team_t newTeam ); gentity_t *Team_GetLocation( gentity_t *ent ); -qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen ); void TeamplayInfoMessage( gentity_t *ent ); void CheckTeamStatus( void ); - -// -// g_mem.c -// -void *G_Alloc( int size ); -void G_InitMemory( void ); -void G_Free( void *ptr ); -void G_DefragmentMemory( void ); -void Svcmd_GameMem_f( void ); +void G_UpdateTeamConfigStrings( void ); // // g_session.c @@ -1195,100 +1076,28 @@ void G_WriteSessionData( void ); // // g_maprotation.c // -#define MAX_MAP_ROTATIONS 64 -#define MAX_MAP_ROTATION_MAPS 64 -#define MAX_MAP_COMMANDS 16 -#define MAX_MAP_ROTATION_CONDS 8 - -#define NOT_ROTATING -1 - -typedef enum -{ - MCV_ERR, - MCV_RANDOM, - MCV_NUMCLIENTS, - MCV_LASTWIN, - MCV_VOTE, - MCV_SELECTEDRANDOM -} mapConditionVariable_t; - -typedef enum -{ - MCO_LT, - MCO_EQ, - MCO_GT -} mapConditionOperator_t; - -typedef enum -{ - MCT_ERR, - MCT_MAP, - MCT_ROTATION -} mapConditionType_t; - -typedef struct mapRotationCondition_s -{ - char dest[ MAX_QPATH ]; - - qboolean unconditional; - - mapConditionVariable_t lhs; - mapConditionOperator_t op; - - int numClients; - pTeam_t lastWin; -} mapRotationCondition_t; - -typedef struct mapRotationEntry_s -{ - char name[ MAX_QPATH ]; - - char postCmds[ MAX_MAP_COMMANDS ][ MAX_STRING_CHARS ]; - char layouts[ MAX_CVAR_VALUE_STRING ]; - int numCmds; - - mapRotationCondition_t conditions[ MAX_MAP_ROTATION_CONDS ]; - int numConditions; -} mapRotationEntry_t; - -typedef struct mapRotation_s -{ - char name[ MAX_QPATH ]; - - mapRotationEntry_t maps[ MAX_MAP_ROTATION_MAPS ]; - int numMaps; - int currentMap; -} mapRotation_t; - -typedef struct mapRotations_s -{ - mapRotation_t rotations[ MAX_MAP_ROTATIONS ]; - int numRotations; -} mapRotations_t; - void G_PrintRotations( void ); -qboolean G_AdvanceMapRotation( void ); -qboolean G_StartMapRotation( char *name, qboolean changeMap ); +void G_AdvanceMapRotation( int depth ); +qboolean G_StartMapRotation( char *name, qboolean advance, + qboolean putOnStack, qboolean reset_index, int depth ); void G_StopMapRotation( void ); qboolean G_MapRotationActive( void ); void G_InitMapRotations( void ); -qboolean G_MapExists( char *name ); -int G_GetCurrentMap( int rotation ); - -qboolean G_CheckMapRotationVote( void ); -qboolean G_IntermissionMapVoteWinner( void ); -void G_IntermissionMapVoteMessage( gentity_t *ent ); -void G_IntermissionMapVoteMessageAll( void ); -void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ); +void G_ShutdownMapRotations( void ); +qboolean G_MapExists( const char *name ); +qboolean G_LayoutExists( const char *map, const char *layout ); +void G_ClearRotationStack( void ); // -// g_ptr.c +// g_namelog.c // -void G_UpdatePTRConnection( gclient_t *client ); -connectionRecord_t *G_GenerateNewConnection( gclient_t *client ); -void G_ResetPTRConnections( void ); -connectionRecord_t *G_FindConnectionForCode( int code ); +void G_namelog_connect( gclient_t *client ); +void G_namelog_disconnect( gclient_t *client ); +void G_namelog_restore( gclient_t *client ); +void G_namelog_update_score( gclient_t *client ); +void G_namelog_update_name( gclient_t *client ); +void G_namelog_cleanup( void ); //some maxs #define MAX_FILEPATH 144 @@ -1296,7 +1105,7 @@ connectionRecord_t *G_FindConnectionForCode( int code ); extern level_locals_t level; extern gentity_t g_entities[ MAX_GENTITIES ]; -#define FOFS(x) ((int)&(((gentity_t *)0)->x)) +#define FOFS(x) ((size_t)&(((gentity_t *)0)->x)) extern vmCvar_t g_dedicated; extern vmCvar_t g_cheats; @@ -1304,94 +1113,54 @@ extern vmCvar_t g_maxclients; // allow this many total, including spectato extern vmCvar_t g_maxGameClients; // allow this many active extern vmCvar_t g_restarted; extern vmCvar_t g_lockTeamsAtStart; -extern vmCvar_t g_minCommandPeriod; extern vmCvar_t g_minNameChangePeriod; extern vmCvar_t g_maxNameChanges; -extern vmCvar_t g_newbieNumbering; -extern vmCvar_t g_newbieNamePrefix; extern vmCvar_t g_timelimit; extern vmCvar_t g_suddenDeathTime; -extern vmCvar_t g_suddenDeath; -extern vmCvar_t g_suddenDeathMode; extern vmCvar_t g_friendlyFire; -extern vmCvar_t g_friendlyFireHumans; -extern vmCvar_t g_friendlyFireAliens; -extern vmCvar_t g_retribution; -extern vmCvar_t g_friendlyFireMovementAttacks; extern vmCvar_t g_friendlyBuildableFire; +extern vmCvar_t g_dretchPunt; extern vmCvar_t g_password; extern vmCvar_t g_needpass; extern vmCvar_t g_gravity; extern vmCvar_t g_speed; extern vmCvar_t g_knockback; -extern vmCvar_t g_quadfactor; extern vmCvar_t g_inactivity; extern vmCvar_t g_debugMove; -extern vmCvar_t g_debugAlloc; extern vmCvar_t g_debugDamage; -extern vmCvar_t g_weaponRespawn; -extern vmCvar_t g_weaponTeamRespawn; extern vmCvar_t g_synchronousClients; extern vmCvar_t g_motd; extern vmCvar_t g_warmup; -extern vmCvar_t g_warmupMode; extern vmCvar_t g_doWarmup; -extern vmCvar_t g_blood; extern vmCvar_t g_allowVote; -extern vmCvar_t g_requireVoteReasons; extern vmCvar_t g_voteLimit; extern vmCvar_t g_suddenDeathVotePercent; extern vmCvar_t g_suddenDeathVoteDelay; -extern vmCvar_t g_extendVotesPercent; -extern vmCvar_t g_extendVotesTime; -extern vmCvar_t g_extendVotesCount; -extern vmCvar_t g_kickVotesPercent; -extern vmCvar_t g_customVote1; -extern vmCvar_t g_customVote2; -extern vmCvar_t g_customVote3; -extern vmCvar_t g_customVote4; -extern vmCvar_t g_customVote5; -extern vmCvar_t g_customVote6; -extern vmCvar_t g_customVote7; -extern vmCvar_t g_customVote8; -#define CUSTOM_VOTE_COUNT 8 -extern vmCvar_t g_customVotePercent; -extern vmCvar_t g_mapVotesPercent; -extern vmCvar_t g_mapRotationVote; -extern vmCvar_t g_extendVotesPercent; -extern vmCvar_t g_extendVotesTime; -extern vmCvar_t g_extendVotesCount; -extern vmCvar_t g_readyPercent; -extern vmCvar_t g_designateVotes; -extern vmCvar_t g_teamAutoJoin; extern vmCvar_t g_teamForceBalance; -extern vmCvar_t g_banIPs; -extern vmCvar_t g_filterBan; extern vmCvar_t g_smoothClients; -extern vmCvar_t g_outdatedClientMessage; extern vmCvar_t pmove_fixed; extern vmCvar_t pmove_msec; -extern vmCvar_t g_rankings; -extern vmCvar_t g_allowShare; -extern vmCvar_t g_creditOverflow; -extern vmCvar_t g_enableDust; -extern vmCvar_t g_enableBreath; -extern vmCvar_t g_singlePlayer; -extern vmCvar_t g_humanBuildPoints; extern vmCvar_t g_alienBuildPoints; +extern vmCvar_t g_alienBuildQueueTime; +extern vmCvar_t g_humanBuildPoints; +extern vmCvar_t g_humanBuildQueueTime; +extern vmCvar_t g_humanRepeaterBuildPoints; +extern vmCvar_t g_humanRepeaterBuildQueueTime; +extern vmCvar_t g_humanRepeaterMaxZones; extern vmCvar_t g_humanStage; -extern vmCvar_t g_humanKills; +extern vmCvar_t g_humanCredits; extern vmCvar_t g_humanMaxStage; extern vmCvar_t g_humanStage2Threshold; extern vmCvar_t g_humanStage3Threshold; extern vmCvar_t g_alienStage; -extern vmCvar_t g_alienKills; +extern vmCvar_t g_alienCredits; extern vmCvar_t g_alienMaxStage; extern vmCvar_t g_alienStage2Threshold; extern vmCvar_t g_alienStage3Threshold; extern vmCvar_t g_teamImbalanceWarnings; +extern vmCvar_t g_freeFundPeriod; extern vmCvar_t g_unlagged; @@ -1400,120 +1169,60 @@ extern vmCvar_t g_disabledClasses; extern vmCvar_t g_disabledBuildables; extern vmCvar_t g_markDeconstruct; -extern vmCvar_t g_markDeconstructMode; -extern vmCvar_t g_deconDead; extern vmCvar_t g_debugMapRotation; extern vmCvar_t g_currentMapRotation; -extern vmCvar_t g_currentMap; +extern vmCvar_t g_mapRotationNodes; +extern vmCvar_t g_mapRotationStack; extern vmCvar_t g_nextMap; extern vmCvar_t g_initialMapRotation; -extern vmCvar_t g_chatTeamPrefix; -extern vmCvar_t g_actionPrefix; +extern vmCvar_t g_sayAreaRange; + +extern vmCvar_t g_debugVoices; +extern vmCvar_t g_voiceChats; + extern vmCvar_t g_floodMaxDemerits; extern vmCvar_t g_floodMinTime; -extern vmCvar_t g_spamTime; extern vmCvar_t g_shove; extern vmCvar_t g_mapConfigs; -extern vmCvar_t g_layouts; +extern vmCvar_t g_nextLayout; +extern vmCvar_t g_layouts[ 9 ]; extern vmCvar_t g_layoutAuto; +extern vmCvar_t g_emoticonsAllowedInNames; + extern vmCvar_t g_admin; -extern vmCvar_t g_adminLog; -extern vmCvar_t g_adminParseSay; -extern vmCvar_t g_adminSayFilter; -extern vmCvar_t g_adminNameProtect; -extern vmCvar_t g_adminTempMute; extern vmCvar_t g_adminTempBan; extern vmCvar_t g_adminMaxBan; -extern vmCvar_t g_adminTempSpec; -extern vmCvar_t g_adminMapLog; -extern vmCvar_t g_minLevelToJoinTeam; -extern vmCvar_t g_minDeconLevel; -extern vmCvar_t g_minDeconAffectsMark; -extern vmCvar_t g_forceAutoSelect; -extern vmCvar_t g_minLevelToSpecMM1; -extern vmCvar_t g_banNotice; - -extern vmCvar_t g_devmapKillerHP; -extern vmCvar_t g_killerHP; extern vmCvar_t g_privateMessages; -extern vmCvar_t g_fullIgnore; -extern vmCvar_t g_decolourLogfiles; -extern vmCvar_t g_publicSayadmins; -extern vmCvar_t g_myStats; -extern vmCvar_t g_teamStatus; -extern vmCvar_t g_antiSpawnBlock; - -extern vmCvar_t g_dretchPunt; - -extern vmCvar_t g_devmapNoGod; -extern vmCvar_t g_devmapNoStructDmg; - -extern vmCvar_t g_slapKnockback; -extern vmCvar_t g_slapDamage; - -extern vmCvar_t g_voteMinTime; -extern vmCvar_t g_mapvoteMaxTime; -extern vmCvar_t g_votableMaps; - -extern vmCvar_t g_msg; -extern vmCvar_t g_msgTime; -extern vmCvar_t g_welcomeMsg; -extern vmCvar_t g_welcomeMsgTime; -extern vmCvar_t g_deconBanTime; +extern vmCvar_t g_specChat; +extern vmCvar_t g_publicAdminMessages; +extern vmCvar_t g_allowTeamOverlay; -extern vmCvar_t g_buildLogMaxLength; +extern vmCvar_t g_censorship; -extern vmCvar_t g_AllStats; -extern vmCvar_t g_AllStatsTime; - -extern vmCvar_t mod_jetpackFuel; -extern vmCvar_t mod_jetpackConsume; -extern vmCvar_t mod_jetpackRegen; - -extern vmCvar_t g_adminExpireTime; - -extern vmCvar_t g_autoGhost; - -extern vmCvar_t g_teamKillThreshold; - -extern vmCvar_t g_aimbotAdvertBan; -extern vmCvar_t g_aimbotAdvertBanTime; -extern vmCvar_t g_aimbotAdvertBanReason; - -extern vmCvar_t g_Bubbles; -extern vmCvar_t g_scrimMode; -extern vmCvar_t g_gradualFreeFunds; -extern vmCvar_t g_bleedingSpree; -extern vmCvar_t g_schachtmeisterClearThreshold; -extern vmCvar_t g_schachtmeisterAutobahnThreshold; -extern vmCvar_t g_schachtmeisterAutobahnMessage; -extern vmCvar_t g_adminAutobahnNotify; - -void trap_Printf( const char *fmt ); -void trap_Error( const char *fmt ); +void trap_Print( const char *fmt ); +void trap_Error( const char *fmt ) __attribute__((noreturn)); int trap_Milliseconds( void ); int trap_RealTime( qtime_t *qtime ); int trap_Argc( void ); void trap_Argv( int n, char *buffer, int bufferLength ); void trap_Args( char *buffer, int bufferLength ); -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ); void trap_FS_Read( void *buffer, int len, fileHandle_t f ); void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); -int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t +int trap_FS_Seek( fileHandle_t f, long offset, enum FS_Mode origin ); // fsOrigin_t void trap_SendConsoleCommand( int exec_when, const char *text ); void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ); void trap_Cvar_Update( vmCvar_t *cvar ); void trap_Cvar_Set( const char *var_name, const char *value ); int trap_Cvar_VariableIntegerValue( const char *var_name ); -float trap_Cvar_VariableValue( const char *var_name ); void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, playerState_t *gameClients, int sizeofGameClient ); @@ -1521,6 +1230,7 @@ void trap_DropClient( int clientNum, const char *reason ); void trap_SendServerCommand( int clientNum, const char *text ); void trap_SetConfigstring( int num, const char *string ); void trap_GetConfigstring( int num, char *buffer, int bufferSize ); +void trap_SetConfigstringRestrictions( int num, const clientList_t *clientList ); void trap_GetUserinfo( int num, char *buffer, int bufferSize ); void trap_SetUserinfo( int num, const char *buffer ); void trap_GetServerinfo( char *buffer, int bufferSize ); @@ -1536,10 +1246,11 @@ void trap_LinkEntity( gentity_t *ent ); void trap_UnlinkEntity( gentity_t *ent ); int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); -int trap_BotAllocateClient( void ); -void trap_BotFreeClient( int clientNum ); void trap_GetUsercmd( int clientNum, usercmd_t *cmd ); qboolean trap_GetEntityToken( char *buffer, int bufferSize ); void trap_SnapVector( float *v ); -void trap_SendGameStat( const char *data ); + +void trap_AddCommand( const char *cmdName ); +void trap_RemoveCommand( const char *cmdName ); +int trap_FS_GetFilteredFiles( const char *path, const char *extension, const char *filter, char *listbuf, int bufsize ); diff --git a/src/game/g_main.c b/src/game/g_main.c index 6623959..f0f2158 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,44 +17,38 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "g_local.h" -#define QVM_NAME "AA-QVM" -#define QVM_VERSIONNUM "MULTIPROTOCOL" - level_locals_t level; typedef struct { vmCvar_t *vmCvar; - char *cvarName; - char *defaultString; - int cvarFlags; - int modificationCount; // for tracking changes - qboolean trackChange; // track this variable, and announce if changed - qboolean teamShader; // track and if changed, update shader state + char *cvarName; + char *defaultString; + int cvarFlags; + int modificationCount; // for tracking changes + qboolean trackChange; // track this variable, and announce if changed + /* certain cvars can be set in worldspawn, but we don't want those values to + persist, so keep track of non-worldspawn changes and restore that on map + end. unfortunately, if the server crashes, the value set in worldspawn may + persist */ + char *explicit; } cvarTable_t; gentity_t g_entities[ MAX_GENTITIES ]; gclient_t g_clients[ MAX_CLIENTS ]; -vmCvar_t g_fraglimit; vmCvar_t g_timelimit; vmCvar_t g_suddenDeathTime; -vmCvar_t g_suddenDeath; -vmCvar_t g_suddenDeathMode; -vmCvar_t g_capturelimit; vmCvar_t g_friendlyFire; -vmCvar_t g_friendlyFireAliens; -vmCvar_t g_friendlyFireHumans; -vmCvar_t g_friendlyFireMovementAttacks; -vmCvar_t g_retribution; vmCvar_t g_friendlyBuildableFire; +vmCvar_t g_dretchPunt; vmCvar_t g_password; vmCvar_t g_needpass; vmCvar_t g_maxclients; @@ -63,79 +58,47 @@ vmCvar_t g_speed; vmCvar_t g_gravity; vmCvar_t g_cheats; vmCvar_t g_knockback; -vmCvar_t g_quadfactor; vmCvar_t g_inactivity; vmCvar_t g_debugMove; vmCvar_t g_debugDamage; -vmCvar_t g_debugAlloc; -vmCvar_t g_weaponRespawn; -vmCvar_t g_weaponTeamRespawn; vmCvar_t g_motd; vmCvar_t g_synchronousClients; vmCvar_t g_warmup; -vmCvar_t g_warmupMode; vmCvar_t g_doWarmup; vmCvar_t g_restarted; vmCvar_t g_lockTeamsAtStart; vmCvar_t g_logFile; vmCvar_t g_logFileSync; -vmCvar_t g_blood; -vmCvar_t g_podiumDist; -vmCvar_t g_podiumDrop; vmCvar_t g_allowVote; -vmCvar_t g_requireVoteReasons; vmCvar_t g_voteLimit; vmCvar_t g_suddenDeathVotePercent; vmCvar_t g_suddenDeathVoteDelay; -vmCvar_t g_extendVotesPercent; -vmCvar_t g_extendVotesTime; -vmCvar_t g_extendVotesCount; -vmCvar_t g_kickVotesPercent; -vmCvar_t g_customVote1; -vmCvar_t g_customVote2; -vmCvar_t g_customVote3; -vmCvar_t g_customVote4; -vmCvar_t g_customVote5; -vmCvar_t g_customVote6; -vmCvar_t g_customVote7; -vmCvar_t g_customVote8; -vmCvar_t g_customVotePercent; -vmCvar_t g_mapVotesPercent; -vmCvar_t g_extendVotesPercent; -vmCvar_t g_extendVotesTime; -vmCvar_t g_extendVotesCount; -vmCvar_t g_mapRotationVote; -vmCvar_t g_readyPercent; -vmCvar_t g_designateVotes; -vmCvar_t g_teamAutoJoin; vmCvar_t g_teamForceBalance; -vmCvar_t g_banIPs; -vmCvar_t g_filterBan; vmCvar_t g_smoothClients; -vmCvar_t g_outdatedClientMessage; vmCvar_t pmove_fixed; vmCvar_t pmove_msec; -vmCvar_t g_rankings; -vmCvar_t g_listEntity; -vmCvar_t g_minCommandPeriod; vmCvar_t g_minNameChangePeriod; vmCvar_t g_maxNameChanges; -vmCvar_t g_newbieNumbering; -vmCvar_t g_newbieNamePrefix; -vmCvar_t g_humanBuildPoints; vmCvar_t g_alienBuildPoints; +vmCvar_t g_alienBuildQueueTime; +vmCvar_t g_humanBuildPoints; +vmCvar_t g_humanBuildQueueTime; +vmCvar_t g_humanRepeaterBuildPoints; +vmCvar_t g_humanRepeaterBuildQueueTime; +vmCvar_t g_humanRepeaterMaxZones; vmCvar_t g_humanStage; -vmCvar_t g_humanKills; +vmCvar_t g_humanCredits; vmCvar_t g_humanMaxStage; vmCvar_t g_humanStage2Threshold; vmCvar_t g_humanStage3Threshold; vmCvar_t g_alienStage; -vmCvar_t g_alienKills; +vmCvar_t g_alienCredits; vmCvar_t g_alienMaxStage; vmCvar_t g_alienStage2Threshold; vmCvar_t g_alienStage3Threshold; vmCvar_t g_teamImbalanceWarnings; +vmCvar_t g_freeFundPeriod; vmCvar_t g_unlagged; @@ -144,105 +107,52 @@ vmCvar_t g_disabledClasses; vmCvar_t g_disabledBuildables; vmCvar_t g_markDeconstruct; -vmCvar_t g_markDeconstructMode; -vmCvar_t g_deconDead; vmCvar_t g_debugMapRotation; vmCvar_t g_currentMapRotation; -vmCvar_t g_currentMap; +vmCvar_t g_mapRotationNodes; +vmCvar_t g_mapRotationStack; vmCvar_t g_nextMap; vmCvar_t g_initialMapRotation; +vmCvar_t g_debugVoices; +vmCvar_t g_voiceChats; + vmCvar_t g_shove; vmCvar_t g_mapConfigs; -vmCvar_t g_chatTeamPrefix; -vmCvar_t g_actionPrefix; +vmCvar_t g_sayAreaRange; + vmCvar_t g_floodMaxDemerits; vmCvar_t g_floodMinTime; -vmCvar_t g_spamTime; -vmCvar_t g_layouts; +vmCvar_t g_nextLayout; +vmCvar_t g_layouts[ 9 ]; vmCvar_t g_layoutAuto; +vmCvar_t g_emoticonsAllowedInNames; + vmCvar_t g_admin; -vmCvar_t g_adminLog; -vmCvar_t g_adminParseSay; -vmCvar_t g_adminSayFilter; -vmCvar_t g_adminNameProtect; -vmCvar_t g_adminTempMute; vmCvar_t g_adminTempBan; vmCvar_t g_adminMaxBan; -vmCvar_t g_adminTempSpec; -vmCvar_t g_adminMapLog; -vmCvar_t g_minLevelToJoinTeam; -vmCvar_t g_minDeconLevel; -vmCvar_t g_minDeconAffectsMark; -vmCvar_t g_forceAutoSelect; vmCvar_t g_privateMessages; -vmCvar_t g_fullIgnore; -vmCvar_t g_decolourLogfiles; -vmCvar_t g_minLevelToSpecMM1; -vmCvar_t g_publicSayadmins; -vmCvar_t g_myStats; -vmCvar_t g_AllStats; -vmCvar_t g_AllStatsTime; -vmCvar_t g_teamStatus; -vmCvar_t g_antiSpawnBlock; -vmCvar_t g_banNotice; - -vmCvar_t g_devmapKillerHP; -vmCvar_t g_killerHP; - -vmCvar_t g_buildLogMaxLength; - -vmCvar_t g_tag; - -vmCvar_t g_dretchPunt; - -vmCvar_t g_allowShare; -vmCvar_t g_creditOverflow; - -vmCvar_t g_devmapNoGod; -vmCvar_t g_devmapNoStructDmg; - -vmCvar_t g_slapKnockback; -vmCvar_t g_slapDamage; - -vmCvar_t g_voteMinTime; -vmCvar_t g_mapvoteMaxTime; -vmCvar_t g_votableMaps; +vmCvar_t g_specChat; +vmCvar_t g_publicAdminMessages; +vmCvar_t g_allowTeamOverlay; -vmCvar_t g_msg; -vmCvar_t g_msgTime; -vmCvar_t g_welcomeMsg; -vmCvar_t g_welcomeMsgTime; -vmCvar_t g_deconBanTime; +vmCvar_t g_censorship; +vmCvar_t g_tag; -vmCvar_t mod_jetpackFuel; -vmCvar_t mod_jetpackConsume; -vmCvar_t mod_jetpackRegen; - -vmCvar_t g_adminExpireTime; - -vmCvar_t g_autoGhost; - -vmCvar_t g_teamKillThreshold; - -vmCvar_t g_aimbotAdvertBan; -vmCvar_t g_aimbotAdvertBanTime; -vmCvar_t g_aimbotAdvertBanReason; -vmCvar_t g_Bubbles; -vmCvar_t g_scrimMode; -vmCvar_t g_gradualFreeFunds; -vmCvar_t g_bleedingSpree; -vmCvar_t g_schachtmeisterClearThreshold; -vmCvar_t g_schachtmeisterAutobahnThreshold; -vmCvar_t g_schachtmeisterAutobahnMessage; -vmCvar_t g_adminAutobahnNotify; +// copy cvars that can be set in worldspawn so they can be restored later +static char cv_gravity[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanMaxStage[ MAX_CVAR_VALUE_STRING ]; +static char cv_alienMaxStage[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanRepeaterBuildPoints[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanBuildPoints[ MAX_CVAR_VALUE_STRING ]; +static char cv_alienBuildPoints[ MAX_CVAR_VALUE_STRING ]; static cvarTable_t gameCvarTable[ ] = { @@ -251,223 +161,133 @@ static cvarTable_t gameCvarTable[ ] = // noset vars { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse }, { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse }, { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse }, { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - { NULL, "ff", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - { NULL, "qvm_version", QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, // latched vars { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse }, - { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, // change anytime vars - { &g_timelimit, "timelimit", "45", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, - { &g_suddenDeathTime, "g_suddenDeathTime", "30", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, - { &g_suddenDeathMode, "g_suddenDeathMode", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, - { &g_suddenDeath, "g_suddenDeath", "0", CVAR_SERVERINFO | CVAR_NORESTART, 0, qtrue }, + { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, - { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, + { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, - { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue }, - { &g_friendlyFireAliens, "g_friendlyFireAliens", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_friendlyFireHumans, "g_friendlyFireHumans", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_retribution, "g_retribution", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue }, - { &g_friendlyFireMovementAttacks, "g_friendlyFireMovementAttacks", "1", CVAR_ARCHIVE, 0, qtrue }, - { &g_devmapNoGod, "g_devmapNoGod", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_devmapNoStructDmg, "g_devmapNoStructDmg", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, - { &g_slapKnockback, "g_slapKnockback", "200", CVAR_ARCHIVE, 0, qfalse}, - { &g_slapDamage, "g_slapDamage", "0", CVAR_ARCHIVE, 0, qfalse}, + { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qtrue }, - { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE }, - { &g_teamForceBalance, "g_teamForceBalance", "1", CVAR_ARCHIVE }, + { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE, 0, qtrue }, { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue }, - { &g_warmupMode, "g_warmupMode", "1", CVAR_ARCHIVE, 0, qtrue }, - { &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue }, + { &g_doWarmup, "g_doWarmup", "0", CVAR_ARCHIVE, 0, qtrue }, { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse }, { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse }, - { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - - { &g_autoGhost, "g_autoGhost", "1", CVAR_SERVERINFO, 0, qfalse }, { &g_dedicated, "dedicated", "0", 0, 0, qfalse }, - { &g_speed, "g_speed", "320", CVAR_SERVERINFO, 0, qtrue }, - { &g_gravity, "g_gravity", "800", CVAR_SERVERINFO, 0, qtrue }, - { &g_knockback, "g_knockback", "1000", CVAR_SERVERINFO, 0, qtrue }, - { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue }, - { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue }, - { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue }, + { &g_speed, "g_speed", "320", 0, 0, qtrue }, + { &g_gravity, "g_gravity", "800", 0, 0, qtrue, cv_gravity }, + { &g_knockback, "g_knockback", "1000", 0, 0, qtrue }, { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue }, { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse }, { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse }, - { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse }, { &g_motd, "g_motd", "", 0, 0, qfalse }, - { &g_blood, "com_blood", "1", 0, 0, qfalse }, - - { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse }, - { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse }, { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_requireVoteReasons, "g_requireVoteReasons", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse }, - { &g_voteMinTime, "g_voteMinTime", "120", CVAR_ARCHIVE, 0, qfalse }, - { &g_mapvoteMaxTime, "g_mapvoteMaxTime", "240", CVAR_ARCHIVE, 0, qfalse }, - { &g_votableMaps, "g_votableMaps", "", CVAR_ARCHIVE, 0, qtrue }, { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse }, { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote1, "g_customVote1", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote2, "g_customVote2", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote3, "g_customVote3", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote4, "g_customVote4", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote5, "g_customVote5", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote6, "g_customVote6", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote7, "g_customVote7", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVote8, "g_customVote8", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_customVotePercent, "g_customVotePercent", "50", CVAR_ARCHIVE, 0, qfalse }, - { &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse }, - { &g_extendVotesPercent, "g_extendVotesPercent", "74", CVAR_ARCHIVE, 0, qfalse }, - { &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse }, - { &g_extendVotesCount, "g_extendVotesCount", "2", CVAR_ARCHIVE, 0, qfalse }, - { &g_mapRotationVote, "g_mapRotationVote", "15", CVAR_ARCHIVE, 0, qfalse }, - { &g_readyPercent, "g_readyPercent", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse }, - - { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse }, - { &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse}, { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse}, { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse}, - { &g_newbieNumbering, "g_newbieNumbering", "0", CVAR_ARCHIVE, 0, qfalse}, - { &g_newbieNamePrefix, "g_newbieNamePrefix", "Newbie#", CVAR_ARCHIVE, 0, qfalse}, { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, - { &g_outdatedClientMessage, "g_outdatedClientMessage", "", CVAR_ARCHIVE, 0, qfalse}, { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, - { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse }, - { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse }, + { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, 0, 0, qfalse, cv_alienBuildPoints }, + { &g_alienBuildQueueTime, "g_alienBuildQueueTime", DEFAULT_ALIEN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, 0, 0, qfalse, cv_humanBuildPoints }, + { &g_humanBuildQueueTime, "g_humanBuildQueueTime", DEFAULT_HUMAN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterBuildPoints, "g_humanRepeaterBuildPoints", DEFAULT_HUMAN_REPEATER_BUILDPOINTS, CVAR_ARCHIVE, 0, qfalse, cv_humanRepeaterBuildPoints }, + { &g_humanRepeaterMaxZones, "g_humanRepeaterMaxZones", DEFAULT_HUMAN_REPEATER_MAX_ZONES, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterBuildQueueTime, "g_humanRepeaterBuildQueueTime", DEFAULT_HUMAN_REPEATER_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse }, - { &g_humanKills, "g_humanKills", "0", 0, 0, qfalse }, - { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse }, + { &g_humanCredits, "g_humanCredits", "0", 0, 0, qfalse }, + { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse, cv_humanMaxStage }, { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse }, { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse }, { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse }, - { &g_alienKills, "g_alienKills", "0", 0, 0, qfalse }, - { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse }, + { &g_alienCredits, "g_alienCredits", "0", 0, 0, qfalse }, + { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse, cv_alienMaxStage }, { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, - { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse }, - + { &g_freeFundPeriod, "g_freeFundPeriod", DEFAULT_FREEKILL_PERIOD, CVAR_ARCHIVE, 0, qtrue }, + { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, - { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse }, - { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse }, - { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse }, + { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + + { &g_sayAreaRange, "g_sayAreaRange", "1000", CVAR_ARCHIVE, 0, qtrue }, - { &g_chatTeamPrefix, "g_chatTeamPrefix", "1", CVAR_ARCHIVE }, - { &g_actionPrefix, "g_actionPrefix", "* ", CVAR_ARCHIVE, 0, qfalse }, { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse }, { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse }, - { &g_spamTime, "g_spamTime", "2", CVAR_ARCHIVE, 0, qfalse }, - { &g_markDeconstruct, "g_markDeconstruct", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_markDeconstructMode, "g_markDeconstructMode", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_deconDead, "g_deconDead", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_markDeconstruct, "g_markDeconstruct", "3", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING - { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse }, + { &g_mapRotationNodes, "g_mapRotationNodes", "", CVAR_ROM, 0, qfalse }, + { &g_mapRotationStack, "g_mapRotationStack", "", CVAR_ROM, 0, qfalse }, { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue }, { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse }, + { &g_debugVoices, "g_debugVoices", "0", 0, 0, qfalse }, + { &g_voiceChats, "g_voiceChats", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse }, { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse }, - { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse }, + { &g_nextLayout, "g_nextLayout", "", 0, 0, qfalse }, + { &g_layouts[ 0 ], "g_layouts", "", 0, 0, qfalse }, + { &g_layouts[ 1 ], "g_layouts2", "", 0, 0, qfalse }, + { &g_layouts[ 2 ], "g_layouts3", "", 0, 0, qfalse }, + { &g_layouts[ 3 ], "g_layouts4", "", 0, 0, qfalse }, + { &g_layouts[ 4 ], "g_layouts5", "", 0, 0, qfalse }, + { &g_layouts[ 5 ], "g_layouts6", "", 0, 0, qfalse }, + { &g_layouts[ 6 ], "g_layouts7", "", 0, 0, qfalse }, + { &g_layouts[ 7 ], "g_layouts8", "", 0, 0, qfalse }, + { &g_layouts[ 8 ], "g_layouts9", "", 0, 0, qfalse }, { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_emoticonsAllowedInNames, "g_emoticonsAllowedInNames", "1", CVAR_LATCH|CVAR_ARCHIVE, 0, qfalse }, + { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminSayFilter, "g_adminSayFilter", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminTempMute, "g_adminTempMute", "5m", CVAR_ARCHIVE, 0, qfalse }, { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse }, { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminTempSpec, "g_adminTempSpec", "2m", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminMapLog, "g_adminMapLog", "", CVAR_ROM, 0, qfalse }, - { &g_minLevelToJoinTeam, "g_minLevelToJoinTeam", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_minDeconLevel, "g_minDeconLevel", "0", CVAR_ARCHIVE, 0, qfalse}, - { &g_minDeconAffectsMark, "g_minDeconAffectsMark", "0", CVAR_ARCHIVE, 0, qfalse}, - { &g_forceAutoSelect, "g_forceAutoSelect", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_adminExpireTime, "g_adminExpireTime", "0", CVAR_ARCHIVE, 0, qfalse }, - + { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_fullIgnore, "g_fullIgnore", "1", CVAR_ARCHIVE, 0, qtrue }, - { &g_decolourLogfiles, "g_decolourLogfiles", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_buildLogMaxLength, "g_buildLogMaxLength", "50", CVAR_ARCHIVE, 0, qfalse }, - { &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qtrue }, - { &g_AllStats, "g_AllStats", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_AllStatsTime, "g_AllStatsTime", "60", CVAR_ARCHIVE, 0, qfalse }, - { &g_teamStatus, "g_teamStatus", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_publicSayadmins, "g_publicSayadmins", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_minLevelToSpecMM1, "g_minLevelToSpecMM1", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_antiSpawnBlock, "g_antiSpawnBlock", "0", CVAR_ARCHIVE, 0, qfalse }, - - { &g_devmapKillerHP, "g_devmapKillerHP", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_killerHP, "g_killerHP", "0", CVAR_ARCHIVE, 0, qtrue }, - - { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse }, - - { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qfalse }, - - { &g_msg, "g_msg", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_msgTime, "g_msgTime", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_welcomeMsg, "g_welcomeMsg", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_welcomeMsgTime, "g_welcomeMsgTime", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_deconBanTime, "g_deconBanTime", "2h", CVAR_ARCHIVE, 0, qfalse }, - - { &g_rankings, "g_rankings", "0", 0, 0, qfalse }, - { &g_allowShare, "g_allowShare", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse}, - { &g_creditOverflow, "g_creditOverflow", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse}, - { &g_banNotice, "g_banNotice", "", CVAR_ARCHIVE, 0, qfalse }, - - { &mod_jetpackFuel, "mod_jetpackFuel", "0", CVAR_ARCHIVE, 0, qtrue }, - { &mod_jetpackConsume, "mod_jetpackConsume", "2", CVAR_ARCHIVE, 0, qfalse }, - { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse }, - - { &g_teamKillThreshold, "g_teamKillThreshold", "0", CVAR_ARCHIVE, 0, qfalse }, - - { &g_aimbotAdvertBan, "g_aimbotAdvertBan", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_aimbotAdvertBanTime, "g_aimbotAdvertBanTime", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_aimbotAdvertBanReason, "g_aimbotAdvertBanReason", "AUTOBAN: AIMBOT", CVAR_ARCHIVE, 0, qfalse }, - - { &g_Bubbles, "g_Bubbles", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_scrimMode, "g_scrimMode", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_gradualFreeFunds, "g_gradualFreeFunds", "2", CVAR_ARCHIVE, 0, qtrue }, - { &g_bleedingSpree, "g_bleedingSpree", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_gradualFreeFunds, "g_gradualFreeFunds", "2", CVAR_ARCHIVE, 0, qtrue }, - { &g_schachtmeisterClearThreshold, "g_schachtmeisterClearThreshold", "-10", CVAR_ARCHIVE, 0, qfalse }, - { &g_schachtmeisterAutobahnThreshold, "g_schachtmeisterAutobahnThreshold", "-30", CVAR_ARCHIVE, 0, qfalse }, - { &g_schachtmeisterAutobahnMessage, "g_schachtmeisterAutobahnMessage", "Your host is blacklisted.", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminAutobahnNotify, "g_adminAutobahnNotify", "1", CVAR_ARCHIVE, 0, qfalse } + { &g_specChat, "g_specChat", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_publicAdminMessages, "g_publicAdminMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_allowTeamOverlay, "g_allowTeamOverlay", "1", CVAR_ARCHIVE, 0, qtrue }, + + { &g_censorship, "g_censorship", "", CVAR_ARCHIVE, 0, qfalse }, + + { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse } }; -static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); +static size_t gameCvarTableSize = ARRAY_LEN( gameCvarTable ); void G_InitGame( int levelTime, int randomSeed, int restart ); @@ -486,9 +306,7 @@ This is the only way control passes into the module. This must be the very first function compiled into the .q3vm file ================ */ -Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, - int arg5, int arg6, int arg7, int arg8, int arg9, - int arg10, int arg11 ) +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2 ) { switch( command ) { @@ -541,10 +359,10 @@ void QDECL G_Printf( const char *fmt, ... ) char text[ 1024 ]; va_start( argptr, fmt ); - vsprintf( text, fmt, argptr ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); - trap_Printf( text ); + trap_Print( text ); } void QDECL G_Error( const char *fmt, ... ) @@ -553,7 +371,7 @@ void QDECL G_Error( const char *fmt, ... ) char text[ 1024 ]; va_start( argptr, fmt ); - vsprintf( text, fmt, argptr ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); trap_Error( text ); @@ -579,11 +397,8 @@ void G_FindTeams( void ) c = 0; c2 = 0; - for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ ) + for( i = MAX_CLIENTS, e = g_entities + i; i < level.num_entities; i++, e++ ) { - if( !e->inuse ) - continue; - if( !e->team ) continue; @@ -596,9 +411,6 @@ void G_FindTeams( void ) for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ ) { - if( !e2->inuse ) - continue; - if( !e2->team ) continue; @@ -626,10 +438,6 @@ void G_FindTeams( void ) G_Printf( "%i teams with %i entities\n", c, c2 ); } -void G_RemapTeamShaders( void ) -{ -} - /* ================= @@ -640,7 +448,6 @@ void G_RegisterCvars( void ) { int i; cvarTable_t *cv; - qboolean remapped = qfalse; for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) { @@ -650,12 +457,9 @@ void G_RegisterCvars( void ) if( cv->vmCvar ) cv->modificationCount = cv->vmCvar->modificationCount; - if( cv->teamShader ) - remapped = qtrue; + if( cv->explicit ) + strcpy( cv->explicit, cv->vmCvar->string ); } - - if( remapped ) - G_RemapTeamShaders( ); } /* @@ -667,7 +471,6 @@ void G_UpdateCvars( void ) { int i; cvarTable_t *cv; - qboolean remapped = qfalse; for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) { @@ -680,21 +483,31 @@ void G_UpdateCvars( void ) cv->modificationCount = cv->vmCvar->modificationCount; if( cv->trackChange ) - { trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"", cv->cvarName, cv->vmCvar->string ) ); - // update serverinfo in case this cvar is passed to clients indirectly - CalculateRanks( ); - } - if( cv->teamShader ) - remapped = qtrue; + if( !level.spawning && cv->explicit ) + strcpy( cv->explicit, cv->vmCvar->string ); } } } +} + +/* +================= +G_RestoreCvars +================= +*/ +void G_RestoreCvars( void ) +{ + int i; + cvarTable_t *cv; - if( remapped ) - G_RemapTeamShaders( ); + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + if( cv->vmCvar && cv->explicit ) + trap_Cvar_Set( cv->cvarName, cv->explicit ); + } } /* @@ -713,7 +526,7 @@ void G_MapConfigs( const char *mapname ) trap_SendConsoleCommand( EXEC_APPEND, va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) ); - + trap_SendConsoleCommand( EXEC_APPEND, va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) ); @@ -736,11 +549,8 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) G_Printf( "------- Game Initialization -------\n" ); G_Printf( "gamename: %s\n", GAME_VERSION ); - G_Printf( "gamedate: %s\n", __DATE__ ); - G_ProcessIPBans( ); - - G_InitMemory( ); + BG_InitMemory( ); // set some level globals memset( &level, 0, sizeof( level ) ); @@ -751,9 +561,6 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime - trap_Cvar_Set( "qvm_version", - QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")" ); - if( g_logFile.string[ 0 ] ) { if( g_logFileSync.integer ) @@ -767,17 +574,15 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) { char serverinfo[ MAX_INFO_STRING ]; qtime_t qt; - int t; - trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); G_LogPrintf( "------------------------------------------------------------\n" ); G_LogPrintf( "InitGame: %s\n", serverinfo ); - t = trap_RealTime( &qt ); - G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n", - qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, + trap_RealTime( &qt ); + G_LogPrintf("RealTime: %04i-%02i-%02i %02i:%02i:%02i\n", + qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, qt.tm_hour, qt.tm_min, qt.tm_sec ); } @@ -785,19 +590,28 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) else G_Printf( "Not logging to disk\n" ); + if( g_mapConfigs.string[ 0 ] && !trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) ) { char map[ MAX_CVAR_VALUE_STRING ] = {""}; + G_Printf( "InitGame: executing map configuration scripts and restarting\n" ); trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); G_MapConfigs( map ); + trap_SendConsoleCommand( EXEC_APPEND, "wait\nmap_restart 0\n" ); + } + else + { + // we're done with g_mapConfigs, so reset this for the next map + trap_Cvar_Set( "g_mapConfigsLoaded", "0" ); } - // we're done with g_mapConfigs, so reset this for the next map - trap_Cvar_Set( "g_mapConfigsLoaded", "0" ); + // set this cvar to 0 if it exists, but otherwise avoid its creation + if( trap_Cvar_VariableIntegerValue( "g_rangeMarkerWarningGiven" ) ) + trap_Cvar_Set( "g_rangeMarkerWarningGiven", "0" ); - if ( g_admin.string[ 0 ] ) { - G_admin_readconfig( NULL, 0 ); - } + G_RegisterCommands( ); + G_admin_readconfig( NULL ); + G_LoadCensors( ); // initialize all entities for this game memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) ); @@ -817,26 +631,29 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) // range are NEVER anything but clients level.num_entities = MAX_CLIENTS; + for( i = 0; i < MAX_CLIENTS; i++ ) + g_entities[ i ].classname = "clientslot"; + // let the server system know where the entites are trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) ); + level.emoticonCount = BG_LoadEmoticons( level.emoticons, MAX_EMOTICONS ); + trap_SetConfigstring( CS_INTERMISSION, "0" ); - // update maplog - G_admin_maplog_update( ); + G_InitPlayerModel(); // test to see if a custom buildable layout will be loaded G_LayoutSelect( ); + // this has to be flipped after the first UpdateCvars + level.spawning = qtrue; // parse the key/value pairs and spawn gentities G_SpawnEntitiesFromString( ); // load up a custom building layout if there is one - G_LayoutLoad( ); - - // load any nobuild markers that have been saved - G_NobuildLoad( ); + G_LayoutLoad( level.layout ); // the map might disable some things BG_InitAllowedGameElements( ); @@ -844,9 +661,8 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) // general initialization G_FindTeams( ); - //TA: - BG_InitClassOverrides( ); - BG_InitBuildableOverrides( ); + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); G_InitDamageLocations( ); G_InitMapRotations( ); G_InitSpawnQueue( &level.alienSpawnQueue ); @@ -855,27 +671,27 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) if( g_debugMapRotation.integer ) G_PrintRotations( ); + level.voices = BG_VoiceInit( ); + BG_PrintVoices( level.voices, g_debugVoices.integer ); + //reset stages trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) ); trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) ); - trap_Cvar_Set( "g_alienKills", 0 ); - trap_Cvar_Set( "g_humanKills", 0 ); - trap_Cvar_Set( "g_suddenDeath", 0 ); + trap_Cvar_Set( "g_alienCredits", 0 ); + trap_Cvar_Set( "g_humanCredits", 0 ); level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; G_Printf( "-----------------------------------\n" ); - G_RemapTeamShaders( ); - - //TA: so the server counts the spawns without a client attached + // So the server counts the spawns without a client attached G_CountSpawns( ); - G_ResetPTRConnections( ); - - if(g_lockTeamsAtStart.integer) + G_UpdateTeamConfigStrings( ); + + if( g_lockTeamsAtStart.integer ) { - level.alienTeamLocked=qtrue; - level.humanTeamLocked=qtrue; + level.alienTeamLocked = qtrue; + level.humanTeamLocked = qtrue; trap_Cvar_Set( "g_lockTeamsAtStart", "0" ); } } @@ -889,15 +705,13 @@ remove all currently active votes */ static void G_ClearVotes( void ) { - level.voteTime = 0; - trap_SetConfigstring( CS_VOTE_TIME, "" ); - trap_SetConfigstring( CS_VOTE_STRING, "" ); - level.teamVoteTime[ 0 ] = 0; - trap_SetConfigstring( CS_TEAMVOTE_TIME, "" ); - trap_SetConfigstring( CS_TEAMVOTE_STRING, "" ); - level.teamVoteTime[ 1 ] = 0; - trap_SetConfigstring( CS_TEAMVOTE_TIME + 1, "" ); - trap_SetConfigstring( CS_TEAMVOTE_STRING + 1, "" ); + int i; + memset( level.voteTime, 0, sizeof( level.voteTime ) ); + for( i = 0; i < NUM_TEAMS; i++ ) + { + trap_SetConfigstring( CS_VOTE_TIME + i, "" ); + trap_SetConfigstring( CS_VOTE_STRING + i, "" ); + } } /* @@ -910,6 +724,8 @@ void G_ShutdownGame( int restart ) // in case of a map_restart G_ClearVotes( ); + G_RestoreCvars( ); + G_Printf( "==== ShutdownGame ====\n" ); if( level.logFile ) @@ -917,20 +733,21 @@ void G_ShutdownGame( int restart ) G_LogPrintf( "ShutdownGame:\n" ); G_LogPrintf( "------------------------------------------------------------\n" ); trap_FS_FCloseFile( level.logFile ); + level.logFile = 0; } - // write admin.dat for !seen data - admin_writeconfig(); - // write all the client session data so we can get it back G_WriteSessionData( ); G_admin_cleanup( ); - G_admin_namelog_cleanup( ); - G_admin_adminlog_cleanup( ); + G_namelog_cleanup( ); + G_UnregisterCommands( ); + + G_FreePlayerModel( ); + G_ShutdownMapRotations( ); level.restarted = qfalse; - level.surrenderTeam = PTE_NONE; + level.surrenderTeam = TEAM_NONE; trap_SetConfigstring( CS_WINNER, "" ); } @@ -944,7 +761,7 @@ void QDECL Com_Error( int level, const char *error, ... ) char text[ 1024 ]; va_start( argptr, error ); - vsprintf( text, error, argptr ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); va_end( argptr ); G_Error( "%s", text ); @@ -956,7 +773,7 @@ void QDECL Com_Printf( const char *msg, ... ) char text[ 1024 ]; va_start( argptr, msg ); - vsprintf( text, msg, argptr ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); G_Printf( "%s", text ); @@ -985,9 +802,9 @@ int QDECL SortRanks( const void *a, const void *b ) cb = &level.clients[ *(int *)b ]; // then sort by score - if( ca->pers.score > cb->pers.score ) + if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] ) return -1; - else if( ca->pers.score < cb->pers.score ) + if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] ) return 1; else return 0; @@ -1016,7 +833,7 @@ void G_InitSpawnQueue( spawnQueue_t *sq ) ============ G_GetSpawnQueueLength -Return tha length of a spawn queue +Return the length of a spawn queue ============ */ int G_GetSpawnQueueLength( spawnQueue_t *sq ) @@ -1047,6 +864,7 @@ int G_PopSpawnQueue( spawnQueue_t *sq ) { sq->clients[ sq->front ] = -1; sq->front = QUEUE_PLUS1( sq->front ); + G_StopFollowing( g_entities + clientNum ); g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; return clientNum; @@ -1079,8 +897,11 @@ qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum ) int i; for( i = 0; i < MAX_CLIENTS; i++ ) + { if( sq->clients[ i ] == clientNum ) return qtrue; + } + return qfalse; } @@ -1211,24 +1032,20 @@ G_SpawnClients Spawn queued clients ============ */ -void G_SpawnClients( pTeam_t team ) +void G_SpawnClients( team_t team ) { int clientNum; gentity_t *ent, *spawn; vec3_t spawn_origin, spawn_angles; spawnQueue_t *sq = NULL; int numSpawns = 0; - if( g_doWarmup.integer && ( g_warmupMode.integer==1 || g_warmupMode.integer == 2 ) && - level.time - level.startTime < g_warmup.integer * 1000 ) - { - return; - } - if( team == PTE_ALIENS ) + + if( team == TEAM_ALIENS ) { sq = &level.alienSpawnQueue; numSpawns = level.numAlienSpawns; } - else if( team == PTE_HUMANS ) + else if( team == TEAM_HUMANS ) { sq = &level.humanSpawnQueue; numSpawns = level.numHumanSpawns; @@ -1250,7 +1067,7 @@ void G_SpawnClients( pTeam_t team ) ent = &g_entities[ clientNum ]; - ent->client->sess.sessionTeam = TEAM_FREE; + ent->client->sess.spectatorState = SPECTATOR_NOT; ClientUserinfoChanged( clientNum, qfalse ); ClientSpawn( ent, spawn, spawn_origin, spawn_angles ); } @@ -1271,33 +1088,31 @@ void G_CountSpawns( void ) level.numAlienSpawns = 0; level.numHumanSpawns = 0; - for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) { - if( !ent->inuse ) + if( !ent->inuse || ent->s.eType != ET_BUILDABLE || ent->health <= 0 ) continue; - if( ent->s.modelindex == BA_A_SPAWN && ent->health > 0 ) + if( ent->s.modelindex == BA_A_SPAWN ) level.numAlienSpawns++; - if( ent->s.modelindex == BA_H_SPAWN && ent->health > 0 ) + if( ent->s.modelindex == BA_H_SPAWN ) level.numHumanSpawns++; } - - //let the client know how many spawns there are - trap_SetConfigstring( CS_SPAWNS, va( "%d %d", - level.numAlienSpawns, level.numHumanSpawns ) ); } + /* ============ G_TimeTilSuddenDeath ============ */ +#define SUDDENDEATHWARNING 60000 int G_TimeTilSuddenDeath( void ) { - if( (!g_suddenDeathTime.integer && level.suddenDeathBeginTime==0 ) || level.suddenDeathBeginTime<0 ) - return 999999999; // Always some time away + if( ( !g_suddenDeathTime.integer && level.suddenDeathBeginTime == 0 ) || + ( level.suddenDeathBeginTime < 0 ) ) + return SUDDENDEATHWARNING + 1; // Always some time away return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) ); } @@ -1314,180 +1129,152 @@ Recalculate the quantity of building points available to the teams */ void G_CalculateBuildPoints( void ) { - int i; - buildable_t buildable; - gentity_t *ent; - int localHTP = g_humanBuildPoints.integer, - localATP = g_alienBuildPoints.integer; - - // g_suddenDeath sets what state we want it to be. - // level.suddenDeath says whether we've calculated BPs at the 'start' of SD or not + int i; + buildable_t buildable; + buildPointZone_t *zone; - // reset if SD was on, but now it's off - if(!g_suddenDeath.integer && level.suddenDeath) + // BP queue updates + while( level.alienBuildPointQueue > 0 && + level.alienNextQueueTime < level.time ) { - level.suddenDeath = qfalse; - level.suddenDeathWarning = 0; - level.suddenDeathBeginTime = -1; - if((level.time - level.startTime) < (g_suddenDeathTime.integer * 60000 ) ) - level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; - else - level.suddenDeathBeginTime = -1; + level.alienBuildPointQueue--; + level.alienNextQueueTime += G_NextQueueTime( level.alienBuildPointQueue, + g_alienBuildPoints.integer, + g_alienBuildQueueTime.integer ); } - if(!level.suddenDeath) + while( level.humanBuildPointQueue > 0 && + level.humanNextQueueTime < level.time ) { - if(g_suddenDeath.integer || G_TimeTilSuddenDeath( ) <= 0 ) //Conditions to enter SD - { - //begin sudden death - if( level.suddenDeathWarning < TW_PASSED ) - { - trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); - G_LogPrintf("Beginning Sudden Death (Mode %d)\n",g_suddenDeathMode.integer); - localHTP = 0; - localATP = 0; + level.humanBuildPointQueue--; + level.humanNextQueueTime += G_NextQueueTime( level.humanBuildPointQueue, + g_humanBuildPoints.integer, + g_humanBuildQueueTime.integer ); + } - if( g_suddenDeathMode.integer == SDMODE_SELECTIVE ) - { - for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) - { - if( ent->s.eType != ET_BUILDABLE ) - continue; - - if( BG_FindReplaceableTestForBuildable( ent->s.modelindex ) ) - { - int t = BG_FindTeamForBuildable( ent->s.modelindex ); - - if( t == BIT_HUMANS ) - localHTP += BG_FindBuildPointsForBuildable( ent->s.modelindex ); - else if( t == BIT_ALIENS ) - localATP += BG_FindBuildPointsForBuildable( ent->s.modelindex ); - } - } - } - level.suddenDeathHBuildPoints = localHTP; - level.suddenDeathABuildPoints = localATP; - level.suddenDeathBeginTime = level.time; - level.suddenDeath=qtrue; - trap_Cvar_Set( "g_suddenDeath", "1" ); + // Sudden Death checks + if( G_TimeTilSuddenDeath( ) <= 0 && level.suddenDeathWarning < TW_PASSED ) + { + G_LogPrintf( "Beginning Sudden Death\n" ); + trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); + trap_SendServerCommand( -1, "print \"Beginning Sudden Death.\n\"" ); + level.suddenDeathWarning = TW_PASSED; + G_ClearDeconMarks( ); - level.suddenDeathWarning = TW_PASSED; - } - } - else + // Clear blueprints, or else structs that cost 0 BP can still be built after SD + for( i = 0; i < level.maxclients; i++ ) { - //warn about sudden death - if( ( G_TimeTilSuddenDeath( ) <= 60000 ) && - ( level.suddenDeathWarning < TW_IMMINENT ) ) - { - trap_SendServerCommand( -1, va("cp \"Sudden Death in %d seconds!\"", - (int)(G_TimeTilSuddenDeath() / 1000 ) ) ); - level.suddenDeathWarning = TW_IMMINENT; - } + if( g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) + g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; } } - - //set BP at each cycle - if( g_suddenDeath.integer ) + else if( G_TimeTilSuddenDeath( ) <= SUDDENDEATHWARNING && + level.suddenDeathWarning < TW_IMMINENT ) { - localHTP = level.suddenDeathHBuildPoints; - localATP = level.suddenDeathABuildPoints; + trap_SendServerCommand( -1, va( "cp \"Sudden Death in %d seconds!\"", + (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); + trap_SendServerCommand( -1, va( "print \"Sudden Death will begin in %d seconds.\n\"", + (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); + level.suddenDeathWarning = TW_IMMINENT; } - else + + level.humanBuildPoints = g_humanBuildPoints.integer - level.humanBuildPointQueue; + level.alienBuildPoints = g_alienBuildPoints.integer - level.alienBuildPointQueue; + + // Reset buildPointZones + for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) { - localHTP = g_humanBuildPoints.integer; - localATP = g_alienBuildPoints.integer; + buildPointZone_t *zone = &level.buildPointZones[ i ]; + + zone->active = qfalse; + zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; } - level.humanBuildPoints = level.humanBuildPointsPowered = localHTP; - level.alienBuildPoints = localATP; + // Iterate through entities + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + gentity_t *ent = &g_entities[ i ]; + buildPointZone_t *zone; + buildable_t buildable; + int cost; - level.reactorPresent = qfalse; - level.overmindPresent = qfalse; + if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD ) + continue; - for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + // mark a zone as active + if( ent->usesBuildPointZone ) + { + assert( ent->buildPointZone >= 0 && ent->buildPointZone < g_humanRepeaterMaxZones.integer ); + + zone = &level.buildPointZones[ ent->buildPointZone ]; + zone->active = qtrue; + } + + // Subtract the BP from the appropriate pool + buildable = ent->s.modelindex; + cost = BG_Buildable( buildable )->buildPoints; + + if( ent->buildableTeam == TEAM_ALIENS ) + level.alienBuildPoints -= cost; + if( buildable == BA_H_REPEATER ) + level.humanBuildPoints -= cost; + else if( buildable != BA_H_REACTOR ) + { + gentity_t *power = G_PowerEntityForEntity( ent ); + + if( power ) + { + if( power->s.modelindex == BA_H_REACTOR ) + level.humanBuildPoints -= cost; + else if( power->s.modelindex == BA_H_REPEATER && power->usesBuildPointZone ) + level.buildPointZones[ power->buildPointZone ].totalBuildPoints -= cost; + } + } + } + + // Finally, update repeater zones and their queues + // note that this has to be done after the used BP is calculated + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { - if( !ent->inuse ) - continue; + gentity_t *ent = &g_entities[ i ]; - if( ent->s.eType != ET_BUILDABLE ) + if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD || + ent->buildableTeam != TEAM_HUMANS ) continue; buildable = ent->s.modelindex; - if( buildable != BA_NONE ) - { - if( buildable == BA_H_REACTOR && ent->spawned && ent->health > 0 ) - level.reactorPresent = qtrue; + if( buildable != BA_H_REPEATER ) + continue; - if( buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0 ) - level.overmindPresent = qtrue; + if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) + { + zone = &level.buildPointZones[ ent->buildPointZone ]; - if( !g_suddenDeath.integer || BG_FindReplaceableTestForBuildable( buildable ) ) + if( G_TimeTilSuddenDeath( ) > 0 ) { - if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS ) - { - level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); - if( ent->powered ) - level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable ); - } - else + // BP queue updates + while( zone->queuedBuildPoints > 0 && + zone->nextQueueTime < level.time ) { - level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); + zone->queuedBuildPoints--; + zone->nextQueueTime += G_NextQueueTime( zone->queuedBuildPoints, + zone->totalBuildPoints, + g_humanRepeaterBuildQueueTime.integer ); } } + else + { + zone->totalBuildPoints = zone->queuedBuildPoints = 0; + } } } if( level.humanBuildPoints < 0 ) - { - localHTP -= level.humanBuildPoints; - level.humanBuildPointsPowered -= level.humanBuildPoints; level.humanBuildPoints = 0; - } if( level.alienBuildPoints < 0 ) - { - localATP -= level.alienBuildPoints; level.alienBuildPoints = 0; - } - - trap_SetConfigstring( CS_BUILDPOINTS, va( "%d %d %d %d %d", - level.alienBuildPoints, localATP, - level.humanBuildPoints, localHTP, - level.humanBuildPointsPowered ) ); - - //may as well pump the stages here too - { - float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD; - float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD; - int alienNextStageThreshold, humanNextStageThreshold; - - if( alienPlayerCountMod < 0.1f ) - alienPlayerCountMod = 0.1f; - - if( humanPlayerCountMod < 0.1f ) - humanPlayerCountMod = 0.1f; - - if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) - alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); - else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) - alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); - else - alienNextStageThreshold = -1; - - if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) - humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); - else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) - humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); - else - humanNextStageThreshold = -1; - - trap_SetConfigstring( CS_STAGES, va( "%d %d %d %d %d %d", - g_alienStage.integer, g_humanStage.integer, - g_alienKills.integer, g_humanKills.integer, - alienNextStageThreshold, humanNextStageThreshold ) ); - } } /* @@ -1499,8 +1286,11 @@ void G_CalculateStages( void ) { float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD; float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD; + int alienNextStageThreshold, humanNextStageThreshold; static int lastAlienStageModCount = 1; static int lastHumanStageModCount = 1; + static int alienTriggerStage = 0; + static int humanTriggerStage = 0; if( alienPlayerCountMod < 0.1f ) alienPlayerCountMod = 0.1f; @@ -1508,7 +1298,7 @@ void G_CalculateStages( void ) if( humanPlayerCountMod < 0.1f ) humanPlayerCountMod = 0.1f; - if( g_alienKills.integer >= + if( g_alienCredits.integer >= (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) && g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) { @@ -1518,7 +1308,7 @@ void G_CalculateStages( void ) G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n"); } - if( g_alienKills.integer >= + if( g_alienCredits.integer >= (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) && g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) { @@ -1528,7 +1318,7 @@ void G_CalculateStages( void ) G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n"); } - if( g_humanKills.integer >= + if( g_humanCredits.integer >= (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) && g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) { @@ -1538,30 +1328,33 @@ void G_CalculateStages( void ) G_LogPrintf("Stage: H 2: Humans reached Stage 2\n"); } - if( g_humanKills.integer >= + if( g_humanCredits.integer >= (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) && g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) { trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) ); level.humanStage3Time = level.time; - G_LogPrintf("Stage: H 3: Humans reached Stage 3\n"); lastHumanStageModCount = g_humanStage.modificationCount; + G_LogPrintf("Stage: H 3: Humans reached Stage 3\n"); } - + if( g_alienStage.modificationCount > lastAlienStageModCount ) { - G_Checktrigger_stages( PTE_ALIENS, g_alienStage.integer ); - if( g_alienStage.integer == S2 ) + while( alienTriggerStage < MIN( g_alienStage.integer, S3 ) ) + G_Checktrigger_stages( TEAM_ALIENS, ++alienTriggerStage ); + + if( g_alienStage.integer == S2 ) level.alienStage2Time = level.time; else if( g_alienStage.integer == S3 ) level.alienStage3Time = level.time; - + lastAlienStageModCount = g_alienStage.modificationCount; } - + if( g_humanStage.modificationCount > lastHumanStageModCount ) { - G_Checktrigger_stages( PTE_HUMANS, g_humanStage.integer ); + while( humanTriggerStage < MIN( g_humanStage.integer, S3 ) ) + G_Checktrigger_stages( TEAM_HUMANS, ++humanTriggerStage ); if( g_humanStage.integer == S2 ) level.humanStage2Time = level.time; @@ -1570,6 +1363,35 @@ void G_CalculateStages( void ) lastHumanStageModCount = g_humanStage.modificationCount; } + + if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) + alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); + else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) + alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); + else + alienNextStageThreshold = -1; + + if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) + humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); + else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) + humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); + else + humanNextStageThreshold = -1; + + // save a lot of bandwidth by rounding thresholds up to the nearest 100 + if( alienNextStageThreshold > 0 ) + alienNextStageThreshold = ceil( (float)alienNextStageThreshold / 100 ) * 100; + + if( humanNextStageThreshold > 0 ) + humanNextStageThreshold = ceil( (float)humanNextStageThreshold / 100 ) * 100; + + trap_SetConfigstring( CS_ALIEN_STAGES, va( "%d %d %d", + g_alienStage.integer, g_alienCredits.integer, + alienNextStageThreshold ) ); + + trap_SetConfigstring( CS_HUMAN_STAGES, va( "%d %d %d", + g_humanStage.integer, g_humanCredits.integer, + humanNextStageThreshold ) ); } /* @@ -1587,13 +1409,13 @@ void G_CalculateAvgPlayers( void ) if( !level.numAlienClients ) { level.numAlienSamples = 0; - trap_Cvar_Set( "g_alienKills", "0" ); + trap_Cvar_Set( "g_alienCredits", "0" ); } if( !level.numHumanClients ) { level.numHumanSamples = 0; - trap_Cvar_Set( "g_humanKills", "0" ); + trap_Cvar_Set( "g_humanCredits", "0" ); } //calculate average number of clients for stats @@ -1623,16 +1445,14 @@ void CalculateRanks( void ) { int i; char P[ MAX_CLIENTS + 1 ] = {""}; - int ff = 0; level.numConnectedClients = 0; - level.numNonSpectatorClients = 0; level.numPlayingClients = 0; - level.numVotingClients = 0; // don't count bots + memset( level.numVotingClients, 0, sizeof( level.numVotingClients ) ); level.numAlienClients = 0; level.numHumanClients = 0; - level.numLiveAlienClients = 0; - level.numLiveHumanClients = 0; + level.numAlienClientsAlive = 0; + level.numHumanClientsAlive = 0; for( i = 0; i < level.maxclients; i++ ) { @@ -1643,46 +1463,36 @@ void CalculateRanks( void ) level.numConnectedClients++; P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection; + level.numVotingClients[ TEAM_NONE ]++; + if( level.clients[ i ].pers.connected != CON_CONNECTED ) continue; - level.numVotingClients++; - if( level.clients[ i ].pers.teamSelection != PTE_NONE ) + if( level.clients[ i ].pers.teamSelection != TEAM_NONE ) { level.numPlayingClients++; - if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) - level.numNonSpectatorClients++; - - if( level.clients[ i ].pers.teamSelection == PTE_ALIENS ) + if( level.clients[ i ].pers.teamSelection == TEAM_ALIENS ) { level.numAlienClients++; - if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) - level.numLiveAlienClients++; + if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) + level.numAlienClientsAlive++; } - else if( level.clients[ i ].pers.teamSelection == PTE_HUMANS ) + else if( level.clients[ i ].pers.teamSelection == TEAM_HUMANS ) { level.numHumanClients++; - if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) - level.numLiveHumanClients++; + if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) + level.numHumanClientsAlive++; } } } } - level.numteamVotingClients[ 0 ] = level.numHumanClients; - level.numteamVotingClients[ 1 ] = level.numAlienClients; + level.numNonSpectatorClients = level.numAlienClientsAlive + + level.numHumanClientsAlive; + level.numVotingClients[ TEAM_ALIENS ] = level.numAlienClients; + level.numVotingClients[ TEAM_HUMANS ] = level.numHumanClients; P[ i ] = '\0'; trap_Cvar_Set( "P", P ); - if( g_friendlyFire.value>0 ) - ff |= ( FFF_HUMANS | FFF_ALIENS ); - if( g_friendlyFireHumans.value>0 ) - ff |= FFF_HUMANS; - if( g_friendlyFireAliens.value>0 ) - ff |= FFF_ALIENS; - if( g_friendlyBuildableFire.value>0 ) - ff |= FFF_BUILDABLES; - trap_Cvar_Set( "ff", va( "%i", ff ) ); - qsort( level.sortedClients, level.numConnectedClients, sizeof( level.sortedClients[ 0 ] ), SortRanks ); @@ -1690,7 +1500,7 @@ void CalculateRanks( void ) CheckExitRules( ); // if we are at the intermission, send the new info to everyone - if( level.intermissiontime && !level.mapRotationVoteTime ) + if( level.intermissiontime ) SendScoreboardMessageToAllClients( ); } @@ -1737,8 +1547,10 @@ void MoveClientToIntermission( gentity_t *ent ) G_StopFollowing( ent ); // move to the spot - VectorCopy( level.intermission_origin, ent->s.origin ); + VectorCopy( level.intermission_origin, ent->s.pos.trBase ); + VectorCopy( level.intermission_origin, ent->r.currentOrigin ); VectorCopy( level.intermission_origin, ent->client->ps.origin ); + VectorCopy( level.intermission_angle, ent->s.apos.trBase ); VectorCopy( level.intermission_angle, ent->client->ps.viewangles ); ent->client->ps.pm_type = PM_INTERMISSION; @@ -1748,7 +1560,6 @@ void MoveClientToIntermission( gentity_t *ent ) ent->client->ps.eFlags = 0; ent->s.eFlags = 0; ent->s.eType = ET_GENERAL; - ent->s.modelindex = 0; ent->s.loopSound = 0; ent->s.event = 0; ent->r.contents = 0; @@ -1775,8 +1586,8 @@ void FindIntermissionPoint( void ) } else { - VectorCopy( ent->s.origin, level.intermission_origin ); - VectorCopy( ent->s.angles, level.intermission_angle ); + VectorCopy( ent->r.currentOrigin, level.intermission_origin ); + VectorCopy( ent->r.currentAngles, level.intermission_angle ); // if it has a target, look towards it if( ent->target ) { @@ -1784,7 +1595,7 @@ void FindIntermissionPoint( void ) if( target ) { - VectorSubtract( target->s.origin, level.intermission_origin, dir ); + VectorSubtract( target->r.currentOrigin, level.intermission_origin, dir ); vectoangles( dir, level.intermission_angle ); } } @@ -1805,12 +1616,12 @@ void BeginIntermission( void ) if( level.intermissiontime ) return; // already active - level.numTeamWarnings = 99; - level.intermissiontime = level.time; G_ClearVotes( ); + G_UpdateTeamConfigStrings( ); + FindIntermissionPoint( ); // move all clients to the intermission point @@ -1830,42 +1641,8 @@ void BeginIntermission( void ) // send the current scoring to all clients SendScoreboardMessageToAllClients( ); - - if( g_nextMap.string[ 0 ] ) - { - trap_SendServerCommand( -1, - va( "print \"next map has been set to %s^7%s\n\"", - g_nextMap.string, - ( G_CheckMapRotationVote() ) ? ", voting will be skipped" : "" ) ); - } } -void BeginMapRotationVote( void ) -{ - gentity_t *ent; - int length; - int i; - - if( level.mapRotationVoteTime ) - return; - - length = g_mapRotationVote.integer; - if( length > 60 ) - length = 60; - level.mapRotationVoteTime = level.time + ( length * 1000 ); - - for( i = 0; i < level.maxclients; i++ ) - { - ent = g_entities + i; - - if( !ent->inuse ) - continue; - - ent->client->ps.pm_type = PM_SPECTATOR; - ent->client->sess.sessionTeam = TEAM_SPECTATOR; - ent->client->sess.spectatorState = SPECTATOR_LOCKED; - } -} /* ============= @@ -1879,44 +1656,22 @@ void ExitLevel( void ) { int i; gclient_t *cl; - buildHistory_t *tmp, *mark; - - char currentmap[ MAX_CVAR_VALUE_STRING ]; - - trap_Cvar_VariableStringBuffer( "mapname", currentmap, sizeof( currentmap )); - - if( level.mapRotationVoteTime ) - { - if( level.time < level.mapRotationVoteTime && - !G_IntermissionMapVoteWinner( ) ) - return; - } - else if( g_mapRotationVote.integer > 0 && - G_CheckMapRotationVote() && - !g_nextMap.string[ 0 ] ) - { - BeginMapRotationVote( ); - return; - } - while( ( tmp = level.buildHistory ) ) + if ( G_MapExists( g_nextMap.string ) ) { - level.buildHistory = level.buildHistory->next; - while( ( mark = tmp ) ) - { - tmp = tmp->marked; - G_Free( mark ); - } + G_MapConfigs( g_nextMap.string ); + trap_SendConsoleCommand( EXEC_APPEND, va( "%smap \"%s\"\n", + ( g_cheats.integer ? "dev" : "" ), g_nextMap.string ) ); } - - if( !Q_stricmp( currentmap, g_nextMap.string ) ) - trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); - else if ( G_MapExists( g_nextMap.string ) ) - trap_SendConsoleCommand( EXEC_APPEND, va("!map %s\n", g_nextMap.string ) ); else if( G_MapRotationActive( ) ) - G_AdvanceMapRotation( ); + G_AdvanceMapRotation( 0 ); else + { + char map[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + G_MapConfigs( map ); trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + } trap_Cvar_Set( "g_nextMap", "" ); @@ -1934,7 +1689,7 @@ void ExitLevel( void ) cl->ps.persistant[ PERS_SCORE ] = 0; } - // we need to do this here before chaning to CON_CONNECTING + // we need to do this here before changing to CON_CONNECTING G_WriteSessionData( ); // change all client states to connecting, so the early players into the @@ -1946,79 +1701,48 @@ void ExitLevel( void ) } } + /* ================= -G_AdminsPrintf +G_AdminMessage -Print to all active admins, and the logfile with a time stamp if it is open, and to the console +Print to all active server admins, and to the logfile, and to the server console ================= */ -void QDECL G_AdminsPrintf( const char *fmt, ... ) +void G_AdminMessage( gentity_t *ent, const char *msg ) { - va_list argptr; char string[ 1024 ]; - gentity_t *tempent; - int j; - - va_start( argptr, fmt ); - vsprintf( string, fmt,argptr ); - va_end( argptr ); + int i; - for( j = 0; j < level.maxclients; j++ ) - { - tempent = &g_entities[ j ]; - if( G_admin_permission( tempent, ADMF_ADMINCHAT ) && - !tempent->client->pers.ignoreAdminWarnings ) - { - trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Admins]^7 %s\"", string) ); - } - } - - G_LogPrintf("%s",string); + Com_sprintf( string, sizeof( string ), "chat %d %d \"%s\"", + (int)( ent ? ent - g_entities : -1 ), + G_admin_permission( ent, ADMF_ADMINCHAT ) ? SAY_ADMINS : SAY_ADMINS_PUBLIC, + msg ); + // Send to all appropriate clients + for( i = 0; i < level.maxclients; i++ ) + if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) + trap_SendServerCommand( i, string ); + + // Send to the logfile and server console + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_MAGENTA "%s\n", + G_admin_permission( ent, ADMF_ADMINCHAT ) ? "AdminMsg" : "AdminMsgPublic", + (int)( ent ? ent - g_entities : -1 ), ent ? ent->client->pers.netname : "console", + msg ); } -/* -================= -G_WarningsPrintf - -Print to everyone with a certain flag, and the logfile with a time stamp if it is open, and to the console -(just a copy of the G_AdminsPrintf with flag suport) -================= -*/ -void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... ) -{ - va_list argptr; - char string[ 1024 ]; - gentity_t *tempent; - int j; - va_start( argptr, fmt ); - vsprintf( string, fmt,argptr ); - va_end( argptr ); - - for( j = 0; j < level.maxclients; j++ ) - { - tempent = &g_entities[ j ]; - if( G_admin_permission( tempent, flag ) ) - { - trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Warnings]^7 %s\"", string) ); - } - } - - G_LogPrintf("%s",string); -} /* ================= G_LogPrintf -Print to the logfile with a time stamp if it is open +Print to the logfile with a time stamp if it is open, and to the server console ================= */ void QDECL G_LogPrintf( const char *fmt, ... ) { va_list argptr; - char string[ 1024 ], decoloured[ 1024 ]; + char string[ 1024 ], decolored[ 1024 ]; int min, tens, sec; sec = ( level.time - level.startTime ) / 1000; @@ -2031,186 +1755,20 @@ void QDECL G_LogPrintf( const char *fmt, ... ) Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); va_start( argptr, fmt ); - vsprintf( string +7 , fmt,argptr ); + Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr ); va_end( argptr ); if( g_dedicated.integer ) - G_Printf( "%s", string + 7 ); - - if( !level.logFile ) - return; - - if( g_decolourLogfiles.integer ) - { - G_DecolorString( string, decoloured ); - trap_FS_Write( decoloured, strlen( decoloured ), level.logFile ); - } - else { - trap_FS_Write( string, strlen( string ), level.logFile ); + G_UnEscapeString( string, decolored, sizeof( decolored ) ); + G_Printf( "%s", decolored + 7 ); } -} - -/* -================= -G_LogPrintfColoured - -Bypasses g_decolourLogfiles for events that need colors in the logs -================= -*/ -void QDECL G_LogPrintfColoured( const char *fmt, ... ) -{ - va_list argptr; - char string[ 1024 ]; - int min, tens, sec; - - sec = (level.time - level.startTime) / 1000; - - min = sec / 60; - sec -= min * 60; - tens = sec / 10; - sec -= tens * 10; - - Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); - - va_start( argptr, fmt ); - vsprintf( string +7 , fmt,argptr ); - va_end( argptr ); - - if( g_dedicated.integer ) - G_Printf( "%s", string + 7 ); if( !level.logFile ) return; - trap_FS_Write( string, strlen( string ), level.logFile ); -} - -/* -================= -G_LogOnlyPrintf - -Print to the logfile only (not console) with a time stamp if it is open -================= -*/ -void QDECL G_LogOnlyPrintf( const char *fmt, ... ) -{ - va_list argptr; - char string[ 1024 ], decoloured[ 1024 ]; - int min, tens, sec; - - sec = (level.time - level.startTime) / 1000; - - min = sec / 60; - sec -= min * 60; - tens = sec / 10; - sec -= tens * 10; - - Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); - - va_start( argptr, fmt ); - vsprintf( string +7 , fmt,argptr ); - va_end( argptr ); - - if( !level.logFile ) - return; - - if( g_decolourLogfiles.integer ) - { - G_DecolorString( string, decoloured ); - trap_FS_Write( decoloured, strlen( decoloured ), level.logFile ); - } - else - { - trap_FS_Write( string, strlen( string ), level.logFile ); - } -} - -/* -================= -G_SendGameStat -================= -*/ -void G_SendGameStat( pTeam_t team ) -{ - char map[ MAX_STRING_CHARS ]; - char teamChar; - char data[ BIG_INFO_STRING ]; - char entry[ MAX_STRING_CHARS ]; - int i, dataLength, entryLength; - gclient_t *cl; - - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - - switch( team ) - { - case PTE_ALIENS: teamChar = 'A'; break; - case PTE_HUMANS: teamChar = 'H'; break; - case PTE_NONE: teamChar = 'L'; break; - default: return; - } - - Com_sprintf( data, BIG_INFO_STRING, - "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d", - Q3_VERSION, - g_tag.string, - teamChar, - level.averageNumAlienClients, - level.averageNumHumanClients, - map, - level.time - level.startTime, - G_TimeTilSuddenDeath( ), - g_alienStage.integer, - level.alienStage2Time - level.startTime, - level.alienStage3Time - level.startTime, - g_humanStage.integer, - level.humanStage2Time - level.startTime, - level.humanStage3Time - level.startTime, - level.numConnectedClients ); - - dataLength = strlen( data ); - - for( i = 0; i < level.numConnectedClients; i++ ) - { - int ping; - - cl = &level.clients[ level.sortedClients[ i ] ]; - - // Ignore invisible players - if ( cl->sess.invisible == qtrue ) - continue; - - if( cl->pers.connected == CON_CONNECTING ) - ping = -1; - else - ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - - switch( cl->ps.stats[ STAT_PTEAM ] ) - { - case PTE_ALIENS: teamChar = 'A'; break; - case PTE_HUMANS: teamChar = 'H'; break; - case PTE_NONE: teamChar = 'S'; break; - default: return; - } - - Com_sprintf( entry, MAX_STRING_CHARS, - " \"%s\" %c %d %d %d", - cl->pers.netname, - teamChar, - cl->ps.persistant[ PERS_SCORE ], - ping, - ( level.time - cl->pers.enterTime ) / 60000 ); - - entryLength = strlen( entry ); - - if( dataLength + entryLength >= BIG_INFO_STRING ) - break; - - strcpy( data + dataLength, entry ); - dataLength += entryLength; - } - - trap_SendGameStat( data ); + G_DecolorString( string, decolored, sizeof( decolored ) ); + trap_FS_Write( decolored, strlen( decolored ), level.logFile ); } /* @@ -2226,6 +1784,8 @@ void LogExit( const char *string ) gclient_t *cl; gentity_t *ent; + level.exited = qtrue; + G_LogPrintf( "Exit: %s\n", string ); level.intermissionQueued = level.time; @@ -2245,7 +1805,7 @@ void LogExit( const char *string ) cl = &level.clients[ level.sortedClients[ i ] ]; - if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE ) + if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) continue; if( cl->pers.connected == CON_CONNECTING ) @@ -2270,8 +1830,6 @@ void LogExit( const char *string ) ent->use( ent, ent, ent ); } } - - G_SendGameStat( level.lastWin ); } @@ -2287,20 +1845,13 @@ wait 10 seconds before going on. */ void CheckIntermissionExit( void ) { - int ready, notReady, numPlayers; + int ready, notReady; int i; gclient_t *cl; - int readyMask; + clientList_t readyMasks; //if no clients are connected, just exit - if( !level.numConnectedClients ) - { - ExitLevel( ); - return; - } - - // map vote started - if( level.mapRotationVoteTime ) + if( level.numConnectedClients == 0 ) { ExitLevel( ); return; @@ -2309,30 +1860,28 @@ void CheckIntermissionExit( void ) // see which players are ready ready = 0; notReady = 0; - readyMask = 0; - numPlayers = 0; + Com_Memset( &readyMasks, 0, sizeof( readyMasks ) ); for( i = 0; i < g_maxclients.integer; i++ ) { cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) continue; - if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE ) + if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) continue; if( cl->readyToExit ) { ready++; - if( i < 16 ) - readyMask |= 1 << i; + + Com_ClientListAdd( &readyMasks, i ); } else notReady++; - - numPlayers++; } - trap_SetConfigstring( CS_CLIENTS_READY, va( "%d", readyMask ) ); + trap_SetConfigstring( CS_CLIENTS_READY, Com_ClientListString( &readyMasks ) ); // never exit in less than five seconds if( level.time < level.intermissiontime + 5000 ) @@ -2346,22 +1895,14 @@ void CheckIntermissionExit( void ) } // if nobody wants to go, clear timer - if( !ready && numPlayers ) + if( ready == 0 && notReady > 0 ) { level.readyToExit = qfalse; return; } // if everyone wants to go, go now - if( !notReady ) - { - ExitLevel( ); - return; - } - - // if only a percent is needed to ready, check for it - if( g_readyPercent.integer && numPlayers && - ready * 100 / numPlayers >= g_readyPercent.integer ) + if( notReady == 0 ) { ExitLevel( ); return; @@ -2434,11 +1975,10 @@ void CheckExitRules( void ) { if( level.time - level.startTime >= g_timelimit.integer * 60000 ) { - level.lastWin = PTE_NONE; + level.lastWin = TEAM_NONE; trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" ); trap_SetConfigstring( CS_WINNER, "Stalemate" ); LogExit( "Timelimit hit." ); - G_admin_maplog_result( "t" ); return; } else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 && @@ -2456,295 +1996,177 @@ void CheckExitRules( void ) } if( level.uncondHumanWin || - ( ( level.time > level.startTime + 1000 ) && + ( !level.uncondAlienWin && + ( level.time > level.startTime + 1000 ) && ( level.numAlienSpawns == 0 ) && - ( level.numLiveAlienClients == 0 ) ) ) + ( level.numAlienClientsAlive == 0 ) ) ) { //humans win - level.lastWin = PTE_HUMANS; + level.lastWin = TEAM_HUMANS; trap_SendServerCommand( -1, "print \"Humans win\n\""); trap_SetConfigstring( CS_WINNER, "Humans Win" ); LogExit( "Humans win." ); - G_admin_maplog_result( "h" ); } else if( level.uncondAlienWin || ( ( level.time > level.startTime + 1000 ) && ( level.numHumanSpawns == 0 ) && - ( level.numLiveHumanClients == 0 ) ) ) + ( level.numHumanClientsAlive == 0 ) ) ) { //aliens win - level.lastWin = PTE_ALIENS; + level.lastWin = TEAM_ALIENS; trap_SendServerCommand( -1, "print \"Aliens win\n\""); trap_SetConfigstring( CS_WINNER, "Aliens Win" ); LogExit( "Aliens win." ); - G_admin_maplog_result( "a" ); } } - - -/* -======================================================================== - -FUNCTIONS CALLED EVERY FRAME - -======================================================================== -*/ - - /* ================== -CheckVote +G_Vote ================== */ -void CheckVote( void ) +void G_Vote( gentity_t *ent, team_t team, qboolean voting ) { - int votePassThreshold=level.votePassThreshold; - int voteYesPercent; - - if( level.voteExecuteTime && level.voteExecuteTime < level.time ) - { - level.voteExecuteTime = 0; - - if( !Q_stricmp( level.voteString, "map_restart" ) ) - { - G_admin_maplog_result( "r" ); - } - else if( !Q_stricmpn( level.voteString, "map", 3 ) ) - { - G_admin_maplog_result( "m" ); - } - - - if( !Q_stricmp( level.voteString, "suddendeath" ) ) - { - level.suddenDeathBeginTime = level.time + ( 1000 * g_suddenDeathVoteDelay.integer ) - level.startTime; - - level.voteString[0] = '\0'; - - if( g_suddenDeathVoteDelay.integer ) - trap_SendServerCommand( -1, va("cp \"Sudden Death will begin in %d seconds\n\"", g_suddenDeathVoteDelay.integer ) ); - } - - if( level.voteString[0] ) - trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); + if( !level.voteTime[ team ] ) + return; - if( !Q_stricmp( level.voteString, "map_restart" ) || - !Q_stricmpn( level.voteString, "map", 3 ) ) - { - level.restarted = qtrue; - } - } + if( voting && ent->client->pers.voted & ( 1 << team ) ) + return; - if( !level.voteTime ) + if( !voting && !( ent->client->pers.voted & ( 1 << team ) ) ) return; - if( level.voteYes + level.voteNo > 0 ) - voteYesPercent = (int)( 100 * ( level.voteYes ) / ( level.voteYes + level.voteNo ) ); - else - voteYesPercent = 0; - - if( ( level.time - level.voteTime >= VOTE_TIME ) || - ( level.voteYes + level.voteNo == level.numConnectedClients ) ) + ent->client->pers.voted |= 1 << team; + + if( ent->client->pers.vote & ( 1 << team ) ) { - if( voteYesPercent> votePassThreshold || level.voteNo == 0 ) - { - // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf( "Vote: Vote passed (%d-%d)\n", level.voteYes, level.voteNo ); - level.voteExecuteTime = level.time + 3000; - } + if( voting ) + level.voteYes[ team ]++; else - { - // same behavior as a timeout - trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf( "Vote: Vote failed (%d - %d)\n", level.voteYes, level.voteNo ); - } + level.voteYes[ team ]--; + + trap_SetConfigstring( CS_VOTE_YES + team, + va( "%d", level.voteYes[ team ] ) ); } else { - if( level.voteYes > (int)( (double) level.numConnectedClients * - ( (double) votePassThreshold/100.0 ) ) ) - { - // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf( "Vote: Vote passed (%d - %d)\n", level.voteYes, level.voteNo ); - level.voteExecuteTime = level.time + 3000; - } - else if( level.voteNo > (int)( (double) level.numConnectedClients * - ( (double) ( 100.0-votePassThreshold )/ 100.0 ) ) ) - { - // same behavior as a timeout - trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf("Vote failed\n"); - } + if( voting ) + level.voteNo[ team ]++; else - { - // still waiting for a majority - return; - } - } + level.voteNo[ team ]--; - level.voteTime = 0; - trap_SetConfigstring( CS_VOTE_TIME, "" ); - trap_SetConfigstring( CS_VOTE_STRING, "" ); + trap_SetConfigstring( CS_VOTE_NO + team, + va( "%d", level.voteNo[ team ] ) ); + } } /* -================== -CheckTeamVote -================== +======================================================================== + +FUNCTIONS CALLED EVERY FRAME + +======================================================================== */ -void CheckTeamVote( int team ) -{ - int cs_offset; - if ( team == PTE_HUMANS ) - cs_offset = 0; - else if ( team == PTE_ALIENS ) - cs_offset = 1; - else - return; - if( !level.teamVoteTime[ cs_offset ] ) - return; +void G_ExecuteVote( team_t team ) +{ + level.voteExecuteTime[ team ] = 0; - if( level.time - level.teamVoteTime[ cs_offset ] >= VOTE_TIME ) + if( !Q_stricmpn( level.voteString[ team ], "map_restart", 11 ) ) { - if( level.teamVoteYes[ cs_offset ] > level.teamVoteNo[ cs_offset ] && level.teamVoteYes[ cs_offset ] >= 2 ) - { - // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Team vote passed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) ); - } - else - { - trap_SendServerCommand( -1, va("print \"Team vote failed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); - } + char map[ MAX_QPATH ]; + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + G_MapConfigs( map ); } - else + else if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) ) { - if( level.teamVoteYes[ cs_offset ] > level.numteamVotingClients[ cs_offset ] / 2 ) - { - // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Team vote passed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - G_LogPrintf( "Teamvote: Team vote passed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); - // - trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) ); - } - else if( level.teamVoteNo[ cs_offset ] >= level.numteamVotingClients[ cs_offset ] / 2 ) - { - // same behavior as a timeout - trap_SendServerCommand( -1, va("print \"Team vote failed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); - } - else - { - // still waiting for a majority - return; - } + char map[ MAX_QPATH ]; + char *p; + Q_strncpyz( map, strchr( level.voteString[ team ], '"' ) + 1, sizeof( map ) ); + if( ( p = strchr( map, '"' ) ) ) + *p = '\0'; + G_MapConfigs( map ); } - level.teamVoteTime[ cs_offset ] = 0; - trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" ); - trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, "" ); + trap_SendConsoleCommand( EXEC_APPEND, va( "%s%s\n", + ( !Q_stricmp( level.voteString[ team ], "map" ) && g_cheats.integer ? "dev" : "" ), + level.voteString[ team ] ) ); + + if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) ) + level.restarted = qtrue; } /* ================== -CheckMsgTimer +G_CheckVote ================== */ -void CheckMsgTimer( void ) +void G_CheckVote( team_t team ) { - static int LastTime = 0; + float votePassThreshold = (float)level.voteThreshold[ team ] / 100.0f; + qboolean pass = qfalse; + const char *msg; + int i; - if( level.time - LastTime < 1000 ) - return; + if( level.voteExecuteTime[ team ] && + level.voteExecuteTime[ team ] < level.time ) + { + G_ExecuteVote( team ); + } - LastTime = level.time; + if( !level.voteTime[ team ] ) + return; - if( level.mapRotationVoteTime ) + if( ( level.time - level.voteTime[ team ] >= VOTE_TIME ) || + ( level.voteYes[ team ] + level.voteNo[ team ] == level.numVotingClients[ team ] ) ) { - G_IntermissionMapVoteMessageAll( ); - return; + pass = ( level.voteYes[ team ] && + (float)level.voteYes[ team ] / ( (float)level.voteYes[ team ] + (float)level.voteNo[ team ] ) > votePassThreshold ); } - - if( g_welcomeMsgTime.integer && g_welcomeMsg.string[ 0 ] ) + else { - char buffer[ MAX_STRING_CHARS ]; - int wt; - int i; - - buffer[ 0 ] = '\0'; - wt = g_welcomeMsgTime.integer * 1000; - for( i = 0; i < level.maxclients; i++ ) + if( (float)level.voteYes[ team ] > + (float)level.numVotingClients[ team ] * votePassThreshold ) { - if( level.clients[ i ].pers.connected != CON_CONNECTED ) - continue; - - if( level.time - level.clients[ i ].pers.enterTime < wt ) - { - if( buffer[ 0 ] == '\0' ) - { - Q_strncpyz( buffer, g_welcomeMsg.string, sizeof( buffer ) ); - G_ParseEscapedString( buffer ); - } - trap_SendServerCommand( i, va( "cp \"%s\"", buffer ) ); - } + pass = qtrue; + } + else if( (float)level.voteNo[ team ] <= + (float)level.numVotingClients[ team ] * ( 1.0f - votePassThreshold ) ) + { + return; } } - if( !g_msgTime.integer ) - return; + if( pass ) + level.voteExecuteTime[ team ] = level.time + level.voteDelay[ team ]; - if( level.time - level.lastMsgTime < abs( g_msgTime.integer ) * 60000 ) - return; + G_LogPrintf( "EndVote: %s %s %d %d %d\n", + team == TEAM_NONE ? "global" : BG_TeamName( team ), + pass ? "pass" : "fail", + level.voteYes[ team ], level.voteNo[ team ], level.numVotingClients[ team ] ); - // negative settings only print once per map - if( ( level.lastMsgTime ) && g_msgTime.integer < 0 ) - return; + msg = va( "print \"%sote %sed (%d - %d)\n\"", + team == TEAM_NONE ? "V" : "Team v", pass ? "pass" : "fail", + level.voteYes[ team ], level.voteNo[ team ] ); - level.lastMsgTime = level.time; - - if( g_msg.string[0] ) - { - char buffer[ MAX_STRING_CHARS ]; - - Q_strncpyz( buffer, g_msg.string, sizeof( buffer ) ); - G_ParseEscapedString( buffer ); - trap_SendServerCommand( -1, va( "cp \"%s\"", buffer ) ); - trap_SendServerCommand( -1, va( "print \"%s\n\"", buffer ) ); - } -} - -/* -================== -CheckCountdown -================== -*/ -void CheckCountdown( void ) -{ - static int lastmsg = 0; - int timeleft = g_warmup.integer - ( level.time - level.startTime ) / 1000; + if( team == TEAM_NONE ) + trap_SendServerCommand( -1, msg ); + else + G_TeamCommand( team, msg ); - if( !g_doWarmup.integer || timeleft < 0 ) - return; + level.voteTime[ team ] = 0; + level.voteYes[ team ] = 0; + level.voteNo[ team ] = 0; - if( level.time - lastmsg < 1000 ) - return; + for( i = 0; i < level.maxclients; i++ ) + level.clients[ i ].pers.voted &= ~( 1 << team ); - lastmsg = level.time; - if( timeleft > 0 ) - trap_SendServerCommand( -1, va( "cp \"^1Warmup Time:^7\n^%i----- ^7%i ^%i-----\"", timeleft % 7, timeleft, timeleft % 7 ) ); - else if( timeleft == 0 ) - trap_SendServerCommand( -1, "cp \"^2----- GO! -----^7\"" ); + trap_SetConfigstring( CS_VOTE_TIME + team, "" ); + trap_SetConfigstring( CS_VOTE_STRING + team, "" ); + trap_SetConfigstring( CS_VOTE_YES + team, "0" ); + trap_SetConfigstring( CS_VOTE_NO + team, "0" ); } @@ -2758,6 +2180,7 @@ void CheckCvars( void ) static int lastPasswordModCount = -1; static int lastMarkDeconModCount = -1; static int lastSDTimeModCount = -1; + static int lastNumZones = 0; if( g_password.modificationCount != lastPasswordModCount ) { @@ -2773,29 +2196,36 @@ void CheckCvars( void ) // the server setting is changed if( g_markDeconstruct.modificationCount != lastMarkDeconModCount ) { - int i; - gentity_t *ent; - lastMarkDeconModCount = g_markDeconstruct.modificationCount; - - for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) - { - if( !ent->inuse ) - continue; - - if( ent->s.eType != ET_BUILDABLE ) - continue; - - ent->deconstruct = qfalse; - } + G_ClearDeconMarks( ); } + // If we change g_suddenDeathTime during a map, we need to update + // when sd will begin if( g_suddenDeathTime.modificationCount != lastSDTimeModCount ) { lastSDTimeModCount = g_suddenDeathTime.modificationCount; level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; } + // If the number of zones changes, we need a new array + if( g_humanRepeaterMaxZones.integer != lastNumZones ) + { + buildPointZone_t *newZones; + size_t newsize = g_humanRepeaterMaxZones.integer * sizeof( buildPointZone_t ); + size_t oldsize = lastNumZones * sizeof( buildPointZone_t ); + + newZones = BG_Alloc( newsize ); + if( level.buildPointZones ) + { + Com_Memcpy( newZones, level.buildPointZones, MIN( oldsize, newsize ) ); + BG_Free( level.buildPointZones ); + } + + level.buildPointZones = newZones; + lastNumZones = g_humanRepeaterMaxZones.integer; + } + level.frameMsec = trap_Milliseconds( ); } @@ -2855,47 +2285,62 @@ Advances the non-player objects in the world */ void G_RunFrame( int levelTime ) { - int i; - gentity_t *ent; - int msec; - int start, end; + int i; + gentity_t *ent; + int msec; + static int ptime3000 = 0; // if we are waiting for the level to restart, do nothing if( level.restarted ) return; - - if( level.paused ) + + if( level.pausedTime ) { - if( ( levelTime % 6000 ) == 0) - trap_SendServerCommand( -1, "cp \"^3Game is paused.\"" ); + msec = levelTime - level.time - level.pausedTime; + level.pausedTime = levelTime - level.time; + + ptime3000 += msec; + while( ptime3000 > 3000 ) + { + ptime3000 -= 3000; + trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); - level.startTime += levelTime - level.time; - trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) ); + if( level.pausedTime >= 110000 && level.pausedTime <= 119000 ) + trap_SendServerCommand( -1, va( "print \"Server: Game will auto-unpause in %d seconds\n\"", + (int) ( (float) ( 120000 - level.pausedTime ) / 1000.0f ) ) ); + } - if( levelTime - level.pauseTime > 3 * 60000 ) + // Prevents clients from getting lagged-out messages + for( i = 0; i < level.maxclients; i++ ) { - trap_SendConsoleCommand( EXEC_APPEND, "!unpause" ); + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + level.clients[ i ].ps.commandTime = levelTime; } + + if( level.pausedTime > 120000 ) + { + trap_SendServerCommand( -1, "print \"Server: The game has been unpaused automatically (2 minute max)\n\"" ); + trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); + level.pausedTime = 0; + } + + return; } - CheckMsgTimer( ); - CheckCountdown( ); - level.framenum++; level.previousTime = level.time; level.time = levelTime; msec = level.time - level.previousTime; - //TA: seed the rng - srand( level.framenum ); - // get any cvar changes G_UpdateCvars( ); + CheckCvars( ); + // now we are done spawning + level.spawning = qfalse; // // go through all allocated objects // - start = trap_Milliseconds( ); ent = &g_entities[ 0 ]; for( i = 0; i < level.num_entities; i++, ent++ ) @@ -2935,7 +2380,7 @@ void G_RunFrame( int levelTime ) if( ent->freeAfterEvent ) continue; - //TA: calculate the acceleration of this entity + // calculate the acceleration of this entity if( ent->evaluateAcceleration ) G_EvaluateAcceleration( ent, msec ); @@ -2948,6 +2393,12 @@ void G_RunFrame( int levelTime ) continue; } + if ( ent->s.eType == ET_WEAPON_DROP ) + { + G_RunWeaponDrop( ent ); + continue; + } + if( ent->s.eType == ET_BUILDABLE ) { G_BuildableThink( ent, msec ); @@ -2974,9 +2425,6 @@ void G_RunFrame( int levelTime ) G_RunThink( ent ); } - end = trap_Milliseconds(); - - start = trap_Milliseconds(); // perform final fixups on the players ent = &g_entities[ 0 ]; @@ -2987,19 +2435,20 @@ void G_RunFrame( int levelTime ) ClientEndFrame( ent ); } - // save position information for all active clients + // save position information for all active clients G_UnlaggedStore( ); - end = trap_Milliseconds(); - - //TA: G_CountSpawns( ); - G_CalculateBuildPoints( ); - G_CalculateStages( ); - G_SpawnClients( PTE_ALIENS ); - G_SpawnClients( PTE_HUMANS ); - G_CalculateAvgPlayers( ); - G_UpdateZaps( msec ); + if( !g_doWarmup.integer || level.warmupTime <= level.time ) + { + G_CalculateBuildPoints( ); + G_CalculateStages( ); + G_SpawnClients( TEAM_ALIENS ); + G_SpawnClients( TEAM_HUMANS ); + G_CalculateAvgPlayers( ); + G_UpdateZaps( msec ); + } + G_UpdateBuildableRangeMarkers( ); // see if it is time to end the level CheckExitRules( ); @@ -3008,23 +2457,8 @@ void G_RunFrame( int levelTime ) CheckTeamStatus( ); // cancel vote if timed out - CheckVote( ); - - // check team votes - CheckTeamVote( PTE_HUMANS ); - CheckTeamVote( PTE_ALIENS ); + for( i = 0; i < NUM_TEAMS; i++ ) + G_CheckVote( i ); - G_admin_schachtmeisterFrame(); - - // for tracking changes - CheckCvars( ); - - if( g_listEntity.integer ) - { - for( i = 0; i < MAX_GENTITIES; i++ ) - G_Printf( "%4i: %s\n", i, g_entities[ i ].classname ); - - trap_Cvar_Set( "g_listEntity", "0" ); - } + level.frameMsec = trap_Milliseconds(); } - diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c index a846c79..90a51cf 100644 --- a/src/game/g_maprotation.c +++ b/src/game/g_maprotation.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -25,10 +26,91 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "g_local.h" -mapRotations_t mapRotations; +#define MAX_MAP_ROTATIONS 64 +#define MAX_MAP_ROTATION_MAPS 256 + +#define NOT_ROTATING -1 + +typedef enum +{ + CV_ERR, + CV_RANDOM, + CV_NUMCLIENTS, + CV_LASTWIN +} conditionVariable_t; + +typedef enum +{ + CO_LT, + CO_EQ, + CO_GT +} conditionOp_t; + +typedef struct condition_s +{ + struct node_s *target; + + conditionVariable_t lhs; + conditionOp_t op; + + int numClients; + team_t lastWin; +} condition_t; + +typedef struct map_s +{ + char name[ MAX_QPATH ]; + + char postCommand[ MAX_STRING_CHARS ]; + char layouts[ MAX_CVAR_VALUE_STRING ]; +} map_t; + +typedef struct label_s +{ + char name[ MAX_QPATH ]; +} label_t; + +typedef enum +{ + NT_MAP, + NT_CONDITION, + NT_GOTO, + NT_RESUME, + NT_LABEL, + NT_RETURN +} nodeType_t; + +typedef struct node_s +{ + nodeType_t type; + + union + { + map_t map; + condition_t condition; + label_t label; + } u; + +} node_t; + +typedef struct mapRotation_s +{ + char name[ MAX_QPATH ]; + + node_t *nodes[ MAX_MAP_ROTATION_MAPS ]; + int numNodes; + int currentNode; +} mapRotation_t; +typedef struct mapRotations_s +{ + mapRotation_t rotations[ MAX_MAP_ROTATIONS ]; + int numRotations; +} mapRotations_t; + +static mapRotations_t mapRotations; -static qboolean G_GetVotedMap( char *name, int size, int rotation, int map ); +static int G_NodeIndexAfter( int currentNode, int rotation ); /* =============== @@ -37,9 +119,21 @@ G_MapExists Check if a map exists =============== */ -qboolean G_MapExists( char *name ) +qboolean G_MapExists( const char *name ) +{ + return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ ) > 0; +} + +/* +=============== +G_LayoutExists + +Check if a layout exists for a map +=============== +*/ +qboolean G_LayoutExists( const char *map, const char *layout ) { - return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ ); + return !Q_stricmp( layout, "*BUILTIN*" ) || trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), NULL, FS_READ ) > 0; } /* @@ -64,62 +158,111 @@ static qboolean G_RotationExists( char *name ) /* =============== -G_ParseCommandSection +G_LabelExists + +Check if a label exists in a rotation +=============== +*/ +static qboolean G_LabelExists( int rotation, char *name ) +{ + mapRotation_t *mr = &mapRotations.rotations[ rotation ]; + int i; + + for( i = 0; i < mr->numNodes; i++ ) + { + node_t *node = mr->nodes[ i ]; + + if( node->type == NT_LABEL && + !Q_stricmp( name, node->u.label.name ) ) + return qtrue; + + if( node->type == NT_MAP && + !Q_stricmp( name, node->u.map.name ) ) + return qtrue; + } + + return qfalse; +} + +/* +=============== +G_AllocateNode + +Allocate memory for a node_t +=============== +*/ +static node_t *G_AllocateNode( void ) +{ + node_t *node = BG_Alloc( sizeof( node_t ) ); + + return node; +} + +/* +=============== +G_ParseMapCommandSection Parse a map rotation command section =============== */ -static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p ) +static qboolean G_ParseMapCommandSection( node_t *node, char **text_p ) { - char *token; + char *token; + map_t *map = &node->u.map; + int commandLength = 0; // read optional parameters while( 1 ) { token = COM_Parse( text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "" ) ) return qfalse; if( !Q_stricmp( token, "}" ) ) + { + if( commandLength > 0 ) + { + // Replace last ; with \n + map->postCommand[ commandLength - 1 ] = '\n'; + } + return qtrue; //reached the end of this command section + } if( !Q_stricmp( token, "layouts" ) ) { token = COM_ParseExt( text_p, qfalse ); - mre->layouts[ 0 ] = '\0'; - while( token && token[ 0 ] != 0 ) + map->layouts[ 0 ] = '\0'; + + while( token[ 0 ] != 0 ) { - Q_strcat( mre->layouts, sizeof( mre->layouts ), token ); - Q_strcat( mre->layouts, sizeof( mre->layouts ), " " ); + Q_strcat( map->layouts, sizeof( map->layouts ), token ); + Q_strcat( map->layouts, sizeof( map->layouts ), " " ); token = COM_ParseExt( text_p, qfalse ); } + continue; } - Q_strncpyz( mre->postCmds[ mre->numCmds ], token, sizeof( mre->postCmds[ 0 ] ) ); - Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " ); + // Parse the rest of the line into map->postCommand + Q_strcat( map->postCommand, sizeof( map->postCommand ), token ); + Q_strcat( map->postCommand, sizeof( map->postCommand ), " " ); token = COM_ParseExt( text_p, qfalse ); - while( token && token[ 0 ] != 0 ) + while( token[ 0 ] != 0 ) { - Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), token ); - Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " ); + Q_strcat( map->postCommand, sizeof( map->postCommand ), token ); + Q_strcat( map->postCommand, sizeof( map->postCommand ), " " ); token = COM_ParseExt( text_p, qfalse ); } - if( mre->numCmds == MAX_MAP_COMMANDS ) - { - G_Printf( S_COLOR_RED "ERROR: maximum number of map commands (%d) reached\n", - MAX_MAP_COMMANDS ); - return qfalse; - } - else - mre->numCmds++; + commandLength = strlen( map->postCommand ); + map->postCommand[ commandLength - 1 ] = ';'; } return qfalse; @@ -127,291 +270,195 @@ static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p /* =============== -G_ParseMapRotation +G_ParseNode -Parse a map rotation section +Parse a node =============== */ -static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) +static qboolean G_ParseNode( node_t **node, char *token, char **text_p, qboolean conditional ) { - char *token; - qboolean mnSet = qfalse; - mapRotationEntry_t *mre = NULL; - mapRotationCondition_t *mrc; - - // read optional parameters - while( 1 ) + if( !Q_stricmp( token, "if" ) ) { - token = COM_Parse( text_p ); + condition_t *condition; - if( !token ) - break; + (*node)->type = NT_CONDITION; + condition = &(*node)->u.condition; - if( !Q_stricmp( token, "" ) ) + token = COM_Parse( text_p ); + + if( !*token ) return qfalse; - if( !Q_stricmp( token, "{" ) ) + if( !Q_stricmp( token, "numClients" ) ) { - if( !mnSet ) - { - G_Printf( S_COLOR_RED "ERROR: map settings section with no name\n" ); + condition->lhs = CV_NUMCLIENTS; + + token = COM_Parse( text_p ); + + if( !*token ) return qfalse; - } - if( !G_ParseMapCommandSection( mre, text_p ) ) + if( !Q_stricmp( token, "<" ) ) + condition->op = CO_LT; + else if( !Q_stricmp( token, ">" ) ) + condition->op = CO_GT; + else if( !Q_stricmp( token, "=" ) ) + condition->op = CO_EQ; + else { - G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" ); + G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token ); return qfalse; } - mnSet = qfalse; - continue; - } - else if( !Q_stricmp( token, "goto" ) ) - { token = COM_Parse( text_p ); - if( !token ) - break; - - mrc = &mre->conditions[ mre->numConditions ]; - mrc->unconditional = qtrue; - Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - - if( mre->numConditions == MAX_MAP_ROTATION_CONDS ) - { - G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n", - MAX_MAP_ROTATION_CONDS ); + if( !*token ) return qfalse; - } - else - mre->numConditions++; - continue; + condition->numClients = atoi( token ); } - else if( !Q_stricmp( token, "if" ) ) + else if( !Q_stricmp( token, "lastWin" ) ) { - token = COM_Parse( text_p ); + condition->lhs = CV_LASTWIN; - if( !token ) - break; + token = COM_Parse( text_p ); - mrc = &mre->conditions[ mre->numConditions ]; + if( !*token ) + return qfalse; - if( !Q_stricmp( token, "numClients" ) ) + if( !Q_stricmp( token, "aliens" ) ) + condition->lastWin = TEAM_ALIENS; + else if( !Q_stricmp( token, "humans" ) ) + condition->lastWin = TEAM_HUMANS; + else { - mrc->lhs = MCV_NUMCLIENTS; + G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token ); + return qfalse; + } + } + else if( !Q_stricmp( token, "random" ) ) + condition->lhs = CV_RANDOM; + else + { + G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token ); + return qfalse; + } - token = COM_Parse( text_p ); + token = COM_Parse( text_p ); - if( !token ) - break; + if( !*token ) + return qfalse; - if( !Q_stricmp( token, "<" ) ) - mrc->op = MCO_LT; - else if( !Q_stricmp( token, ">" ) ) - mrc->op = MCO_GT; - else if( !Q_stricmp( token, "=" ) ) - mrc->op = MCO_EQ; - else - { - G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token ); - return qfalse; - } + condition->target = G_AllocateNode( ); + *node = condition->target; - token = COM_Parse( text_p ); + return G_ParseNode( node, token, text_p, qtrue ); + } + else if( !Q_stricmp( token, "return" ) ) + { + (*node)->type = NT_RETURN; + } + else if( !Q_stricmp( token, "goto" ) || + !Q_stricmp( token, "resume" ) ) + { + label_t *label; - if( !token ) - break; + if( !Q_stricmp( token, "goto" ) ) + (*node)->type = NT_GOTO; + else + (*node)->type = NT_RESUME; + label = &(*node)->u.label; - mrc->numClients = atoi( token ); - } - else if( !Q_stricmp( token, "lastWin" ) ) - { - mrc->lhs = MCV_LASTWIN; + token = COM_Parse( text_p ); - token = COM_Parse( text_p ); + if( !*token ) + { + G_Printf( S_COLOR_RED "ERROR: goto or resume without label\n" ); + return qfalse; + } - if( !token ) - break; + Q_strncpyz( label->name, token, sizeof( label->name ) ); + } + else if( *token == '#' || conditional ) + { + label_t *label; - if( !Q_stricmp( token, "aliens" ) ) - mrc->lastWin = PTE_ALIENS; - else if( !Q_stricmp( token, "humans" ) ) - mrc->lastWin = PTE_HUMANS; - else - { - G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token ); - return qfalse; - } - } - else if( !Q_stricmp( token, "random" ) ) - mrc->lhs = MCV_RANDOM; - else - { - G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token ); - return qfalse; - } + (*node)->type = ( conditional ) ? NT_GOTO : NT_LABEL; + label = &(*node)->u.label; - token = COM_Parse( text_p ); + Q_strncpyz( label->name, token, sizeof( label->name ) ); + } + else + { + map_t *map; - if( !token ) - break; + (*node)->type = NT_MAP; + map = &(*node)->u.map; - mrc->unconditional = qfalse; - Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); + Q_strncpyz( map->name, token, sizeof( map->name ) ); + map->postCommand[ 0 ] = '\0'; + } - if( mre->numConditions == MAX_MAP_ROTATION_CONDS ) - { - G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n", - MAX_MAP_ROTATION_CONDS ); - return qfalse; - } - else - mre->numConditions++; + return qtrue; +} - continue; - } - else if( !Q_stricmp( token, "*VOTE*" ) ) - { - if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) - { - G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", - MAX_MAP_ROTATION_MAPS ); - return qfalse; - } - mre = &mr->maps[ mr->numMaps ]; - Q_strncpyz( mre->name, token, sizeof( mre->name ) ); +/* +=============== +G_ParseMapRotation - token = COM_Parse( text_p ); +Parse a map rotation section +=============== +*/ +static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) +{ + char *token; + node_t *node = NULL; - if( !Q_stricmp( token, "{" ) ) - { - while( 1 ) - { - token = COM_Parse( text_p ); + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); - if( !token ) - break; + if( !*token ) + break; - if( !Q_stricmp( token, "}" ) ) - { - break; - } - else - { - if( mre->numConditions < MAX_MAP_ROTATION_CONDS ) - { - mrc = &mre->conditions[ mre->numConditions ]; - mrc->lhs = MCV_VOTE; - mrc->unconditional = qfalse; - Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - - mre->numConditions++; - } - else - { - G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one vote (%d) reached\n", - MAX_MAP_ROTATION_CONDS ); - } - } - } - if( !mre->numConditions ) - { - G_Printf( S_COLOR_YELLOW "WARNING: no maps in *VOTE* section\n" ); - } - else - { - mr->numMaps++; - mnSet = qtrue; - } - } - else - { - G_Printf( S_COLOR_RED "ERROR: *VOTE* with no section\n" ); - return qfalse; - } + if( !Q_stricmp( token, "" ) ) + return qfalse; - continue; - } - else if( !Q_stricmp( token, "*RANDOM*" ) ) + if( !Q_stricmp( token, "{" ) ) { - if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) + if( node == NULL ) { - G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", - MAX_MAP_ROTATION_MAPS ); + G_Printf( S_COLOR_RED "ERROR: map command section with no associated map\n" ); return qfalse; } - mre = &mr->maps[ mr->numMaps ]; - Q_strncpyz( mre->name, token, sizeof( mre->name ) ); - - token = COM_Parse( text_p ); - - if( !Q_stricmp( token, "{" ) ) - { - while( 1 ) - { - token = COM_Parse( text_p ); - - if( !token ) - break; - if( !Q_stricmp( token, "}" ) ) - { - break; - } - else - { - if( mre->numConditions < MAX_MAP_ROTATION_CONDS ) - { - mrc = &mre->conditions[ mre->numConditions ]; - mrc->lhs = MCV_SELECTEDRANDOM; - mrc->unconditional = qfalse; - Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - - mre->numConditions++; - } - else - { - G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one Random Slot (%d) reached\n", - MAX_MAP_ROTATION_CONDS ); - } - } - } - if( !mre->numConditions ) - { - G_Printf( S_COLOR_YELLOW "WARNING: no maps in *RANDOM* section\n" ); - } - else - { - mr->numMaps++; - mnSet = qtrue; - } - } - else + if( !G_ParseMapCommandSection( node, text_p ) ) { - G_Printf( S_COLOR_RED "ERROR: *RANDOM* with no section\n" ); + G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" ); return qfalse; } continue; } else if( !Q_stricmp( token, "}" ) ) - return qtrue; //reached the end of this map rotation - - mre = &mr->maps[ mr->numMaps ]; + { + // Reached the end of this map rotation + return qtrue; + } - if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) + if( mr->numNodes == MAX_MAP_ROTATION_MAPS ) { G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", MAX_MAP_ROTATION_MAPS ); return qfalse; } - else - mr->numMaps++; - Q_strncpyz( mre->name, token, sizeof( mre->name ) ); - mnSet = qtrue; + node = G_AllocateNode( ); + mr->nodes[ mr->numNodes++ ] = node; + + if( !G_ParseNode( &node, token, text_p, qfalse ) ) + return qfalse; } return qfalse; @@ -427,7 +474,7 @@ Load the map rotations from a map rotation file static qboolean G_ParseMapRotationFile( const char *fileName ) { char *text_p; - int i, j, k; + int i, j; int len; char *token; char text[ 20000 ]; @@ -460,7 +507,7 @@ static qboolean G_ParseMapRotationFile( const char *fileName ) { token = COM_Parse( &text_p ); - if( !token ) + if( !*token ) break; if( !Q_stricmp( token, "" ) ) @@ -471,13 +518,17 @@ static qboolean G_ParseMapRotationFile( const char *fileName ) if( mrNameSet ) { //check for name space clashes - for( i = 0; i < mapRotations.numRotations; i++ ) + if( G_RotationExists( mrName ) ) { - if( !Q_stricmp( mapRotations.rotations[ i ].name, mrName ) ) - { - G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName ); - return qfalse; - } + G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName ); + return qfalse; + } + + if( mapRotations.numRotations == MAX_MAP_ROTATIONS ) + { + G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n", + MAX_MAP_ROTATIONS ); + return qfalse; } Q_strncpyz( mapRotations.rotations[ mapRotations.numRotations ].name, mrName, MAX_QPATH ); @@ -488,23 +539,16 @@ static qboolean G_ParseMapRotationFile( const char *fileName ) return qfalse; } + mapRotations.numRotations++; + //start parsing map rotations again mrNameSet = qfalse; - if( mapRotations.numRotations == MAX_MAP_ROTATIONS ) - { - G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n", - MAX_MAP_ROTATIONS ); - return qfalse; - } - else - mapRotations.numRotations++; - continue; } else { - G_Printf( S_COLOR_RED "ERROR: unamed map rotation\n" ); + G_Printf( S_COLOR_RED "ERROR: unnamed map rotation\n" ); return qfalse; } } @@ -523,29 +567,46 @@ static qboolean G_ParseMapRotationFile( const char *fileName ) for( i = 0; i < mapRotations.numRotations; i++ ) { - for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) + mapRotation_t *mr = &mapRotations.rotations[ i ]; + int mapCount = 0; + + for( j = 0; j < mr->numNodes; j++ ) { - if( Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*") != 0 && - Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*") != 0 && - !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) ) - { - G_Printf( S_COLOR_RED "ERROR: map \"%s\" doesn't exist\n", - mapRotations.rotations[ i ].maps[ j ].name ); - return qfalse; - } + node_t *node = mr->nodes[ j ]; - for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) + if( node->type == NT_MAP ) { - if( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ) && - !G_RotationExists( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ) ) + mapCount++; + if( !G_MapExists( node->u.map.name ) ) { - G_Printf( S_COLOR_RED "ERROR: conditional destination \"%s\" doesn't exist\n", - mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ); + G_Printf( S_COLOR_RED "ERROR: rotation map \"%s\" doesn't exist\n", + node->u.map.name ); return qfalse; } + continue; + } + else if( node->type == NT_RETURN ) + continue; + else if( node->type == NT_LABEL ) + continue; + else while( node->type == NT_CONDITION ) + node = node->u.condition.target; + if( ( node->type == NT_GOTO || node->type == NT_RESUME ) && + !G_LabelExists( i, node->u.label.name ) && + !G_RotationExists( node->u.label.name ) ) + { + G_Printf( S_COLOR_RED "ERROR: goto destination named \"%s\" doesn't exist\n", + node->u.label.name ); + return qfalse; } + } + if( mapCount == 0 ) + { + G_Printf( S_COLOR_RED "ERROR: rotation \"%s\" needs at least one map entry\n", + mr->name ); + return qfalse; } } @@ -554,62 +615,187 @@ static qboolean G_ParseMapRotationFile( const char *fileName ) /* =============== -G_PrintRotations - -Print the parsed map rotations +G_PrintSpaces =============== */ -void G_PrintRotations( void ) +static void G_PrintSpaces( int spaces ) { - int i, j, k; + int i; + + for( i = 0; i < spaces; i++ ) + G_Printf( " " ); +} + +/* +=============== +G_PrintRotations + +Print the parsed map rotations +=============== +*/ +void G_PrintRotations( void ) +{ + int i, j; + int size = sizeof( mapRotations ); G_Printf( "Map rotations as parsed:\n\n" ); for( i = 0; i < mapRotations.numRotations; i++ ) { - G_Printf( "rotation: %s\n{\n", mapRotations.rotations[ i ].name ); + mapRotation_t *mr = &mapRotations.rotations[ i ]; + + G_Printf( "rotation: %s\n{\n", mr->name ); + + size += mr->numNodes * sizeof( node_t ); - for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) + for( j = 0; j < mr->numNodes; j++ ) { - G_Printf( " map: %s\n {\n", mapRotations.rotations[ i ].maps[ j ].name ); + node_t *node = mr->nodes[ j ]; + int indentation = 0; - for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numCmds; k++ ) + while( node->type == NT_CONDITION ) { - G_Printf( " command: %s\n", - mapRotations.rotations[ i ].maps[ j ].postCmds[ k ] ); + G_PrintSpaces( indentation ); + G_Printf( " condition\n" ); + node = node->u.condition.target; + + size += sizeof( node_t ); + + indentation += 2; } - G_Printf( " }\n" ); + G_PrintSpaces( indentation ); - for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) + switch( node->type ) { - G_Printf( " conditional: %s\n", - mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ); - } + case NT_MAP: + G_Printf( " %s\n", node->u.map.name ); + + if( strlen( node->u.map.postCommand ) > 0 ) + G_Printf( " command: %s", node->u.map.postCommand ); + break; + + case NT_RETURN: + G_Printf( " return\n" ); + break; + + case NT_LABEL: + G_Printf( " label: %s\n", node->u.label.name ); + break; + + case NT_GOTO: + G_Printf( " goto: %s\n", node->u.label.name ); + break; + + case NT_RESUME: + G_Printf( " resume: %s\n", node->u.label.name ); + break; + + default: + break; + } } G_Printf( "}\n" ); } - G_Printf( "Total memory used: %d bytes\n", sizeof( mapRotations ) ); + G_Printf( "Total memory used: %d bytes\n", size ); } /* =============== -G_GetCurrentMapArray +G_ClearRotationStack -Fill a static array with the current map of each rotation +Clear the rotation stack =============== */ -static int *G_GetCurrentMapArray( void ) +void G_ClearRotationStack( void ) { - static int currentMap[ MAX_MAP_ROTATIONS ]; + trap_Cvar_Set( "g_mapRotationStack", "" ); + trap_Cvar_Update( &g_mapRotationStack ); +} + +/* +=============== +G_PushRotationStack + +Push the rotation stack +=============== +*/ +static void G_PushRotationStack( int rotation ) +{ + char text[ MAX_CVAR_VALUE_STRING ]; + + Com_sprintf( text, sizeof( text ), "%d %s", + rotation, g_mapRotationStack.string ); + trap_Cvar_Set( "g_mapRotationStack", text ); + trap_Cvar_Update( &g_mapRotationStack ); +} + +/* +=============== +G_PopRotationStack + +Pop the rotation stack +=============== +*/ +static int G_PopRotationStack( void ) +{ + int value = -1; + char text[ MAX_CVAR_VALUE_STRING ]; + char *text_p, *token; + + Q_strncpyz( text, g_mapRotationStack.string, sizeof( text ) ); + + text_p = text; + token = COM_Parse( &text_p ); + + if( *token ) + value = atoi( token ); + + if( text_p ) + { + while ( *text_p == ' ' ) + text_p++; + trap_Cvar_Set( "g_mapRotationStack", text_p ); + trap_Cvar_Update( &g_mapRotationStack ); + } + else + G_ClearRotationStack( ); + + return value; +} + +/* +=============== +G_RotationNameByIndex + +Returns the name of a rotation by its index +=============== +*/ +static char *G_RotationNameByIndex( int index ) +{ + if( index >= 0 && index < mapRotations.numRotations ) + return mapRotations.rotations[ index ].name; + return NULL; +} + +/* +=============== +G_CurrentNodeIndexArray + +Fill a static array with the current node of each rotation +=============== +*/ +static int *G_CurrentNodeIndexArray( void ) +{ + static int currentNode[ MAX_MAP_ROTATIONS ]; int i = 0; char text[ MAX_MAP_ROTATIONS * 2 ]; char *text_p, *token; - Q_strncpyz( text, g_currentMap.string, sizeof( text ) ); + Q_strncpyz( text, g_mapRotationNodes.string, sizeof( text ) ); text_p = text; @@ -617,166 +803,138 @@ static int *G_GetCurrentMapArray( void ) { token = COM_Parse( &text_p ); - if( !token ) - break; - - if( !Q_stricmp( token, "" ) ) + if( !*token ) break; - currentMap[ i++ ] = atoi( token ); + currentNode[ i++ ] = atoi( token ); } - return currentMap; + return currentNode; } /* =============== -G_SetCurrentMap +G_SetCurrentNodeByIndex Set the current map in some rotation =============== */ -static void G_SetCurrentMap( int currentMap, int rotation ) +static void G_SetCurrentNodeByIndex( int currentNode, int rotation ) { - char text[ MAX_MAP_ROTATIONS * 2 ] = { 0 }; - int *p = G_GetCurrentMapArray( ); + char text[ MAX_MAP_ROTATIONS * 4 ] = { 0 }; + int *p = G_CurrentNodeIndexArray( ); int i; - p[ rotation ] = currentMap; + p[ rotation ] = currentNode; for( i = 0; i < mapRotations.numRotations; i++ ) Q_strcat( text, sizeof( text ), va( "%d ", p[ i ] ) ); - trap_Cvar_Set( "g_currentMap", text ); - trap_Cvar_Update( &g_currentMap ); + trap_Cvar_Set( "g_mapRotationNodes", text ); + trap_Cvar_Update( &g_mapRotationNodes ); } /* =============== -G_GetCurrentMap +G_CurrentNodeIndex -Return the current map in some rotation +Return the current node index in some rotation =============== */ -int G_GetCurrentMap( int rotation ) +static int G_CurrentNodeIndex( int rotation ) { - int *p = G_GetCurrentMapArray( ); + int *p = G_CurrentNodeIndexArray( ); return p[ rotation ]; } -static qboolean G_GetRandomMap( char *name, int size, int rotation, int map ); - /* =============== -G_IssueMapChange +G_NodeByIndex -Send commands to the server to actually change the map +Return a node in a rotation by its index =============== */ -static void G_IssueMapChange( int rotation ) +static node_t *G_NodeByIndex( int index, int rotation ) { - int i; - int map = G_GetCurrentMap( rotation ); - char cmd[ MAX_TOKEN_CHARS ]; - char newmap[ MAX_CVAR_VALUE_STRING ]; - char currentmap[ MAX_CVAR_VALUE_STRING ]; - - Q_strncpyz( newmap, mapRotations.rotations[rotation].maps[map].name, sizeof( newmap )); - trap_Cvar_VariableStringBuffer( "mapname", currentmap, sizeof( currentmap )); + if( rotation >= 0 && rotation < mapRotations.numRotations && + index >= 0 && index < mapRotations.rotations[ rotation ].numNodes ) + return mapRotations.rotations[ rotation ].nodes[ index ]; - if (!Q_stricmp( newmap, "*VOTE*") ) - { - fileHandle_t f; + return NULL; +} - G_GetVotedMap( newmap, sizeof( newmap ), rotation, map ); - if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 ) - { - trap_FS_FCloseFile( f ); - } - else - { - G_AdvanceMapRotation(); - return; - } - } - else if(!Q_stricmp( newmap, "*RANDOM*") ) - { - fileHandle_t f; +/* +=============== +G_IssueMapChange - G_GetRandomMap( newmap, sizeof( newmap ), rotation, map ); - if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 ) - { - trap_FS_FCloseFile( f ); - } - else - { - G_AdvanceMapRotation(); - return; - } - } +Send commands to the server to actually change the map +=============== +*/ +static void G_IssueMapChange( int index, int rotation ) +{ + node_t *node = mapRotations.rotations[ rotation ].nodes[ index ]; + map_t *map = &node->u.map; - // allow a manually defined g_layouts setting to override the maprotation - if( !g_layouts.string[ 0 ] && - mapRotations.rotations[ rotation ].maps[ map ].layouts[ 0 ] ) + // allow a manually defined g_nextLayout setting to override the maprotation + if( !g_nextLayout.string[ 0 ] && map->layouts[ 0 ] ) { - trap_Cvar_Set( "g_layouts", - mapRotations.rotations[ rotation ].maps[ map ].layouts ); + trap_Cvar_Set( "g_nextLayout", map->layouts ); } - if( !Q_stricmp( currentmap, newmap ) ) - { - trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); - } - else - { - trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", newmap ) ); - } + G_MapConfigs( map->name ); - // load up map defaults if g_mapConfigs is set - G_MapConfigs( newmap ); + trap_SendConsoleCommand( EXEC_APPEND, va( "map \"%s\"\n", map->name ) ); - for( i = 0; i < mapRotations.rotations[ rotation ].maps[ map ].numCmds; i++ ) - { - Q_strncpyz( cmd, mapRotations.rotations[ rotation ].maps[ map ].postCmds[ i ], - sizeof( cmd ) ); - Q_strcat( cmd, sizeof( cmd ), "\n" ); - trap_SendConsoleCommand( EXEC_APPEND, cmd ); - } + if( strlen( map->postCommand ) > 0 ) + trap_SendConsoleCommand( EXEC_APPEND, map->postCommand ); } /* =============== -G_ResolveConditionDestination +G_GotoLabel -Resolve the destination of some condition +Resolve the label of some condition =============== */ -static mapConditionType_t G_ResolveConditionDestination( int *n, char *name ) +static qboolean G_GotoLabel( int rotation, int nodeIndex, char *name, + qboolean reset_index, int depth ) { - int i; + node_t *node; + int i; + + // Search the rotation names... + if( G_StartMapRotation( name, qtrue, qtrue, reset_index, depth ) ) + return qtrue; - //search the current rotation first... - for( i = 0; i < mapRotations.rotations[ g_currentMapRotation.integer ].numMaps; i++ ) + // ...then try labels in the rotation + for( i = 0; i < mapRotations.rotations[ rotation ].numNodes; i++ ) { - if( !Q_stricmp( mapRotations.rotations[ g_currentMapRotation.integer ].maps[ i ].name, name ) ) + node = mapRotations.rotations[ rotation ].nodes[ i ]; + + if( node->type == NT_LABEL && !Q_stricmp( node->u.label.name, name ) ) { - *n = i; - return MCT_MAP; + G_SetCurrentNodeByIndex( G_NodeIndexAfter( i, rotation ), rotation ); + G_AdvanceMapRotation( depth ); + return qtrue; } } - //...then search the rotation names - for( i = 0; i < mapRotations.numRotations; i++ ) + // finally check for a map by name + for( i = 0; i < mapRotations.rotations[ rotation ].numNodes; i++ ) { - if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) ) + nodeIndex = G_NodeIndexAfter( nodeIndex, rotation ); + node = mapRotations.rotations[ rotation ].nodes[ nodeIndex ]; + + if( node->type == NT_MAP && !Q_stricmp( node->u.map.name, name ) ) { - *n = i; - return MCT_ROTATION; + G_SetCurrentNodeByIndex( nodeIndex, rotation ); + G_AdvanceMapRotation( depth ); + return qtrue; } } - return MCT_ERR; + return qfalse; } /* @@ -786,113 +944,200 @@ G_EvaluateMapCondition Evaluate a map condition =============== */ -static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc ) +static qboolean G_EvaluateMapCondition( condition_t **condition ) { - switch( mrc->lhs ) + qboolean result = qfalse; + condition_t *localCondition = *condition; + + switch( localCondition->lhs ) { - case MCV_RANDOM: - return rand( ) & 1; + case CV_RANDOM: + result = rand( ) / ( RAND_MAX / 2 + 1 ); break; - case MCV_NUMCLIENTS: - switch( mrc->op ) + case CV_NUMCLIENTS: + switch( localCondition->op ) { - case MCO_LT: - return level.numConnectedClients < mrc->numClients; + case CO_LT: + result = level.numConnectedClients < localCondition->numClients; break; - case MCO_GT: - return level.numConnectedClients > mrc->numClients; + case CO_GT: + result = level.numConnectedClients > localCondition->numClients; break; - case MCO_EQ: - return level.numConnectedClients == mrc->numClients; + case CO_EQ: + result = level.numConnectedClients == localCondition->numClients; break; } break; - case MCV_LASTWIN: - return level.lastWin == mrc->lastWin; - break; - - case MCV_VOTE: - // ignore vote for conditions; - break; - case MCV_SELECTEDRANDOM: - // ignore vote for conditions; + case CV_LASTWIN: + result = level.lastWin == localCondition->lastWin; break; default: - case MCV_ERR: - G_Printf( S_COLOR_RED "ERROR: malformed map switch condition\n" ); + case CV_ERR: + G_Printf( S_COLOR_RED "ERROR: malformed map switch localCondition\n" ); break; } - return qfalse; + if( localCondition->target->type == NT_CONDITION ) + { + *condition = &localCondition->target->u.condition; + + return result && G_EvaluateMapCondition( condition ); + } + + return result; } /* =============== -G_AdvanceMapRotation - -Increment the current map rotation +G_NodeIndexAfter =============== */ -qboolean G_AdvanceMapRotation( void ) +static int G_NodeIndexAfter( int currentNode, int rotation ) { - mapRotation_t *mr; - mapRotationEntry_t *mre; - mapRotationCondition_t *mrc; - int currentRotation, currentMap, nextMap; - int i, n; - mapConditionType_t mct; - - if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING ) - return qfalse; + mapRotation_t *mr = &mapRotations.rotations[ rotation ]; + + return ( currentNode + 1 ) % mr->numNodes; +} + +/* +=============== +G_StepMapRotation - currentMap = G_GetCurrentMap( currentRotation ); +Run one node of a map rotation +=============== +*/ +qboolean G_StepMapRotation( int rotation, int nodeIndex, int depth ) +{ + node_t *node; + condition_t *condition; + int returnRotation; + qboolean step = qtrue; - mr = &mapRotations.rotations[ currentRotation ]; - mre = &mr->maps[ currentMap ]; - nextMap = ( currentMap + 1 ) % mr->numMaps; + node = G_NodeByIndex( nodeIndex, rotation ); + depth++; - for( i = 0; i < mre->numConditions; i++ ) + // guard against inifinite loop in conditional code + if( depth > 32 && node->type != NT_MAP ) { - mrc = &mre->conditions[ i ]; + if( depth > 64 ) + { + G_Printf( S_COLOR_RED "ERROR: infinite loop protection stopped at map rotation %s\n", + G_RotationNameByIndex( rotation ) ); + return qfalse; + } + + G_Printf( S_COLOR_YELLOW "WARNING: possible infinite loop in map rotation %s\n", + G_RotationNameByIndex( rotation ) ); + return qtrue; + } - if( mrc->unconditional || G_EvaluateMapCondition( mrc ) ) + while( step ) + { + step = qfalse; + switch( node->type ) { - mct = G_ResolveConditionDestination( &n, mrc->dest ); + case NT_CONDITION: + condition = &node->u.condition; - switch( mct ) - { - case MCT_MAP: - nextMap = n; - break; + if( G_EvaluateMapCondition( &condition ) ) + { + node = condition->target; + step = qtrue; + continue; + } + break; - case MCT_ROTATION: - //need to increment the current map before changing the rotation - //or you get infinite loops with some conditionals - G_SetCurrentMap( nextMap, currentRotation ); - G_StartMapRotation( mrc->dest, qtrue ); - return qtrue; - break; + case NT_RETURN: + returnRotation = G_PopRotationStack( ); + if( returnRotation >= 0 ) + { + G_SetCurrentNodeByIndex( + G_NodeIndexAfter( nodeIndex, rotation ), rotation ); + if( G_StartMapRotation( G_RotationNameByIndex( returnRotation ), + qtrue, qfalse, qfalse, depth ) ) + { + return qfalse; + } + } + break; - default: - case MCT_ERR: - G_Printf( S_COLOR_YELLOW "WARNING: map switch destination could not be resolved: %s\n", - mrc->dest ); - break; - } + case NT_MAP: + if( G_MapExists( node->u.map.name ) ) + { + G_SetCurrentNodeByIndex( + G_NodeIndexAfter( nodeIndex, rotation ), rotation ); + G_IssueMapChange( nodeIndex, rotation ); + return qfalse; + } + + G_Printf( S_COLOR_YELLOW "WARNING: skipped missing map %s in rotation %s\n", + node->u.map.name, G_RotationNameByIndex( rotation ) ); + break; + + case NT_LABEL: + break; + + case NT_GOTO: + case NT_RESUME: + G_SetCurrentNodeByIndex( + G_NodeIndexAfter( nodeIndex, rotation ), rotation ); + if ( G_GotoLabel( rotation, nodeIndex, node->u.label.name, + ( node->type == NT_GOTO ), depth ) ) + return qfalse; + + G_Printf( S_COLOR_YELLOW "WARNING: label, map, or rotation %s not found in %s\n", + node->u.label.name, G_RotationNameByIndex( rotation ) ); + break; } } - G_SetCurrentMap( nextMap, currentRotation ); - G_IssueMapChange( currentRotation ); - return qtrue; } +/* +=============== +G_AdvanceMapRotation + +Increment the current map rotation +=============== +*/ +void G_AdvanceMapRotation( int depth ) +{ + node_t *node; + int rotation; + int nodeIndex; + + rotation = g_currentMapRotation.integer; + if( rotation < 0 || rotation >= MAX_MAP_ROTATIONS ) + return; + + nodeIndex = G_CurrentNodeIndex( rotation ); + node = G_NodeByIndex( nodeIndex, rotation ); + if( !node ) + { + G_Printf( S_COLOR_YELLOW "WARNING: index incorrect for map rotation %s, trying 0\n", + G_RotationNameByIndex( rotation) ); + nodeIndex = 0; + node = G_NodeByIndex( nodeIndex, rotation ); + } + + while( node && G_StepMapRotation( rotation, nodeIndex, depth ) ) + { + nodeIndex = G_NodeIndexAfter( nodeIndex, rotation ); + node = G_NodeByIndex( nodeIndex, rotation ); + depth++; + } + + if( !node ) + G_Printf( S_COLOR_RED "ERROR: unexpected end of maprotation '%s'\n", + G_RotationNameByIndex( rotation) ); +} + /* =============== G_StartMapRotation @@ -900,19 +1145,30 @@ G_StartMapRotation Switch to a new map rotation =============== */ -qboolean G_StartMapRotation( char *name, qboolean changeMap ) +qboolean G_StartMapRotation( char *name, qboolean advance, + qboolean putOnStack, qboolean reset_index, int depth ) { int i; + int currentRotation = g_currentMapRotation.integer; for( i = 0; i < mapRotations.numRotations; i++ ) { if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) ) { + if( putOnStack && currentRotation >= 0 ) + G_PushRotationStack( currentRotation ); + trap_Cvar_Set( "g_currentMapRotation", va( "%d", i ) ); trap_Cvar_Update( &g_currentMapRotation ); - if( changeMap ) - G_IssueMapChange( i ); + if( advance ) + { + if( reset_index ) + G_SetCurrentNodeByIndex( 0, i ); + + G_AdvanceMapRotation( depth ); + } + break; } } @@ -952,14 +1208,14 @@ qboolean G_MapRotationActive( void ) =============== G_InitMapRotations -Load and intialise the map rotations +Load and initialise the map rotations =============== */ void G_InitMapRotations( void ) { - const char *fileName = "maprotation.cfg"; + const char *fileName = "maprotation.cfg"; - //load the file if it exists + // Load the file if it exists if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) ) { if( !G_ParseMapRotationFile( fileName ) ) @@ -972,7 +1228,7 @@ void G_InitMapRotations( void ) { if( g_initialMapRotation.string[ 0 ] != 0 ) { - G_StartMapRotation( g_initialMapRotation.string, qfalse ); + G_StartMapRotation( g_initialMapRotation.string, qfalse, qtrue, qfalse, 0 ); trap_Cvar_Set( "g_initialMapRotation", "" ); trap_Cvar_Update( &g_initialMapRotation ); @@ -980,347 +1236,41 @@ void G_InitMapRotations( void ) } } -static char rotationVoteList[ MAX_MAP_ROTATION_CONDS ][ MAX_QPATH ]; -static int rotationVoteLen = 0; - -static int rotationVoteClientPosition[ MAX_CLIENTS ]; -static int rotationVoteClientSelection[ MAX_CLIENTS ]; - /* =============== -G_CheckMapRotationVote +G_FreeNode + +Free up memory used by a node =============== */ -qboolean G_CheckMapRotationVote( void ) -{ - mapRotation_t *mr; - mapRotationEntry_t *mre; - mapRotationCondition_t *mrc; - int currentRotation, currentMap, nextMap; - int i; - - rotationVoteLen = 0; - - if( g_mapRotationVote.integer < 1 ) - return qfalse; - - if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING ) - return qfalse; - - currentMap = G_GetCurrentMap( currentRotation ); - - mr = &mapRotations.rotations[ currentRotation ]; - nextMap = ( currentMap + 1 ) % mr->numMaps; - mre = &mr->maps[ nextMap ]; - - for( i = 0; i < mre->numConditions; i++ ) - { - mrc = &mre->conditions[ i ]; - - if( mrc->lhs == MCV_VOTE ) - { - Q_strncpyz( rotationVoteList[ rotationVoteLen ], mrc->dest, - sizeof( rotationVoteList[ rotationVoteLen ] ) ); - rotationVoteLen++; - if( rotationVoteLen >= MAX_MAP_ROTATION_CONDS ) - break; - } - } - - if( !rotationVoteLen ) - return qfalse; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - rotationVoteClientPosition[ i ] = 0; - rotationVoteClientSelection[ i ] = -1; - } - - return qtrue; -} - -typedef struct { - int votes; - int map; -} MapVoteResultsSort_t; - -static int SortMapVoteResults( const void *av, const void *bv ) -{ - const MapVoteResultsSort_t *a = av; - const MapVoteResultsSort_t *b = bv; - - if( a->votes > b->votes ) - return -1; - if( a->votes < b->votes ) - return 1; - - if( a->map > b->map ) - return 1; - if( a->map < b->map ) - return -1; - - return 0; -} - -static int G_GetMapVoteWinner( int *winvotes, int *totalvotes, int *resultorder ) -{ - MapVoteResultsSort_t results[ MAX_MAP_ROTATION_CONDS ]; - int tv = 0; - int i, n; - - for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ ) - { - results[ i ].votes = 0; - results[ i ].map = i; - } - for( i = 0; i < MAX_CLIENTS; i++ ) - { - n = rotationVoteClientSelection[ i ]; - if( n >=0 && n < MAX_MAP_ROTATION_CONDS ) - { - results[ n ].votes += 1; - tv++; - } - } - - qsort ( results, MAX_MAP_ROTATION_CONDS, sizeof( results[ 0 ] ), SortMapVoteResults ); - - if( winvotes != NULL ) - *winvotes = results[ 0 ].votes; - if( totalvotes != NULL ) - *totalvotes = tv; - - if( resultorder != NULL ) - { - for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ ) - resultorder[ results[ i ].map ] = i; - } - - return results[ 0 ].map; -} - -qboolean G_IntermissionMapVoteWinner( void ) +void G_FreeNode( node_t *node ) { - int winner, winvotes, totalvotes; - int nonvotes; + if( node->type == NT_CONDITION ) + G_FreeNode( node->u.condition.target ); - winner = G_GetMapVoteWinner( &winvotes, &totalvotes, NULL ); - if( winvotes * 2 > level.numConnectedClients ) - return qtrue; - nonvotes = level.numConnectedClients - totalvotes; - if( nonvotes < 0 ) - nonvotes = 0; - if( winvotes > nonvotes + ( totalvotes - winvotes ) ) - return qtrue; - - return qfalse; + BG_Free( node ); } -static qboolean G_GetVotedMap( char *name, int size, int rotation, int map ) -{ - mapRotation_t *mr; - mapRotationEntry_t *mre; - mapRotationCondition_t *mrc; - int i, n; - int winner; - qboolean found = qfalse; - - if( !rotationVoteLen ) - return qfalse; - - winner = G_GetMapVoteWinner( NULL, NULL, NULL ); - - mr = &mapRotations.rotations[ rotation ]; - mre = &mr->maps[ map ]; - - n = 0; - for( i = 0; i < mre->numConditions && n < rotationVoteLen; i++ ) - { - mrc = &mre->conditions[ i ]; - - if( mrc->lhs == MCV_VOTE ) - { - if( n == winner ) - { - Q_strncpyz( name, mrc->dest, size ); - found = qtrue; - break; - } - n++; - } - } - - rotationVoteLen = 0; - - return found; -} +/* +=============== +G_ShutdownMapRotations -static void G_IntermissionMapVoteMessageReal( gentity_t *ent, int winner, int winvotes, int totalvotes, int *ranklist ) +Free up memory used by map rotations +=============== +*/ +void G_ShutdownMapRotations( void ) { - int clientNum; - char string[ MAX_STRING_CHARS ]; - char entry[ MAX_STRING_CHARS ]; - int ourlist[ MAX_MAP_ROTATION_CONDS ]; - int len = 0; - int index, selection; - int i; - char *color; - char *rank; - - clientNum = ent-g_entities; - - index = rotationVoteClientSelection[ clientNum ]; - selection = rotationVoteClientPosition[ clientNum ]; - - if( winner < 0 || winner >= MAX_MAP_ROTATION_CONDS || ranklist == NULL ) - { - ranklist = &ourlist[0]; - winner = G_GetMapVoteWinner( &winvotes, &totalvotes, ranklist ); - } + int i, j; - Q_strncpyz( string, "^7Attack = down ^0/^7 Repair = up ^0/^7 F1 = vote\n\n" - "^2Map Vote Menu\n" - "^7+------------------+\n", sizeof( string ) ); - for( i = 0; i < rotationVoteLen; i++ ) + for( i = 0; i < mapRotations.numRotations; i++ ) { - if( !G_MapExists( rotationVoteList[ i ] ) ) - continue; - - if( i == selection ) - color = "^5"; - else if( i == index ) - color = "^1"; - else - color = "^7"; + mapRotation_t *mr = &mapRotations.rotations[ i ]; - switch( ranklist[ i ] ) + for( j = 0; j < mr->numNodes; j++ ) { - case 0: - rank = "^7---"; - break; - case 1: - rank = "^7--"; - break; - case 2: - rank = "^7-"; - break; - default: - rank = ""; - break; - } - - Com_sprintf( entry, sizeof( entry ), "^7%s%s%s%s %s %s%s^7%s\n", - ( i == index ) ? "^1>>>" : "", - ( i == selection ) ? "^7(" : " ", - rank, - color, - rotationVoteList[ i ], - rank, - ( i == selection ) ? "^7)" : " ", - ( i == index ) ? "^1<<<" : "" ); - - Q_strcat( string, sizeof( string ), entry ); - len += strlen( entry ); - } + node_t *node = mr->nodes[ j ]; - Com_sprintf( entry, sizeof( entry ), - "\n^7+----------------+\nleader: ^3%s^7 with %d vote%s\nvoters: %d\ntime left: %d", - rotationVoteList[ winner ], - winvotes, - ( winvotes == 1 ) ? "" : "s", - totalvotes, - ( level.mapRotationVoteTime - level.time ) / 1000 ); - Q_strcat( string, sizeof( string ), entry ); - - trap_SendServerCommand( ent-g_entities, va( "cp \"%s\"\n", string ) ); -} - -void G_IntermissionMapVoteMessageAll( void ) -{ - int ranklist[ MAX_MAP_ROTATION_CONDS ]; - int winner; - int winvotes, totalvotes; - int i; - - winner = G_GetMapVoteWinner( &winvotes, &totalvotes, &ranklist[ 0 ] ); - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].pers.connected == CON_CONNECTED ) - G_IntermissionMapVoteMessageReal( g_entities + i, winner, winvotes, totalvotes, ranklist ); - } -} - -void G_IntermissionMapVoteMessage( gentity_t *ent ) -{ - G_IntermissionMapVoteMessageReal( ent, -1, 0, 0, NULL ); -} - -void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ) -{ - int clientNum; - int n; - - clientNum = ent-g_entities; - - if( choose ) - { - rotationVoteClientSelection[ clientNum ] = rotationVoteClientPosition[ clientNum ]; - } - else - { - n = rotationVoteClientPosition[ clientNum ]; - if( next ) - n++; - else - n--; - - if( n >= rotationVoteLen ) - n = rotationVoteLen - 1; - if( n < 0 ) - n = 0; - - rotationVoteClientPosition[ clientNum ] = n; - } - - G_IntermissionMapVoteMessage( ent ); -} - -static qboolean G_GetRandomMap( char *name, int size, int rotation, int map ) -{ - mapRotation_t *mr; - mapRotationEntry_t *mre; - mapRotationCondition_t *mrc; - int i, nummaps; - int randompick = 0; - int maplist[ 32 ]; - - mr = &mapRotations.rotations[ rotation ]; - mre = &mr->maps[ map ]; - - nummaps = 0; - //count the number of map votes - for( i = 0; i < mre->numConditions; i++ ) - { - mrc = &mre->conditions[ i ]; - - if( mrc->lhs == MCV_SELECTEDRANDOM ) - { - //map doesnt exist - if( !G_MapExists( mrc->dest ) ) { - continue; - } - maplist[ nummaps ] = i; - nummaps++; + G_FreeNode( node ); } } - - if( nummaps == 0 ) { - return qfalse; - } - - randompick = (int)( random() * nummaps ); - - Q_strncpyz( name, mre->conditions[ maplist[ randompick ] ].dest, size ); - - return qtrue; } diff --git a/src/game/g_mem.c b/src/game/g_mem.c deleted file mode 100644 index 6935194..0000000 --- a/src/game/g_mem.c +++ /dev/null @@ -1,216 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous 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. - -Tremulous 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "g_local.h" - -#define POOLSIZE ( 1024 * 1024 ) -#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value -#define ROUNDBITS 31 // Round to 32 bytes - -struct freememnode -{ - // Size of ROUNDBITS - int cookie, size; // Size includes node (obviously) - struct freememnode *prev, *next; -}; - -static char memoryPool[POOLSIZE]; -static struct freememnode *freehead; -static int freemem; - -void *G_Alloc( int size ) -{ - // Find a free block and allocate. - // Does two passes, attempts to fill same-sized free slot first. - - struct freememnode *fmn, *prev, *next, *smallest; - int allocsize, smallestsize; - char *endptr; - int *ptr; - - allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary - ptr = NULL; - - smallest = NULL; - smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :) - for( fmn = freehead; fmn; fmn = fmn->next ) - { - if( fmn->cookie != FREEMEMCOOKIE ) - G_Error( "G_Alloc: Memory corruption detected!\n" ); - - if( fmn->size >= allocsize ) - { - // We've got a block - if( fmn->size == allocsize ) - { - // Same size, just remove - - prev = fmn->prev; - next = fmn->next; - if( prev ) - prev->next = next; // Point previous node to next - if( next ) - next->prev = prev; // Point next node to previous - if( fmn == freehead ) - freehead = next; // Set head pointer to next - ptr = (int *) fmn; - break; // Stop the loop, this is fine - } - else - { - // Keep track of the smallest free slot - if( fmn->size < smallestsize ) - { - smallest = fmn; - smallestsize = fmn->size; - } - } - } - } - - if( !ptr && smallest ) - { - // We found a slot big enough - smallest->size -= allocsize; - endptr = (char *) smallest + smallest->size; - ptr = (int *) endptr; - } - - if( ptr ) - { - freemem -= allocsize; - if( g_debugAlloc.integer ) - G_Printf( "G_Alloc of %i bytes (%i left)\n", allocsize, freemem ); - memset( ptr, 0, allocsize ); - *ptr++ = allocsize; // Store a copy of size for deallocation - return( (void *) ptr ); - } - - G_Error( "G_Alloc: failed on allocation of %i bytes\n", size ); - return( NULL ); -} - -void G_Free( void *ptr ) -{ - // Release allocated memory, add it to the free list. - - struct freememnode *fmn; - char *freeend; - int *freeptr; - - freeptr = ptr; - freeptr--; - - freemem += *freeptr; - if( g_debugAlloc.integer ) - G_Printf( "G_Free of %i bytes (%i left)\n", *freeptr, freemem ); - - for( fmn = freehead; fmn; fmn = fmn->next ) - { - freeend = ((char *) fmn) + fmn->size; - if( freeend == (char *) freeptr ) - { - // Released block can be merged to an existing node - - fmn->size += *freeptr; // Add size of node. - return; - } - } - // No merging, add to head of list - - fmn = (struct freememnode *) freeptr; - fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr - fmn->cookie = FREEMEMCOOKIE; - fmn->prev = NULL; - fmn->next = freehead; - freehead->prev = fmn; - freehead = fmn; -} - -void G_InitMemory( void ) -{ - // Set up the initial node - - freehead = (struct freememnode *)memoryPool; - freehead->cookie = FREEMEMCOOKIE; - freehead->size = POOLSIZE; - freehead->next = NULL; - freehead->prev = NULL; - freemem = sizeof( memoryPool ); -} - -void G_DefragmentMemory( void ) -{ - // If there's a frenzy of deallocation and we want to - // allocate something big, this is useful. Otherwise... - // not much use. - - struct freememnode *startfmn, *endfmn, *fmn; - - for( startfmn = freehead; startfmn; ) - { - endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size); - for( fmn = freehead; fmn; ) - { - if( fmn->cookie != FREEMEMCOOKIE ) - G_Error( "G_DefragmentMemory: Memory corruption detected!\n" ); - - if( fmn == endfmn ) - { - // We can add fmn onto startfmn. - - if( fmn->prev ) - fmn->prev->next = fmn->next; - if( fmn->next ) - { - if( !(fmn->next->prev = fmn->prev) ) - freehead = fmn->next; // We're removing the head node - } - startfmn->size += fmn->size; - memset( fmn, 0, sizeof(struct freememnode) ); // A redundant call, really. - - startfmn = freehead; - endfmn = fmn = NULL; // Break out of current loop - } - else - fmn = fmn->next; - } - - if( endfmn ) - startfmn = startfmn->next; // endfmn acts as a 'restart' flag here - } -} - -void Svcmd_GameMem_f( void ) -{ - // Give a breakdown of memory - - struct freememnode *fmn; - - G_Printf( "Game memory status: %i out of %i bytes allocated\n", POOLSIZE - freemem, POOLSIZE ); - - for( fmn = freehead; fmn; fmn = fmn->next ) - G_Printf( " %dd: %d bytes free.\n", fmn, fmn->size ); - G_Printf( "Status complete.\n" ); -} - diff --git a/src/game/g_misc.c b/src/game/g_misc.c index a68eeb8..66066ba 100644 --- a/src/game/g_misc.c +++ b/src/game/g_misc.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -44,7 +45,7 @@ target_position does the same thing */ void SP_info_notnull( gentity_t *self ) { - G_SetOrigin( self, self->s.origin ); + G_SetOrigin( self, self->r.currentOrigin ); } @@ -70,39 +71,47 @@ TELEPORTERS ================================================================================= */ -void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, float speed ) { // unlink to make sure it can't possibly interfere with G_KillBox trap_UnlinkEntity( player ); VectorCopy( origin, player->client->ps.origin ); - player->client->ps.origin[ 2 ] += 1; + player->client->ps.groundEntityNum = ENTITYNUM_NONE; + player->client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; - // spit the player out AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); - VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); - player->client->ps.pm_time = 160; // hold time - player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorScale( player->client->ps.velocity, speed, player->client->ps.velocity ); + player->client->ps.pm_time = 0.4f * fabs( speed ); // duration of loss of control + if( player->client->ps.pm_time > 160 ) + player->client->ps.pm_time = 160; + if( player->client->ps.pm_time != 0 ) + player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; G_UnlaggedClear( player ); + // cut all relevant zap beams + G_ClearPlayerZapEffects( player ); + // set angles G_SetClientViewAngle( player, angles ); - // kill anything at the destination - if( player->client->sess.sessionTeam != TEAM_SPECTATOR ) - G_KillBox( player ); - // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); + VectorCopy( player->client->ps.viewangles, player->r.currentAngles ); + + if( player->client->sess.spectatorState == SPECTATOR_NOT ) + { + // kill anything at the destination + G_KillBox( player ); - if( player->client->sess.sessionTeam != TEAM_SPECTATOR ) trap_LinkEntity (player); + } } @@ -129,8 +138,7 @@ void SP_misc_model( gentity_t *ent ) VectorSet (ent->maxs, 16, 16, 16); trap_LinkEntity (ent); - G_SetOrigin( ent, ent->s.origin ); - VectorCopy( ent->s.angles, ent->s.apos.trBase ); + G_SetOrigin( ent, ent->r.currentOrigin ); #else G_FreeEntity( ent ); #endif @@ -171,19 +179,23 @@ void locateCamera( gentity_t *ent ) // clientNum holds the rotate offset ent->s.clientNum = owner->s.clientNum; - VectorCopy( owner->s.origin, ent->s.origin2 ); + VectorCopy( owner->r.currentOrigin, ent->s.origin2 ); // see if the portal_camera has a target target = G_PickTarget( owner->target ); if( target ) { - VectorSubtract( target->s.origin, owner->s.origin, dir ); + VectorSubtract( target->r.currentOrigin, owner->r.currentOrigin, dir ); VectorNormalize( dir ); } else - G_SetMovedir( owner->s.angles, dir ); + G_SetMovedir( owner->r.currentAngles, dir ); ent->s.eventParm = DirToByte( dir ); + + ByteToDir( ent->s.eventParm, dir ); + vectoangles( dir, ent->r.currentAngles ); + ent->r.currentAngles[ 2 ] = ent->s.clientNum * 360.0f / 256; } /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) @@ -201,7 +213,7 @@ void SP_misc_portal_surface( gentity_t *ent ) if( !ent->target ) { - VectorCopy( ent->s.origin, ent->s.origin2 ); + VectorCopy( ent->r.currentOrigin, ent->s.origin2 ); } else { @@ -273,7 +285,7 @@ void SP_misc_particle_system( gentity_t *self ) { char *s; - G_SetOrigin( self, self->s.origin ); + G_SetOrigin( self, self->r.currentOrigin ); G_SpawnString( "psName", "", &s ); G_SpawnFloat( "wait", "0", &self->wait ); @@ -420,7 +432,7 @@ void SP_misc_light_flare( gentity_t *self ) //try to find a spot near to the flare which is empty. This //is used to facilitate visibility testing - findEmptySpot( self->s.origin, 8.0f, self->s.angles2 ); + findEmptySpot( self->r.currentOrigin, 8.0f, self->s.angles2 ); self->use = SP_use_light_flare; diff --git a/src/game/g_missile.c b/src/game/g_missile.c index 26cb97e..14494cd 100644 --- a/src/game/g_missile.c +++ b/src/game/g_missile.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -82,7 +83,6 @@ void G_ExplodeMissile( gentity_t *ent ) ent->s.eType = ET_GENERAL; - //TA: tired... can't be fucked... hack if( ent->s.weapon != WP_LOCKBLOB_LAUNCHER && ent->s.weapon != WP_FLAMER ) G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); @@ -92,7 +92,7 @@ void G_ExplodeMissile( gentity_t *ent ) // splash damage if( ent->splashDamage ) G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, - ent->splashRadius, ent, ent->dflags, ent->splashMethodOfDeath ); + ent->splashRadius, ent, ent->splashMethodOfDeath ); trap_LinkEntity( ent ); } @@ -140,7 +140,7 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) } else if( !strcmp( ent->classname, "lockblob" ) ) { - if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { other->client->ps.stats[ STAT_STATE ] |= SS_BLOBLOCKED; other->client->lastLockTime = level.time; @@ -150,7 +150,7 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) } else if( !strcmp( ent->classname, "slowblob" ) ) { - if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { other->client->ps.stats[ STAT_STATE ] |= SS_SLOWLOCKED; other->client->lastSlowTime = level.time; @@ -175,11 +175,11 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) //prevent collision with the client when returning ent->r.ownerNum = other->s.number; - ent->think = AHive_ReturnToHive; + ent->think = G_ExplodeMissile; ent->nextthink = level.time + FRAMETIME; //only damage humans - if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) returnAfterDamage = qtrue; else return; @@ -198,8 +198,8 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) if( VectorLength( velocity ) == 0 ) velocity[ 2 ] = 1; // stepped on a grenade - G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage, - ent->dflags, ent->methodOfDeath ); + G_Damage( other, ent, attacker, velocity, ent->r.currentOrigin, ent->damage, + DAMAGE_NO_LOCDAMAGE, ent->methodOfDeath ); } } @@ -209,7 +209,8 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) // is it cheaper in bandwidth to just remove this ent and create a new // one, rather than changing the missile into the explosion? - if( other->takedamage && other->client ) + if( other->takedamage && + ( other->s.eType == ET_PLAYER || other->s.eType == ET_BUILDABLE ) ) { G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) ); ent->s.otherEntityNum = other->s.number; @@ -231,7 +232,7 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace ) // splash damage (doesn't apply to person directly hit) if( ent->splashDamage ) G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, - other, ent->dflags, ent->splashMethodOfDeath ); + other, ent->splashMethodOfDeath ); trap_LinkEntity( ent ); } @@ -247,7 +248,8 @@ void G_RunMissile( gentity_t *ent ) { vec3_t origin; trace_t tr; - int passent; + int passent; + qboolean impact = qfalse; // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); @@ -255,40 +257,73 @@ void G_RunMissile( gentity_t *ent ) // ignore interactions with the missile owner passent = ent->r.ownerNum; - // trace a line from the previous position to the current position - trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask ); + // general trace to see if we hit anything at all + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, + origin, passent, ent->clipmask ); if( tr.startsolid || tr.allsolid ) { - // make sure the tr.entityNum is set to the entity we're stuck in - trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask ); - tr.fraction = 0; + tr.fraction = 0.0f; + VectorCopy( ent->r.currentOrigin, tr.endpos ); } - else - VectorCopy( tr.endpos, ent->r.currentOrigin ); - ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... - trap_LinkEntity( ent ); - ent->r.contents = 0; //...encoding bbox information + if( tr.fraction < 1.0f ) + { + if( !ent->pointAgainstWorld || tr.contents & CONTENTS_BODY ) + { + // We hit an entity or we don't care + impact = qtrue; + } + else + { + trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, origin, + passent, ent->clipmask ); + + if( tr.fraction < 1.0f ) + { + // Hit the world with point trace + impact = qtrue; + } + else + { + if( tr.contents & CONTENTS_BODY ) + { + // Hit an entity + impact = qtrue; + } + else + { + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, + origin, passent, CONTENTS_BODY ); + + if( tr.fraction < 1.0f ) + impact = qtrue; + } + } + } + } - if( tr.fraction != 1 ) + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if( impact ) { - // never explode or bounce on sky if( tr.surfaceFlags & SURF_NOIMPACT ) { - // If grapple, reset owner - if( ent->parent && ent->parent->client && ent->parent->client->hook == ent ) - ent->parent->client->hook = NULL; - + // Never explode or bounce on sky G_FreeEntity( ent ); return; } G_MissileImpact( ent, &tr ); + if( ent->s.eType != ET_MISSILE ) return; // exploded } + ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... + trap_LinkEntity( ent ); + ent->r.contents = 0; //...encoding bbox information + // check think function after bouncing G_RunThink( ent ); } @@ -311,23 +346,23 @@ gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn(); bolt->classname = "flame"; + bolt->pointAgainstWorld = qfalse; bolt->nextthink = level.time + FLAMER_LIFETIME; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_FLAMER; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = FLAMER_DMG; - bolt->splashDamage = FLAMER_DMG; + bolt->splashDamage = FLAMER_SPLASHDAMAGE; bolt->splashRadius = FLAMER_RADIUS; bolt->methodOfDeath = MOD_FLAMER; bolt->splashMethodOfDeath = MOD_FLAMER_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; - bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -15.0f; - bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 15.0f; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -FLAMER_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = FLAMER_SIZE; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame @@ -357,10 +392,10 @@ gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn(); bolt->classname = "blaster"; + bolt->pointAgainstWorld = qtrue; bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_BLASTER; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; @@ -372,6 +407,8 @@ gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir ) bolt->splashMethodOfDeath = MOD_BLASTER; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -BLASTER_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = BLASTER_SIZE; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame @@ -400,10 +437,10 @@ gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn(); bolt->classname = "pulse"; + bolt->pointAgainstWorld = qtrue; bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_PULSE_RIFLE; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; @@ -415,6 +452,8 @@ gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir ) bolt->splashMethodOfDeath = MOD_PRIFLE; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -PRIFLE_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = PRIFLE_SIZE; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame @@ -435,42 +474,53 @@ fire_luciferCannon ================= */ -gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius ) +gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, + int damage, int radius, int speed ) { gentity_t *bolt; - int localDamage = (int)( ceil( ( (float)damage / - (float)LCANNON_TOTAL_CHARGE ) * (float)LCANNON_DAMAGE ) ); + float charge; VectorNormalize( dir ); bolt = G_Spawn( ); bolt->classname = "lcannon"; + bolt->pointAgainstWorld = qtrue; - if( damage == LCANNON_TOTAL_CHARGE ) + if( damage == LCANNON_DAMAGE ) bolt->nextthink = level.time; else bolt->nextthink = level.time + 10000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_LUCIFER_CANNON; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; bolt->parent = self; - bolt->damage = localDamage; - bolt->dflags = DAMAGE_KNOCKBACK; - bolt->splashDamage = localDamage / 2; + bolt->damage = damage; + bolt->splashDamage = damage / 2; bolt->splashRadius = radius; bolt->methodOfDeath = MOD_LCANNON; bolt->splashMethodOfDeath = MOD_LCANNON_SPLASH; bolt->clipmask = MASK_SHOT; bolt->target_ent = NULL; + + // Give the missile a small bounding box + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = + -LCANNON_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = + -bolt->r.mins[ 0 ]; + + // Pass the missile charge through + charge = (float)( damage - LCANNON_SECONDARY_DAMAGE ) / LCANNON_DAMAGE; + bolt->s.torsoAnim = charge * 255; + if( bolt->s.torsoAnim < 0 ) + bolt->s.torsoAnim = 0; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); - VectorScale( dir, LCANNON_SPEED, bolt->s.pos.trDelta ); + VectorScale( dir, speed, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy( start, bolt->r.currentOrigin ); @@ -492,17 +542,16 @@ gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn( ); bolt->classname = "grenade"; + bolt->pointAgainstWorld = qfalse; bolt->nextthink = level.time + 5000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_GRENADE; bolt->s.eFlags = EF_BOUNCE_HALF; bolt->s.generic1 = WPM_PRIMARY; //weaponMode bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = GRENADE_DAMAGE; - bolt->dflags = DAMAGE_KNOCKBACK; bolt->splashDamage = GRENADE_DAMAGE; bolt->splashRadius = GRENADE_RANGE; bolt->methodOfDeath = MOD_GRENADE; @@ -525,85 +574,79 @@ gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir ) } //============================================================================= + + /* ================ -AHive_ReturnToHive +AHive_SearchAndDestroy -Adjust the trajectory to point towards the hive +Adjust the trajectory to point towards the target ================ */ -void AHive_ReturnToHive( gentity_t *self ) +void AHive_SearchAndDestroy( gentity_t *self ) { - vec3_t dir; - trace_t tr; - - if( !self->parent ) - { - G_Printf( S_COLOR_YELLOW "WARNING: AHive_ReturnToHive called with no self->parent\n" ); - return; - } + vec3_t dir; + trace_t tr; + gentity_t *ent; + int i; + float d, nearest; - trap_UnlinkEntity( self->parent ); - trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, - self->parent->r.currentOrigin, self->r.ownerNum, self->clipmask ); - trap_LinkEntity( self->parent ); + if( self->parent && !self->parent->inuse ) + self->parent = NULL; - if( tr.fraction < 1.0f ) + if( level.time > self->timestamp ) { - //if can't see hive then disperse VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); self->s.pos.trType = TR_STATIONARY; self->s.pos.trTime = level.time; self->think = G_ExplodeMissile; - self->nextthink = level.time + 2000; - self->parent->active = qfalse; //allow the parent to start again + self->nextthink = level.time + 50; + if( self->parent ) + self->parent->active = qfalse; //allow the parent to start again + return; } + + ent = self->target_ent; + if( ent && ent->health > 0 && ent->client && ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + nearest = DistanceSquared( self->r.currentOrigin, ent->r.currentOrigin ); else { - VectorSubtract( self->parent->r.currentOrigin, self->r.currentOrigin, dir ); - VectorNormalize( dir ); - - //change direction towards the hive - VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); - SnapVector( self->s.pos.trDelta ); // save net bandwidth - VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); - self->s.pos.trTime = level.time; - - self->think = G_ExplodeMissile; - self->nextthink = level.time + 15000; + self->target_ent = NULL; + nearest = 0; // silence warning } -} - -/* -================ -AHive_SearchAndDestroy -Adjust the trajectory to point towards the target -================ -*/ -void AHive_SearchAndDestroy( gentity_t *self ) -{ - vec3_t dir; - trace_t tr; - - trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, - self->target_ent->r.currentOrigin, self->r.ownerNum, self->clipmask ); - - //if there is no LOS or the parent hive is too far away or the target is dead or notargeting, return - if( tr.entityNum == ENTITYNUM_WORLD || - Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > ( HIVE_RANGE * 5 ) || - self->target_ent->health <= 0 || self->target_ent->flags & FL_NOTARGET ) + //find the closest human + for( i = 0; i < MAX_CLIENTS; i++ ) { - self->r.ownerNum = ENTITYNUM_WORLD; + ent = &g_entities[ i ]; + + if( ent->flags & FL_NOTARGET ) + continue; - self->think = AHive_ReturnToHive; - self->nextthink = level.time + FRAMETIME; + if( ent->client && + ent->health > 0 && + ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + ( d = DistanceSquared( ent->r.currentOrigin, self->r.currentOrigin ), + ( self->target_ent == NULL || d < nearest ) ) ) + { + trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, + ent->r.currentOrigin, self->r.ownerNum, self->clipmask ); + if( tr.entityNum != ENTITYNUM_WORLD ) + { + nearest = d; + self->target_ent = ent; + } + } } + + if( self->target_ent == NULL ) + VectorClear( dir ); else { VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir ); VectorNormalize( dir ); + } //change direction towards the player VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); @@ -612,7 +655,6 @@ void AHive_SearchAndDestroy( gentity_t *self ) self->s.pos.trTime = level.time; self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; - } } /* @@ -628,11 +670,11 @@ gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn( ); bolt->classname = "hive"; + bolt->pointAgainstWorld = qfalse; bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; bolt->think = AHive_SearchAndDestroy; bolt->s.eType = ET_MISSILE; - bolt->s.eFlags |= EF_BOUNCE|EF_NO_BOUNCE_SOUND; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.eFlags |= EF_BOUNCE | EF_NO_BOUNCE_SOUND; bolt->s.weapon = WP_HIVE; bolt->s.generic1 = WPM_PRIMARY; //weaponMode bolt->r.ownerNum = self->s.number; @@ -643,6 +685,7 @@ gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir ) bolt->methodOfDeath = MOD_SWARM; bolt->clipmask = MASK_SHOT; bolt->target_ent = self->target_ent; + bolt->timestamp = level.time + HIVE_LIFETIME; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame @@ -669,10 +712,10 @@ gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn( ); bolt->classname = "lockblob"; + bolt->pointAgainstWorld = qtrue; bolt->nextthink = level.time + 15000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_LOCKBLOB_LAUNCHER; bolt->s.generic1 = WPM_PRIMARY; //weaponMode bolt->r.ownerNum = self->s.number; @@ -707,10 +750,10 @@ gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn( ); bolt->classname = "slowblob"; + bolt->pointAgainstWorld = qtrue; bolt->nextthink = level.time + 15000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_ABUILD2; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; @@ -746,10 +789,10 @@ gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn( ); bolt->classname = "lockblob"; + bolt->pointAgainstWorld = qtrue; bolt->nextthink = level.time + 15000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_LOCKBLOB_LAUNCHER; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; @@ -783,18 +826,17 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir ) bolt = G_Spawn( ); bolt->classname = "bounceball"; + bolt->pointAgainstWorld = qtrue; bolt->nextthink = level.time + 3000; bolt->think = G_ExplodeMissile; bolt->s.eType = ET_MISSILE; - bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; bolt->s.weapon = WP_ALEVEL3_UPG; bolt->s.generic1 = self->s.generic1; //weaponMode bolt->r.ownerNum = self->s.number; bolt->parent = self; bolt->damage = LEVEL3_BOUNCEBALL_DMG; - bolt->dflags = DAMAGE_KNOCKBACK; - bolt->splashDamage = 0; - bolt->splashRadius = 0; + bolt->splashDamage = LEVEL3_BOUNCEBALL_DMG; + bolt->splashRadius = LEVEL3_BOUNCEBALL_RADIUS; bolt->methodOfDeath = MOD_LEVEL3_BOUNCEBALL; bolt->splashMethodOfDeath = MOD_LEVEL3_BOUNCEBALL; bolt->clipmask = MASK_SHOT; @@ -806,8 +848,6 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir ) VectorScale( dir, LEVEL3_BOUNCEBALL_SPEED, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy( start, bolt->r.currentOrigin ); - /*bolt->s.eFlags |= EF_BOUNCE;*/ return bolt; } - diff --git a/src/game/g_mover.c b/src/game/g_mover.c index 20b5c08..77c1e0c 100644 --- a/src/game/g_mover.c +++ b/src/game/g_mover.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -33,8 +34,6 @@ PUSHMOVE =============================================================================== */ -void MatchTeam( gentity_t *teamLeader, int moverState, int time ); - typedef struct { gentity_t *ent; @@ -55,17 +54,11 @@ G_TestEntityPosition gentity_t *G_TestEntityPosition( gentity_t *ent ) { trace_t tr; - int mask; - - if( ent->clipmask ) - mask = ent->clipmask; - else - mask = MASK_SOLID; if( ent->client ) - trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask ); + trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, ent->clipmask ); else - trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask ); + trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, ent->clipmask ); if( tr.startsolid ) return &g_entities[ tr.entityNum ]; @@ -183,7 +176,7 @@ qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, v // may have pushed them off an edge if( check->s.groundEntityNum != pusher->s.number ) - check->s.groundEntityNum = -1; + check->s.groundEntityNum = ENTITYNUM_NONE; block = G_TestEntityPosition( check ); @@ -212,7 +205,7 @@ qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, v if( !block ) { - check->s.groundEntityNum = -1; + check->s.groundEntityNum = ENTITYNUM_NONE; pushed_p--; return qtrue; } @@ -285,7 +278,7 @@ qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t ** listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); - // move the pusher to it's final position + // move the pusher to its final position VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin ); VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles ); trap_LinkEntity( pusher ); @@ -379,6 +372,12 @@ void G_MoverTeam( gentity_t *ent ) pushed_p = pushed; for( part = ent; part; part = part->teamchain ) { + if( part->s.pos.trType == TR_STATIONARY && + part->s.apos.trType == TR_STATIONARY ) + { + continue; + } + // get current position BG_EvaluateTrajectory( &part->s.pos, level.time, origin ); BG_EvaluateTrajectory( &part->s.apos, level.time, angles ); @@ -393,6 +392,12 @@ void G_MoverTeam( gentity_t *ent ) // go back to the previous position for( part = ent; part; part = part->teamchain ) { + if( part->s.pos.trType == TR_STATIONARY && + part->s.apos.trType == TR_STATIONARY ) + { + continue; + } + part->s.pos.trTime += level.time - level.previousTime; part->s.apos.trTime += level.time - level.previousTime; BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin ); @@ -442,10 +447,7 @@ void G_RunMover( gentity_t *ent ) if( ent->flags & FL_TEAMSLAVE ) return; - // if stationary at one of the positions, don't move anything - if( ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) && - ent->moverState < MODEL_POS1 ) //yuck yuck hack - G_MoverTeam( ent ); + G_MoverTeam( ent ); // check think function G_RunThink( ent ); @@ -554,7 +556,6 @@ void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) MatchTeam All entities in a mover team will move from pos1 to pos2 -in the same amount of time ================ */ void MatchTeam( gentity_t *teamLeader, int moverState, int time ) @@ -566,40 +567,65 @@ void MatchTeam( gentity_t *teamLeader, int moverState, int time ) } +/* +================ +MasterOf +================ +*/ +gentity_t *MasterOf( gentity_t *ent ) +{ + if( ent->teammaster ) + return ent->teammaster; + else + return ent; +} + /* ================ -ReturnToPos1 +GetMoverTeamState + +Returns a MOVER_* value representing the phase (either one + of pos1, 1to2, pos2, or 2to1) of a mover team as a whole. ================ */ -void ReturnToPos1( gentity_t *ent ) +moverState_t GetMoverTeamState( gentity_t *ent ) { - MatchTeam( ent, MOVER_2TO1, level.time ); + qboolean pos1 = qfalse; - // looping sound - ent->s.loopSound = ent->soundLoop; + for( ent = MasterOf( ent ); ent; ent = ent->teamchain ) + { + if( ent->moverState == MOVER_POS1 || ent->moverState == ROTATOR_POS1 ) + pos1 = qtrue; + else if( ent->moverState == MOVER_1TO2 || ent->moverState == ROTATOR_1TO2 ) + return MOVER_1TO2; + else if( ent->moverState == MOVER_2TO1 || ent->moverState == ROTATOR_2TO1 ) + return MOVER_2TO1; + } - // starting sound - if( ent->sound2to1 ) - G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + if( pos1 ) + return MOVER_POS1; + else + return MOVER_POS2; } /* ================ -ReturnToApos1 +ReturnToPos1orApos1 + +Used only by a master movers. ================ */ -void ReturnToApos1( gentity_t *ent ) -{ - MatchTeam( ent, ROTATOR_2TO1, level.time ); - // looping sound - ent->s.loopSound = ent->soundLoop; +void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ); - // starting sound - if( ent->sound2to1 ) - G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); +void ReturnToPos1orApos1( gentity_t *ent ) +{ + if( GetMoverTeamState( ent ) != MOVER_POS2 ) + return; // not every mover in the team has reached its endpoint yet + + Use_BinaryMover( ent, ent, ent->activator ); } @@ -690,10 +716,10 @@ void Think_OpenModelDoor( gentity_t *ent ) //set brush non-solid trap_UnlinkEntity( ent->clipBrush ); - // looping sound - ent->s.loopSound = ent->soundLoop; + // stop the looping sound + ent->s.loopSound = 0; - // starting sound + // play sound if( ent->soundPos2 ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); @@ -718,8 +744,10 @@ Reached_BinaryMover */ void Reached_BinaryMover( gentity_t *ent ) { + gentity_t *master = MasterOf( ent ); + // stop the looping sound - ent->s.loopSound = ent->soundLoop; + ent->s.loopSound = 0; if( ent->moverState == MOVER_1TO2 ) { @@ -731,8 +759,8 @@ void Reached_BinaryMover( gentity_t *ent ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); // return to pos1 after a delay - ent->think = ReturnToPos1; - ent->nextthink = level.time + ent->wait; + master->think = ReturnToPos1orApos1; + master->nextthink = MAX( master->nextthink, level.time + ent->wait ); // fire targets if( !ent->activator ) @@ -763,8 +791,8 @@ void Reached_BinaryMover( gentity_t *ent ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); // return to apos1 after a delay - ent->think = ReturnToApos1; - ent->nextthink = level.time + ent->wait; + master->think = ReturnToPos1orApos1; + master->nextthink = MAX( master->nextthink, level.time + ent->wait ); // fire targets if( !ent->activator ) @@ -799,6 +827,8 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { int total; int partial; + gentity_t *master; + moverState_t teamState; // if this is a non-client-usable door return if( ent->targetname && other && other->client ) @@ -813,11 +843,17 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) ent->activator = activator; + master = MasterOf( ent ); + teamState = GetMoverTeamState( ent ); + + for( ent = master; ent; ent = ent->teamchain ) + { + //ind if( ent->moverState == MOVER_POS1 ) { // start moving 50 msec later, becase if this was player // triggered, level.time hasn't been advanced yet - MatchTeam( ent, MOVER_1TO2, level.time + 50 ); + SetMoverState( ent, MOVER_1TO2, level.time + 50 ); // starting sound if( ent->sound1to2 ) @@ -830,10 +866,30 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) if( ent->teammaster == ent || !ent->teammaster ) trap_AdjustAreaPortalState( ent, qtrue ); } - else if( ent->moverState == MOVER_POS2 ) + else if( ent->moverState == MOVER_POS2 && + !( teamState == MOVER_1TO2 || other == master ) ) { // if all the way up, just delay before coming down - ent->nextthink = level.time + ent->wait; + master->think = ReturnToPos1orApos1; + master->nextthink = MAX( master->nextthink, level.time + ent->wait ); + } + else if( ent->moverState == MOVER_POS2 && + ( teamState == MOVER_1TO2 || other == master ) ) + { + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + SetMoverState( ent, MOVER_2TO1, level.time + 50 ); + + // starting sound + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qtrue ); } else if( ent->moverState == MOVER_2TO1 ) { @@ -844,7 +900,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) if( partial > total ) partial = total; - MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) ); + SetMoverState( ent, MOVER_1TO2, level.time - ( total - partial ) ); if( ent->sound1to2 ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); @@ -858,7 +914,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) if( partial > total ) partial = total; - MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) ); + SetMoverState( ent, MOVER_2TO1, level.time - ( total - partial ) ); if( ent->sound2to1 ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); @@ -867,7 +923,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { // start moving 50 msec later, becase if this was player // triggered, level.time hasn't been advanced yet - MatchTeam( ent, ROTATOR_1TO2, level.time + 50 ); + SetMoverState( ent, ROTATOR_1TO2, level.time + 50 ); // starting sound if( ent->sound1to2 ) @@ -880,10 +936,30 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) if( ent->teammaster == ent || !ent->teammaster ) trap_AdjustAreaPortalState( ent, qtrue ); } - else if( ent->moverState == ROTATOR_POS2 ) + else if( ent->moverState == ROTATOR_POS2 && + !( teamState == MOVER_1TO2 || other == master ) ) { // if all the way up, just delay before coming down - ent->nextthink = level.time + ent->wait; + master->think = ReturnToPos1orApos1; + master->nextthink = MAX( master->nextthink, level.time + ent->wait ); + } + else if( ent->moverState == ROTATOR_POS2 && + ( teamState == MOVER_1TO2 || other == master ) ) + { + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + SetMoverState( ent, ROTATOR_2TO1, level.time + 50 ); + + // starting sound + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qtrue ); } else if( ent->moverState == ROTATOR_2TO1 ) { @@ -894,7 +970,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) if( partial > total ) partial = total; - MatchTeam( ent, ROTATOR_1TO2, level.time - ( total - partial ) ); + SetMoverState( ent, ROTATOR_1TO2, level.time - ( total - partial ) ); if( ent->sound1to2 ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); @@ -908,7 +984,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) if( partial > total ) partial = total; - MatchTeam( ent, ROTATOR_2TO1, level.time - ( total - partial ) ); + SetMoverState( ent, ROTATOR_2TO1, level.time - ( total - partial ) ); if( ent->sound2to1 ) G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); @@ -939,6 +1015,8 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) // if all the way up, just delay before coming down ent->nextthink = level.time + ent->wait; } + //outd + } } @@ -959,15 +1037,16 @@ void InitMover( gentity_t *ent ) vec3_t color; qboolean lightSet, colorSet; char *sound; + char *team; // if the "model2" key is set, use a seperate model // for drawing, but clip against the brushes if( ent->model2 ) ent->s.modelindex2 = G_ModelIndex( ent->model2 ); - // if the "loopsound" key is set, use a constant looping sound when moving - if( G_SpawnString( "noise", "100", &sound ) ) - ent->s.loopSound = G_SoundIndex( sound ); + // if the "noise" key is set, use a constant looping sound when moving + if( G_SpawnString( "noise", "", &sound ) ) + ent->soundLoop = G_SoundIndex( sound ); // if the "color" or "light" keys are set, setup constantLight lightSet = G_SpawnFloat( "light", "100", &light ); @@ -1000,8 +1079,10 @@ void InitMover( gentity_t *ent ) ent->use = Use_BinaryMover; ent->reached = Reached_BinaryMover; + if( G_SpawnString( "team", "", &team ) ) + ent->team = G_CopyString( team ); + ent->moverState = MOVER_POS1; - ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; VectorCopy( ent->pos1, ent->r.currentOrigin ); trap_LinkEntity( ent ); @@ -1039,15 +1120,16 @@ void InitRotator( gentity_t *ent ) vec3_t color; qboolean lightSet, colorSet; char *sound; + char *team; // if the "model2" key is set, use a seperate model // for drawing, but clip against the brushes if( ent->model2 ) ent->s.modelindex2 = G_ModelIndex( ent->model2 ); - // if the "loopsound" key is set, use a constant looping sound when moving - if( G_SpawnString( "noise", "100", &sound ) ) - ent->s.loopSound = G_SoundIndex( sound ); + // if the "noise" key is set, use a constant looping sound when moving + if( G_SpawnString( "noise", "", &sound ) ) + ent->soundLoop = G_SoundIndex( sound ); // if the "color" or "light" keys are set, setup constantLight lightSet = G_SpawnFloat( "light", "100", &light ); @@ -1084,8 +1166,10 @@ void InitRotator( gentity_t *ent ) ent->use = Use_BinaryMover; ent->reached = Reached_BinaryMover; + if( G_SpawnString( "team", "", &team ) ) + ent->team = G_CopyString( team ); + ent->moverState = ROTATOR_POS1; - ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; VectorCopy( ent->pos1, ent->r.currentAngles ); trap_LinkEntity( ent ); @@ -1156,8 +1240,8 @@ static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_ axis = ent->count; VectorClear( dir ); - if( fabs( other->s.origin[ axis ] - ent->r.absmax[ axis ] ) < - fabs( other->s.origin[ axis ] - ent->r.absmin[ axis ] ) ) + if( fabs( other->r.currentOrigin[ axis ] - ent->r.absmax[ axis ] ) < + fabs( other->r.currentOrigin[ axis ] - ent->r.absmin[ axis ] ) ) { origin[ axis ] = ent->r.absmin[ axis ] - 20; dir[ axis ] = -1; @@ -1177,7 +1261,7 @@ static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_ } vectoangles( dir, angles ); - TeleportPlayer( other, origin, angles ); + TeleportPlayer( other, origin, angles, 400.0f ); } @@ -1237,7 +1321,7 @@ static void manualDoorTriggerSpectator( gentity_t *door, gentity_t *player ) ================ manualTriggerSpectator -Trip to skip the closest door targetted by trigger +Trip to skip the closest door targeted by trigger ================ */ void manualTriggerSpectator( gentity_t *trigger, gentity_t *player ) @@ -1259,14 +1343,6 @@ void manualTriggerSpectator( gentity_t *trigger, gentity_t *player ) { if( !strcmp( t->classname, "func_door" ) ) targets[ i++ ] = t; - else if( t == trigger ) - G_Printf( "WARNING: Entity used itself.\n" ); - - if( !trigger->inuse ) - { - G_Printf( "triggerity was removed while using targets\n" ); - return; - } } //if more than 0 targets @@ -1299,25 +1375,30 @@ Touch_DoorTrigger */ void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) { + moverState_t teamState; + //buildables don't trigger movers if( other->s.eType == ET_BUILDABLE ) return; - if( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) + teamState = GetMoverTeamState( ent->parent ); + + if( other->client && other->client->sess.spectatorState != SPECTATOR_NOT ) { // if the door is not open and not opening - if( ent->parent->moverState != MOVER_1TO2 && - ent->parent->moverState != MOVER_POS2 && - ent->parent->moverState != ROTATOR_1TO2 && - ent->parent->moverState != ROTATOR_POS2 ) + if( teamState != MOVER_POS2 && teamState != MOVER_1TO2 ) Touch_DoorTriggerSpectator( ent, other, trace ); } - else if( ent->parent->moverState != MOVER_1TO2 && - ent->parent->moverState != ROTATOR_1TO2 && - ent->parent->moverState != ROTATOR_2TO1 ) - { + else if( teamState != MOVER_1TO2 ) Use_BinaryMover( ent->parent, ent, other ); - } +} + + +void Think_MatchTeam( gentity_t *ent ) +{ + if( ent->flags & FL_TEAMSLAVE ) + return; + MatchTeam( ent, ent->moverState, level.time ); } @@ -1335,11 +1416,6 @@ void Think_SpawnNewDoorTrigger( gentity_t *ent ) vec3_t mins, maxs; int i, best; - //TA: disable shootable doors - // set all of the slaves as shootable - //for( other = ent; other; other = other->teamchain ) - // other->takedamage = qtrue; - // find the bounds of everything on the team VectorCopy( ent->r.absmin, mins ); VectorCopy( ent->r.absmax, maxs ); @@ -1374,12 +1450,7 @@ void Think_SpawnNewDoorTrigger( gentity_t *ent ) trap_LinkEntity( other ); if( ent->moverState < MODEL_POS1 ) - MatchTeam( ent, ent->moverState, level.time ); -} - -void Think_MatchTeam( gentity_t *ent ) -{ - MatchTeam( ent, ent->moverState, level.time ); + Think_MatchTeam( ent ); } @@ -1406,6 +1477,7 @@ void SP_func_door( gentity_t *ent ) vec3_t size; float lip; char *s; + int health; G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s ); ent->sound2to1 = G_SoundIndex( s ); @@ -1436,11 +1508,13 @@ void SP_func_door( gentity_t *ent ) G_SpawnInt( "dmg", "2", &ent->damage ); // first position at start - VectorCopy( ent->s.origin, ent->pos1 ); + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + G_SetMovedir( ent->r.currentAngles, ent->movedir ); + VectorClear( ent->s.apos.trBase ); + VectorClear( ent->r.currentOrigin ); // calculate second position trap_SetBrushModel( ent, ent->model ); - G_SetMovedir( ent->s.angles, ent->movedir ); abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] ); abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] ); abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] ); @@ -1454,7 +1528,7 @@ void SP_func_door( gentity_t *ent ) vec3_t temp; VectorCopy( ent->pos2, temp ); - VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( ent->r.currentOrigin, ent->pos2 ); VectorCopy( temp, ent->pos1 ); } @@ -1462,22 +1536,17 @@ void SP_func_door( gentity_t *ent ) ent->nextthink = level.time + FRAMETIME; - if( !( ent->flags & FL_TEAMSLAVE ) ) - { - int health; - - G_SpawnInt( "health", "0", &health ); - if( health ) - ent->takedamage = qtrue; + G_SpawnInt( "health", "0", &health ); + if( health ) + ent->takedamage = qtrue; - if( ent->targetname || health ) - { - // non touch/shoot doors - ent->think = Think_MatchTeam; - } - else - ent->think = Think_SpawnNewDoorTrigger; + if( ent->targetname || health ) + { + // non touch/shoot doors + ent->think = Think_MatchTeam; } + else + ent->think = Think_SpawnNewDoorTrigger; } /*QUAKED func_door_rotating (0 .5 .8) START_OPEN CRUSHER REVERSE TOGGLE X_AXIS Y_AXIS @@ -1502,6 +1571,7 @@ void SP_func_door( gentity_t *ent ) void SP_func_door_rotating( gentity_t *ent ) { char *s; + int health; G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s ); ent->sound2to1 = G_SoundIndex( s ); @@ -1534,7 +1604,8 @@ void SP_func_door_rotating( gentity_t *ent ) // set the axis of rotation VectorClear( ent->movedir ); - VectorClear( ent->s.angles ); + VectorClear( ent->s.apos.trBase ); + VectorClear( ent->r.currentAngles ); if( ent->spawnflags & 32 ) ent->movedir[ 2 ] = 1.0; @@ -1552,12 +1623,12 @@ void SP_func_door_rotating( gentity_t *ent ) if( !ent->rotatorAngle ) { G_Printf( "%s at %s with no rotatorAngle set.\n", - ent->classname, vtos( ent->s.origin ) ); + ent->classname, vtos( ent->r.currentOrigin ) ); ent->rotatorAngle = 90.0; } - VectorCopy( ent->s.angles, ent->pos1 ); + VectorCopy( ent->r.currentAngles, ent->pos1 ); trap_SetBrushModel( ent, ent->model ); VectorMA( ent->pos1, ent->rotatorAngle, ent->movedir, ent->pos2 ); @@ -1567,36 +1638,26 @@ void SP_func_door_rotating( gentity_t *ent ) vec3_t temp; VectorCopy( ent->pos2, temp ); - VectorCopy( ent->s.angles, ent->pos2 ); + VectorCopy( ent->pos1, ent->pos2 ); VectorCopy( temp, ent->pos1 ); VectorNegate( ent->movedir, ent->movedir ); } - // set origin - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - InitRotator( ent ); ent->nextthink = level.time + FRAMETIME; - if( !( ent->flags & FL_TEAMSLAVE ) ) - { - int health; - - G_SpawnInt( "health", "0", &health ); - - if( health ) - ent->takedamage = qtrue; + G_SpawnInt( "health", "0", &health ); + if( health ) + ent->takedamage = qtrue; - if( ent->targetname || health ) - { - // non touch/shoot doors - ent->think = Think_MatchTeam; - } - else - ent->think = Think_SpawnNewDoorTrigger; + if( ent->targetname || health ) + { + // non touch/shoot doors + ent->think = Think_MatchTeam; } + else + ent->think = Think_SpawnNewDoorTrigger; } /*QUAKED func_door_model (0 .5 .8) ? START_OPEN @@ -1620,6 +1681,7 @@ void SP_func_door_model( gentity_t *ent ) qboolean lightSet, colorSet; char *sound; gentity_t *clipBrush; + int health; G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s ); ent->sound2to1 = G_SoundIndex( s ); @@ -1643,6 +1705,8 @@ void SP_func_door_model( gentity_t *ent ) //brush model clipBrush = ent->clipBrush = G_Spawn( ); + clipBrush->classname = "func_door_model_clip_brush"; + clipBrush->clipBrush = ent; // link back clipBrush->model = ent->model; trap_SetBrushModel( clipBrush, clipBrush->model ); clipBrush->s.eType = ET_INVISIBLE; @@ -1655,7 +1719,7 @@ void SP_func_door_model( gentity_t *ent ) VectorCopy( clipBrush->r.mins, ent->r.mins ); VectorCopy( clipBrush->r.maxs, ent->r.maxs ); - G_SpawnVector( "modelOrigin", "0 0 0", ent->s.origin ); + G_SpawnVector( "modelOrigin", "0 0 0", ent->r.currentOrigin ); G_SpawnVector( "scale", "1 1 1", ent->s.origin2 ); @@ -1666,9 +1730,9 @@ void SP_func_door_model( gentity_t *ent ) else ent->s.modelindex = G_ModelIndex( ent->model2 ); - // if the "loopsound" key is set, use a constant looping sound when moving - if( G_SpawnString( "noise", "100", &sound ) ) - ent->s.loopSound = G_SoundIndex( sound ); + // if the "noise" key is set, use a constant looping sound when moving + if( G_SpawnString( "noise", "", &sound ) ) + ent->soundLoop = G_SoundIndex( sound ); // if the "color" or "light" keys are set, setup constantLight lightSet = G_SpawnFloat( "light", "100", &light ); @@ -1701,19 +1765,17 @@ void SP_func_door_model( gentity_t *ent ) ent->moverState = MODEL_POS1; ent->s.eType = ET_MODELDOOR; - VectorCopy( ent->s.origin, ent->s.pos.trBase ); ent->s.pos.trType = TR_STATIONARY; ent->s.pos.trTime = 0; ent->s.pos.trDuration = 0; VectorClear( ent->s.pos.trDelta ); - VectorCopy( ent->s.angles, ent->s.apos.trBase ); ent->s.apos.trType = TR_STATIONARY; ent->s.apos.trTime = 0; ent->s.apos.trDuration = 0; VectorClear( ent->s.apos.trDelta ); - ent->s.misc = (int)ent->animation[ 0 ]; //first frame - ent->s.weapon = abs( (int)ent->animation[ 1 ] ); //number of frames + ent->s.misc = (int)ent->animation[ 0 ]; //first frame + ent->s.weapon = abs( (int)ent->animation[ 1 ] ); //number of frames //must be at least one frame -- mapper has forgotten animation key if( ent->s.weapon == 0 ) @@ -1723,19 +1785,14 @@ void SP_func_door_model( gentity_t *ent ) trap_LinkEntity( ent ); - if( !( ent->flags & FL_TEAMSLAVE ) ) - { - int health; - - G_SpawnInt( "health", "0", &health ); - if( health ) - ent->takedamage = qtrue; + G_SpawnInt( "health", "0", &health ); + if( health ) + ent->takedamage = qtrue; - if( !( ent->targetname || health ) ) - { - ent->nextthink = level.time + FRAMETIME; - ent->think = Think_SpawnNewDoorTrigger; - } + if( !( ent->targetname || health ) ) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_SpawnNewDoorTrigger; } } @@ -1751,7 +1808,7 @@ PLAT ============== Touch_Plat -Don't allow decent if a living player is on it +Don't allow to descend if a player is on it and is alive =============== */ void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) @@ -1860,7 +1917,8 @@ void SP_func_plat( gentity_t *ent ) G_SpawnString( "soundPos1", "sound/movers/plats/pt1_end.wav", &s ); ent->soundPos1 = G_SoundIndex( s ); - VectorClear( ent->s.angles ); + VectorClear( ent->s.apos.trBase ); + VectorClear( ent->r.currentAngles ); G_SpawnFloat( "speed", "200", &ent->speed ); G_SpawnInt( "dmg", "2", &ent->damage ); @@ -1876,7 +1934,7 @@ void SP_func_plat( gentity_t *ent ) height = ( ent->r.maxs[ 2 ] - ent->r.mins[ 2 ] ) - lip; // pos1 is the rest (bottom) position, pos2 is the top - VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( ent->r.currentOrigin, ent->pos2 ); VectorCopy( ent->pos2, ent->pos1 ); ent->pos1[ 2 ] -= height; @@ -1921,7 +1979,7 @@ void Touch_Button( gentity_t *ent, gentity_t *other, trace_t *trace ) /*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. +When a button is touched, it moves some distance in the direction of its angle, triggers all of its targets, waits some time, then returns to its original position where it can be triggered again. "model2" .md3 model to also draw "angle" determines the opening direction @@ -1953,14 +2011,16 @@ void SP_func_button( gentity_t *ent ) ent->wait *= 1000; // first position - VectorCopy( ent->s.origin, ent->pos1 ); + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + G_SetMovedir( ent->r.currentAngles, ent->movedir ); + VectorClear( ent->s.apos.trBase ); + VectorClear( ent->r.currentAngles ); // calculate second position trap_SetBrushModel( ent, ent->model ); G_SpawnFloat( "lip", "4", &lip ); - G_SetMovedir( ent->s.angles, ent->movedir ); abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] ); abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] ); abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] ); @@ -2031,8 +2091,8 @@ void Reached_Train( gentity_t *ent ) // set the new trajectory ent->nextTrain = next->nextTrain; - VectorCopy( next->s.origin, ent->pos1 ); - VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + VectorCopy( next->r.currentOrigin, ent->pos1 ); + VectorCopy( next->nextTrain->r.currentOrigin, ent->pos2 ); // if the path_corner has a speed, use that if( next->speed ) @@ -2056,6 +2116,19 @@ void Reached_Train( gentity_t *ent ) ent->s.pos.trDuration = length * 1000 / speed; + // Be sure to send to clients after any fast move case + ent->r.svFlags &= ~SVF_NOCLIENT; + + // Fast move case + if( ent->s.pos.trDuration < 1 ) + { + // As trDuration is used later in a division, we need to avoid that case now + ent->s.pos.trDuration = 1; + + // Don't send entity to clients so it becomes really invisible + ent->r.svFlags |= SVF_NOCLIENT; + } + // looping sound ent->s.loopSound = next->soundLoop; @@ -2160,7 +2233,7 @@ void Think_SetupTrainTargets( gentity_t *ent ) if( !path->target ) { G_Printf( "Train corner at %s without a target\n", - vtos( path->s.origin ) ); + vtos( path->r.currentOrigin ) ); return; } @@ -2175,7 +2248,7 @@ void Think_SetupTrainTargets( gentity_t *ent ) if( !next ) { G_Printf( "Train corner at %s without a target path_corner\n", - vtos( path->s.origin ) ); + vtos( path->r.currentOrigin ) ); return; } } while( strcmp( next->classname, "path_corner" ) ); @@ -2199,7 +2272,7 @@ void SP_path_corner( gentity_t *self ) { if( !self->targetname ) { - G_Printf( "path_corner with no targetname at %s\n", vtos( self->s.origin ) ); + G_Printf( "path_corner with no targetname at %s\n", vtos( self->r.currentOrigin ) ); G_FreeEntity( self ); return; } @@ -2231,16 +2304,16 @@ void Blocked_Train( gentity_t *self, gentity_t *other ) vec3_t dir; gentity_t *tent; - if( other->biteam == BIT_ALIENS ) + if( other->buildableTeam == TEAM_ALIENS ) { VectorCopy( other->s.origin2, dir ); - tent = G_TempEntity( other->s.origin, EV_ALIEN_BUILDABLE_EXPLOSION ); + tent = G_TempEntity( other->r.currentOrigin, EV_ALIEN_BUILDABLE_EXPLOSION ); tent->s.eventParm = DirToByte( dir ); } - else if( other->biteam == BIT_HUMANS ) + else if( other->buildableTeam == TEAM_HUMANS ) { VectorSet( dir, 0.0f, 0.0f, 1.0f ); - tent = G_TempEntity( other->s.origin, EV_HUMAN_BUILDABLE_EXPLOSION ); + tent = G_TempEntity( other->r.currentOrigin, EV_HUMAN_BUILDABLE_EXPLOSION ); tent->s.eventParm = DirToByte( dir ); } } @@ -2271,7 +2344,8 @@ The train spawns at the first target it is pointing at. */ void SP_func_train( gentity_t *self ) { - VectorClear( self->s.angles ); + VectorClear( self->s.apos.trBase ); + VectorClear( self->r.currentAngles ); if( self->spawnflags & TRAIN_BLOCK_STOPS ) self->damage = 0; @@ -2318,10 +2392,12 @@ A bmodel that just sits there, doing nothing. Can be used for conditional walls */ void SP_func_static( gentity_t *ent ) { + vec3_t savedOrigin; trap_SetBrushModel( ent, ent->model ); + VectorCopy( ent->r.currentOrigin, savedOrigin ); InitMover( ent ); - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - VectorCopy( ent->s.origin, ent->r.currentOrigin ); + VectorCopy( savedOrigin, ent->r.currentOrigin ); + VectorCopy( savedOrigin, ent->s.pos.trBase ); } @@ -2347,6 +2423,8 @@ check either the X_AXIS or Y_AXIS box to change that. */ void SP_func_rotating( gentity_t *ent ) { + vec3_t savedOrigin; + if( !ent->speed ) ent->speed = 100; @@ -2364,11 +2442,10 @@ void SP_func_rotating( gentity_t *ent ) ent->damage = 2; trap_SetBrushModel( ent, ent->model ); + VectorCopy( ent->r.currentOrigin, savedOrigin ); InitMover( ent ); - - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); + VectorCopy( savedOrigin, ent->r.currentOrigin ); + VectorCopy( savedOrigin, ent->s.pos.trBase ); trap_LinkEntity( ent ); } @@ -2397,6 +2474,7 @@ void SP_func_bobbing( gentity_t *ent ) { float height; float phase; + vec3_t savedOrigin; G_SpawnFloat( "speed", "4", &ent->speed ); G_SpawnFloat( "height", "32", &height ); @@ -2404,10 +2482,10 @@ void SP_func_bobbing( gentity_t *ent ) G_SpawnFloat( "phase", "0", &phase ); trap_SetBrushModel( ent, ent->model ); + VectorCopy( ent->r.currentOrigin, savedOrigin ); InitMover( ent ); - - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - VectorCopy( ent->s.origin, ent->r.currentOrigin ); + VectorCopy( savedOrigin, ent->r.currentOrigin ); + VectorCopy( savedOrigin, ent->s.pos.trBase ); ent->s.pos.trDuration = ent->speed * 1000; ent->s.pos.trTime = ent->s.pos.trDuration * phase; @@ -2448,6 +2526,7 @@ void SP_func_pendulum( gentity_t *ent ) float length; float phase; float speed; + vec3_t savedOrigin; G_SpawnFloat( "speed", "30", &speed ); G_SpawnInt( "dmg", "2", &ent->damage ); @@ -2465,12 +2544,10 @@ void SP_func_pendulum( gentity_t *ent ) ent->s.pos.trDuration = ( 1000 / freq ); + VectorCopy( ent->r.currentOrigin, savedOrigin ); InitMover( ent ); - - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - VectorCopy( ent->s.origin, ent->r.currentOrigin ); - - VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorCopy( savedOrigin, ent->r.currentOrigin ); + VectorCopy( savedOrigin, ent->s.pos.trBase ); ent->s.apos.trDuration = 1000 / freq; ent->s.apos.trTime = ent->s.apos.trDuration * phase; diff --git a/src/game/g_namelog.c b/src/game/g_namelog.c new file mode 100644 index 0000000..b789743 --- /dev/null +++ b/src/game/g_namelog.c @@ -0,0 +1,128 @@ +/* +=========================================================================== +Copyright (C) 2010 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "g_local.h" + +void G_namelog_cleanup( void ) +{ + namelog_t *namelog, *n; + + for( namelog = level.namelogs; namelog; namelog = n ) + { + n = namelog->next; + BG_Free( namelog ); + } +} + +void G_namelog_connect( gclient_t *client ) +{ + namelog_t *n, *p = NULL; + int i; + char *newname; + + for( n = level.namelogs; n; p = n, n = n->next ) + { + if( n->slot != -1 ) + continue; + if( !Q_stricmp( client->pers.guid, n->guid ) ) + break; + } + if( !n ) + { + n = BG_Alloc( sizeof( namelog_t ) ); + strcpy( n->guid, client->pers.guid ); + n->guidless = client->pers.guidless; + if( p ) + { + p->next = n; + n->id = p->id + 1; + } + else + { + level.namelogs = n; + n->id = MAX_CLIENTS; + } + } + client->pers.namelog = n; + n->slot = client - level.clients; + n->banned = qfalse; + + newname = n->name[ n->nameOffset ]; + // If they're muted, copy in their last known name - this will stop people + // reconnecting to get around the name change protection. + if( n->muted && G_admin_name_check( &g_entities[ n->slot ], + newname, NULL, 0 ) ) + Q_strncpyz( client->pers.netname, newname, MAX_NAME_LENGTH ); + + for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) + if( !strcmp( n->ip[ i ].str, client->pers.ip.str ) ) + return; + if( i == MAX_NAMELOG_ADDRS ) + i--; + memcpy( &n->ip[ i ], &client->pers.ip, sizeof( n->ip[ i ] ) ); +} + +void G_namelog_disconnect( gclient_t *client ) +{ + if( client->pers.namelog == NULL ) + return; + client->pers.namelog->slot = -1; + client->pers.namelog = NULL; +} + +void G_namelog_update_score( gclient_t *client ) +{ + namelog_t *n = client->pers.namelog; + if( n == NULL ) + return; + + n->team = client->pers.teamSelection; + n->score = client->ps.persistant[ PERS_SCORE ]; + n->credits = client->pers.credit; +} + +void G_namelog_update_name( gclient_t *client ) +{ + char n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ]; + namelog_t *n = client->pers.namelog; + + if( n->name[ n->nameOffset ][ 0 ] ) + { + G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) ); + G_SanitiseString( n->name[ n->nameOffset ], + n2, sizeof( n2 ) ); + if( strcmp( n1, n2 ) != 0 ) + n->nameOffset = ( n->nameOffset + 1 ) % MAX_NAMELOG_NAMES; + } + strcpy( n->name[ n->nameOffset ], client->pers.netname ); +} + +void G_namelog_restore( gclient_t *client ) +{ + namelog_t *n = client->pers.namelog; + + G_ChangeTeam( g_entities + n->slot, n->team ); + client->ps.persistant[ PERS_SCORE ] = n->score; + client->ps.persistant[ PERS_CREDIT ] = 0; + G_AddCreditToClient( client, n->credits, qfalse ); +} diff --git a/src/game/g_physics.c b/src/game/g_physics.c index 58c6487..9384c17 100644 --- a/src/game/g_physics.c +++ b/src/game/g_physics.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -45,8 +46,8 @@ static void G_Bounce( gentity_t *ent, trace_t *trace ) if( ent->s.eType == ET_BUILDABLE ) { - minNormal = BG_FindMinNormalForBuildable( ent->s.modelindex ); - invert = BG_FindInvertNormalForBuildable( ent->s.modelindex ); + minNormal = BG_Buildable( ent->s.modelindex )->minNormal; + invert = BG_Buildable( ent->s.modelindex )->invertNormal; } else minNormal = 0.707f; @@ -61,7 +62,6 @@ static void G_Bounce( gentity_t *ent, trace_t *trace ) if( VectorLength( ent->s.pos.trDelta ) < 10 ) { - VectorMA( trace->endpos, 0.5f, trace->plane.normal, trace->endpos ); // make sure it is off ground G_SetOrigin( ent, trace->endpos ); ent->s.groundEntityNum = trace->entityNum; VectorCopy( trace->plane.normal, ent->s.origin2 ); @@ -69,8 +69,8 @@ static void G_Bounce( gentity_t *ent, trace_t *trace ) return; } + VectorMA( ent->r.currentOrigin, 0.15, trace->plane.normal, ent->r.currentOrigin ); VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); - VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin); ent->s.pos.trTime = level.time; } @@ -87,16 +87,15 @@ void G_Physics( gentity_t *ent, int msec ) vec3_t origin; trace_t tr; int contents; - int mask; - // if groundentity has been set to -1, it may have been pushed off an edge - if( ent->s.groundEntityNum == -1 ) + // if groundentity has been set to ENTITYNUM_NONE, ent may have been pushed off an edge + if( ent->s.groundEntityNum == ENTITYNUM_NONE ) { if( ent->s.eType == ET_BUILDABLE ) { - if( ent->s.pos.trType != BG_FindTrajectoryForBuildable( ent->s.modelindex ) ) + if( ent->s.pos.trType != BG_Buildable( ent->s.modelindex )->traj ) { - ent->s.pos.trType = BG_FindTrajectoryForBuildable( ent->s.modelindex ); + ent->s.pos.trType = BG_Buildable( ent->s.modelindex )->traj; ent->s.pos.trTime = level.time; } } @@ -107,12 +106,6 @@ void G_Physics( gentity_t *ent, int msec ) } } - // trace a line from the previous position to the current position - if( ent->clipmask ) - mask = ent->clipmask; - else - mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID; - if( ent->s.pos.trType == TR_STATIONARY ) { // check think function @@ -125,10 +118,10 @@ void G_Physics( gentity_t *ent, int msec ) VectorMA( origin, -2.0f, ent->s.origin2, origin ); - trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask ); + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, ent->clipmask ); if( tr.fraction == 1.0f ) - ent->s.groundEntityNum = -1; + ent->s.groundEntityNum = ENTITYNUM_NONE; ent->nextPhysicsTime = level.time + PHYSICS_TIME; } @@ -136,10 +129,12 @@ void G_Physics( gentity_t *ent, int msec ) return; } + // trace a line from the previous position to the current position + // get current position BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); - trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask ); + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, ent->clipmask ); VectorCopy( tr.endpos, ent->r.currentOrigin ); @@ -158,10 +153,11 @@ void G_Physics( gentity_t *ent, int msec ) contents = trap_PointContents( ent->r.currentOrigin, -1 ); if( contents & CONTENTS_NODROP ) { + if( ent->s.eType == ET_BUILDABLE ) + G_RemoveRangeMarkerFrom( ent ); G_FreeEntity( ent ); return; } G_Bounce( ent, &tr ); } - diff --git a/src/game/g_playermodel.c b/src/game/g_playermodel.c new file mode 100644 index 0000000..7f79b38 --- /dev/null +++ b/src/game/g_playermodel.c @@ -0,0 +1,193 @@ +// +// Author: blowFish +// + +#include "g_local.h" + +//------------------------------------------------------------------------- +// Player models +//------------------------------------------------------------------------- +// +static qboolean +_is_playermodel_uniq(const char *model) +{ + int i; + for ( i = 0; i < level.playerModelCount; i++ ) + { + if ( !strcmp( model, level.playerModel[i] ) ) + return qfalse; + } + + return qtrue; +} + +static void +G_AddPlayerModel(const char *model) +{ + if (!_is_playermodel_uniq(model)) + return; + + // HACK! + if (!strcmp(model, "human_bsuit")) + return; + + level.playerModel[ level.playerModelCount ] = G_CopyString(model); + level.playerModelCount++; +} + +void G_InitPlayerModel(void) +{ + char fileList[ 16*1024 ] = {""}; + char *filePtr; + int numFiles; + int fileLen = 0; + int i; + + // TODO: Add an FS trap which is does correct file globbing + numFiles = trap_FS_GetFilteredFiles( "/models/players", "", + "models*players*head_*.skin", + fileList, sizeof(fileList) ); + filePtr = fileList; + + for( i = 0; i < numFiles && level.playerModelCount < MAX_PLAYER_MODEL; + i++, filePtr += fileLen + 1 ) + { + char *start, *c; + + fileLen = strlen( filePtr ); + + // skip leading '/' + start = filePtr + 15; + + // Only want directory names at the current depth. + for ( c = start; c != '\0'; c++ ) + { + if ( *c == '/' || *c == '\\' ) + { + *c = '\0'; + break; + } + } + + G_AddPlayerModel(start); + } +} + +qboolean G_IsValidPlayerModel(const char *model) +{ + return !_is_playermodel_uniq(model); +} + +void G_FreePlayerModel(void) +{ + int i; + for ( i = 0; i < level.playerModelCount; i++ ) + BG_Free( level.playerModel[i] ); +} + +//------------------------------------------------------------------------- +// Skins +//------------------------------------------------------------------------- + +void G_GetPlayerModelSkins( const char *modelname, char skins[][ 64 ], + int maxskins, int *numskins ) +{ + char fileList[ 16*1024 ] = {""}; + int nFiles; + char *filePtr; + int fileLen = 0; + int i; + + *numskins = 0; + nFiles = trap_FS_GetFilteredFiles("models/players", ".skin", + va("models*players*%s*skin", modelname), + fileList, sizeof(fileList)); + filePtr = fileList; + for (i = 0; i < nFiles && i < maxskins; i++ ) + { + char *start, *end; + + fileLen = strlen( filePtr ); + + start = filePtr; + start += strlen(va("models/players/%s/", modelname)); + + end = filePtr + fileLen; + end -= 5; + *end = '\0'; + filePtr += fileLen + 1; + + // dumb way to filter out the unique skins of segmented and + // nonsegmented models. + // TODO: Stop writing code at 4am. + if ( start[0] == 'h' + && start[1] == 'e' + && start[2] == 'a' + && start[3] == 'd' + && start[4] == '_' ) + start += 5; + + else if ( start[0] == 'n' + && start[1] == 'o' + && start[2] == 'n' + && start[3] == 's' + && start[4] == 'e' + && start[5] == 'g' + && start[6] == '_' ) + start += 7; + + else + continue; + + strncpy(skins[*numskins], start, 64 ); + (*numskins)++; + } +} + +/* +====================== +GetSkin + +Probably should be called GetSkin[or]Default. Tries to recreate what +appears to be an undocumented set of conventions that must be allowed +in other q3 derives. + +This algorithm is not really good enough for Tremulous considering +armour + upgrade/advanced in gameplay + +XXX Move this into bg_ +====================== +*/ +char *GetSkin( char *modelname, char *wish ) +{ + char skins[ MAX_PLAYER_MODEL ][ 64 ]; + int numskins; + int i; + qboolean foundDefault = qfalse; + qboolean foundSelfNamed = qfalse; + static char lastpick[ 64 ] = {""}; + lastpick[0] = '\0'; // reset static buf + + G_GetPlayerModelSkins(modelname, skins, MAX_PLAYER_MODEL, &numskins); + + for (i = 0; i < numskins; i++) + { + if ( i == 0 ) + strncpy(lastpick, skins[0], 64 ); + + if ( !strcmp(wish, skins[i]) ) + return wish; + else if ( !strcmp("default", skins[i])) + foundDefault = qtrue; + else if ( !strcmp(modelname, skins[i])) + foundSelfNamed = qtrue; + } + + if (foundDefault) + return "default"; + else if (foundSelfNamed) + return modelname; + + return lastpick; +} + diff --git a/src/game/g_ptr.c b/src/game/g_ptr.c deleted file mode 100644 index e102183..0000000 --- a/src/game/g_ptr.c +++ /dev/null @@ -1,143 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous 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. - -Tremulous 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// g_ptr.c -- post timeout restoration handling - -#include "g_local.h" - -static connectionRecord_t connections[ MAX_CLIENTS ]; - -/* -=============== -G_CheckForUniquePTRC - -Callback to detect ptrc clashes -=============== -*/ -static qboolean G_CheckForUniquePTRC( int code ) -{ - int i; - - if( code == 0 ) - return qfalse; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - if( connections[ i ].ptrCode == code ) - return qfalse; - } - - return qtrue; -} - -/* -=============== -G_UpdatePTRConnection - -Update the data in a connection record -=============== -*/ -void G_UpdatePTRConnection( gclient_t *client ) -{ - if( client && client->pers.connection ) - { - client->pers.connection->clientTeam = client->pers.teamSelection; - client->pers.connection->clientCredit = client->pers.credit; - client->pers.connection->clientScore = client->pers.score; - } -} - -/* -=============== -G_GenerateNewConnection - -Generates a new connection -=============== -*/ -connectionRecord_t *G_GenerateNewConnection( gclient_t *client ) -{ - int code = 0; - int i; - - // this should be really random - srand( trap_Milliseconds( ) ); - - // there is a very very small possibility that this - // will loop infinitely - do - { - code = rand( ); - } while( !G_CheckForUniquePTRC( code ) ); - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - //found an unused slot - if( !connections[ i ].ptrCode ) - { - connections[ i ].ptrCode = code; - connections[ i ].clientNum = client->ps.clientNum; - client->pers.connection = &connections[ i ]; - G_UpdatePTRConnection( client ); - client->pers.connection->clientEnterTime = client->pers.enterTime; - - return &connections[ i ]; - } - } - - return NULL; -} - -/* -=============== -G_FindConnectionForCode - -Finds a connection for a given code -=============== -*/ -connectionRecord_t *G_FindConnectionForCode( int code ) -{ - int i; - - if( code == 0 ) - return NULL; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - if( connections[ i ].ptrCode == code ) - return &connections[ i ]; - } - - return NULL; -} - -/* -=============== -G_ResetPTRConnections - -Invalidate any existing codes -=============== -*/ -void G_ResetPTRConnections( void ) -{ - memset( connections, 0, sizeof( connectionRecord_t ) * MAX_CLIENTS ); -} diff --git a/src/game/g_public.h b/src/game/g_public.h index e1e01d7..415ab96 100644 --- a/src/game/g_public.h +++ b/src/game/g_public.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,252 +17,253 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // g_public.h -- game module information visible to server +#ifndef GAME_PUBLIC_H +#define GAME_PUBLIC_H + +#include "qcommon/q_shared.h" -#define GAME_API_VERSION 8 +#define GAME_API_VERSION 9 // entity->svFlags // the server does not know how to interpret most of the values // in entityStates (level eType), so the game must explicitly flag // special server behaviors -#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects - -// TTimo -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=551 -#define SVF_CLIENTMASK 0x00000002 - -#define SVF_BROADCAST 0x00000020 // send to all connected clients -#define SVF_PORTAL 0x00000040 // merge a second pvs at origin2 into snapshots -#define SVF_USE_CURRENT_ORIGIN 0x00000080 // entity->r.currentOrigin instead of entity->s.origin - // for link position (missiles and movers) -#define SVF_SINGLECLIENT 0x00000100 // only send to a single client (entityShared_t->singleClient) -#define SVF_NOSERVERINFO 0x00000200 // don't send CS_SERVERINFO updates to this client - // so that it can be updated for ping tools without - // lagging clients -#define SVF_CAPSULE 0x00000400 // use capsule for collision detection instead of bbox -#define SVF_NOTSINGLECLIENT 0x00000800 // send entity to everyone but one client - // (entityShared_t->singleClient) +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects -//=============================================================== +#define SVF_CLIENTMASK 0x00000002 // send to clients specified by these bitmasks: +// entityShared_t->singleClient: low-order bits (0..31) +// entityShared_t->hack.generic1: high-order bits (32..63) +#define SVF_BROADCAST 0x00000020 // send to all connected clients +#define SVF_PORTAL 0x00000040 // merge a second pvs at origin2 into snapshots -typedef struct { - entityState_t s; // communicated by server to clients - - qboolean linked; // qfalse if not in any good cluster - int linkcount; - - int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc - int singleClient; // only send to this client when SVF_SINGLECLIENT is set - - qboolean bmodel; // if false, assume an explicit mins / maxs bounding box - // only set by trap_SetBrushModel - vec3_t mins, maxs; - int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc - // a non-solid entity should set to 0 - - vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation - - // currentOrigin will be used for all collision detection and world linking. - // it will not necessarily be the same as the trajectory evaluation for the current - // time, because each entity must be moved one at a time after time is advanced - // to avoid simultanious collision issues - vec3_t currentOrigin; - vec3_t currentAngles; - - // when a trace call is made and passEntityNum != ENTITYNUM_NONE, - // an ent will be excluded from testing if: - // ent->s.number == passEntityNum (don't interact with self) - // ent->s.ownerNum = passEntityNum (don't interact with your own missiles) - // entity[ent->s.ownerNum].ownerNum = passEntityNum (don't interact with other missiles from owner) - int ownerNum; -} entityShared_t; +#define SVF_SINGLECLIENT 0x00000100 // only send to a single client (entityShared_t->singleClient) +#define SVF_NOSERVERINFO 0x00000200 // don't send CS_SERVERINFO updates to this client +// so that it can be updated for ping tools without +// lagging clients +#define SVF_CAPSULE 0x00000400 // use capsule for collision detection instead of bbox +#define SVF_NOTSINGLECLIENT 0x00000800 // send entity to everyone but one client +// (entityShared_t->singleClient) +//=============================================================== +typedef struct { + entityState_t hack; // exists (as padding) to retain ABI compatibility + // with GPP, but can be used for extension hacks + + qboolean linked; // qfalse if not in any good cluster + int linkcount; + + int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc. + int singleClient; // only send to this client when SVF_SINGLECLIENT is set + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by trap_SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + // when a trace call is made and passEntityNum != ENTITYNUM_NONE, + // an ent will be excluded from testing if: + // ent->s.number == passEntityNum (don't interact with self) + // ent->r.ownerNum == passEntityNum (don't interact with your own missiles) + // entity[ent->r.ownerNum].r.ownerNum == passEntityNum (don't interact with other missiles from owner) + int ownerNum; +} entityShared_t; // the server looks at a sharedEntity, which is the start of the game's gentity_t structure typedef struct { - entityState_t s; // communicated by server to clients - entityShared_t r; // shared by both the server system and game + entityState_t s; // communicated by server to clients + entityShared_t r; // shared by both the server system and game } sharedEntity_t; - - //=============================================================== // // system traps provided by the main engine // typedef enum { - //============== general Quake services ================== - - G_PRINT, // ( const char *string ); - // print message on the local console + //============== general Quake services ================== - G_ERROR, // ( const char *string ); - // abort the game + G_PRINT, // ( const char *string ); + // print message on the local console - G_MILLISECONDS, // ( void ); - // get current time for profiling reasons - // this should NOT be used for any game related tasks, - // because it is not journaled + G_ERROR, // ( const char *string ); + // abort the game - // console variable interaction - G_CVAR_REGISTER, // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); - G_CVAR_UPDATE, // ( vmCvar_t *vmCvar ); - G_CVAR_SET, // ( const char *var_name, const char *value ); - G_CVAR_VARIABLE_INTEGER_VALUE, // ( const char *var_name ); + G_MILLISECONDS, // ( void ); + // get current time for profiling reasons + // this should NOT be used for any game related tasks, + // because it is not journaled - G_CVAR_VARIABLE_STRING_BUFFER, // ( const char *var_name, char *buffer, int bufsize ); + // console variable interaction + G_CVAR_REGISTER, // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); + G_CVAR_UPDATE, // ( vmCvar_t *vmCvar ); + G_CVAR_SET, // ( const char *var_name, const char *value ); + G_CVAR_VARIABLE_INTEGER_VALUE, // ( const char *var_name ); - G_ARGC, // ( void ); - // ClientCommand and ServerCommand parameter access + G_CVAR_VARIABLE_STRING_BUFFER, // ( const char *var_name, char *buffer, int bufsize ); - G_ARGV, // ( int n, char *buffer, int bufferLength ); + G_ARGC, // ( void ); + // ClientCommand and ServerCommand parameter access - G_FS_FOPEN_FILE, // ( const char *qpath, fileHandle_t *file, fsMode_t mode ); - G_FS_READ, // ( void *buffer, int len, fileHandle_t f ); - G_FS_WRITE, // ( const void *buffer, int len, fileHandle_t f ); - G_FS_FCLOSE_FILE, // ( fileHandle_t f ); + G_ARGV, // ( int n, char *buffer, int bufferLength ); - G_SEND_CONSOLE_COMMAND, // ( const char *text ); - // add commands to the console as if they were typed in - // for map changing, etc + G_FS_FOPEN_FILE, // ( const char *qpath, fileHandle_t *file, enum FS_Mode mode ); + G_FS_READ, // ( void *buffer, int len, fileHandle_t f ); + G_FS_WRITE, // ( const void *buffer, int len, fileHandle_t f ); + G_FS_FCLOSE_FILE, // ( fileHandle_t f ); + G_SEND_CONSOLE_COMMAND, // ( const char *text ); + // add commands to the console as if they were typed in + // for map changing, etc - //=========== server specific functionality ============= + //=========== server specific functionality ============= - G_LOCATE_GAME_DATA, // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, - // playerState_t *clients, int sizeofGameClient ); - // the game needs to let the server system know where and how big the gentities - // are, so it can look at them directly without going through an interface + G_LOCATE_GAME_DATA, // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + // playerState_t *clients, int sizeofGameClient ); + // the game needs to let the server system know where and how big the gentities + // are, so it can look at them directly without going through an interface - G_DROP_CLIENT, // ( int clientNum, const char *reason ); - // kick a client off the server with a message + G_DROP_CLIENT, // ( int clientNum, const char *reason ); + // kick a client off the server with a message - G_SEND_SERVER_COMMAND, // ( int clientNum, const char *fmt, ... ); - // reliably sends a command string to be interpreted by the given - // client. If clientNum is -1, it will be sent to all clients + G_SEND_SERVER_COMMAND, // ( int clientNum, const char *fmt, ... ); + // reliably sends a command string to be interpreted by the given + // client. If clientNum is -1, it will be sent to all clients - G_SET_CONFIGSTRING, // ( int num, const char *string ); - // config strings hold all the index strings, and various other information - // that is reliably communicated to all clients - // All of the current configstrings are sent to clients when - // they connect, and changes are sent to all connected clients. - // All confgstrings are cleared at each level start. + G_SET_CONFIGSTRING, // ( int num, const char *string ); + // config strings hold all the index strings, and various other information + // that is reliably communicated to all clients + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + // All confgstrings are cleared at each level start. - G_GET_CONFIGSTRING, // ( int num, char *buffer, int bufferSize ); + G_GET_CONFIGSTRING, // ( int num, char *buffer, int bufferSize ); - G_SET_CONFIGSTRING_RESTRICTIONS, // ( int num, const clientList* clientList ); + G_SET_CONFIGSTRING_RESTRICTIONS, // ( int num, const clientList* clientList ); - G_GET_USERINFO, // ( int num, char *buffer, int bufferSize ); - // userinfo strings are maintained by the server system, so they - // are persistant across level loads, while all other game visible - // data is completely reset + G_GET_USERINFO, // ( int num, char *buffer, int bufferSize ); + // userinfo strings are maintained by the server system, so they + // are persistant across level loads, while all other game visible + // data is completely reset - G_SET_USERINFO, // ( int num, const char *buffer ); + G_SET_USERINFO, // ( int num, const char *buffer ); - G_GET_SERVERINFO, // ( char *buffer, int bufferSize ); - // the serverinfo info string has all the cvars visible to server browsers + G_GET_SERVERINFO, // ( char *buffer, int bufferSize ); + // the serverinfo info string has all the cvars visible to server browsers - G_SET_BRUSH_MODEL, // ( gentity_t *ent, const char *name ); - // sets mins and maxs based on the brushmodel name + G_SET_BRUSH_MODEL, // ( gentity_t *ent, const char *name ); + // sets mins and maxs based on the brushmodel name - G_TRACE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); - // collision detection against all linked entities + G_TRACE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int + // passEntityNum, int contentmask ); + // collision detection against all linked entities - G_POINT_CONTENTS, // ( const vec3_t point, int passEntityNum ); - // point contents against all linked entities + G_POINT_CONTENTS, // ( const vec3_t point, int passEntityNum ); + // point contents against all linked entities - G_IN_PVS, // ( const vec3_t p1, const vec3_t p2 ); + G_IN_PVS, // ( const vec3_t p1, const vec3_t p2 ); - G_IN_PVS_IGNORE_PORTALS, // ( const vec3_t p1, const vec3_t p2 ); + G_IN_PVS_IGNORE_PORTALS, // ( const vec3_t p1, const vec3_t p2 ); - G_ADJUST_AREA_PORTAL_STATE, // ( gentity_t *ent, qboolean open ); + G_ADJUST_AREA_PORTAL_STATE, // ( gentity_t *ent, qboolean open ); - G_AREAS_CONNECTED, // ( int area1, int area2 ); + G_AREAS_CONNECTED, // ( int area1, int area2 ); - G_LINKENTITY, // ( gentity_t *ent ); - // 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. + G_LINKENTITY, // ( gentity_t *ent ); + // 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. - G_UNLINKENTITY, // ( gentity_t *ent ); - // call before removing an interactive entity + G_UNLINKENTITY, // ( gentity_t *ent ); + // call before removing an interactive entity - G_ENTITIES_IN_BOX, // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); - // EntitiesInBox will return brush models based on their bounding box, - // so exact determination must still be done with EntityContact + G_ENTITIES_IN_BOX, // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); + // EntitiesInBox will return brush models based on their bounding box, + // so exact determination must still be done with EntityContact - G_ENTITY_CONTACT, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); - // perform an exact check against inline brush models of non-square shape + G_ENTITY_CONTACT, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + // perform an exact check against inline brush models of non-square shape - G_GET_USERCMD, // ( int clientNum, usercmd_t *cmd ) + G_GET_USERCMD, // ( int clientNum, usercmd_t *cmd ) - G_GET_ENTITY_TOKEN, // qboolean ( char *buffer, int bufferSize ) - // Retrieves the next string token from the entity spawn text, returning - // false when all tokens have been parsed. - // This should only be done at GAME_INIT time. + G_GET_ENTITY_TOKEN, // qboolean ( char *buffer, int bufferSize ) + // Retrieves the next string token from the entity spawn text, returning + // false when all tokens have been parsed. + // This should only be done at GAME_INIT time. - G_FS_GETFILELIST, - G_REAL_TIME, - G_SNAPVECTOR, + G_FS_GETFILELIST, + G_REAL_TIME, + G_SNAPVECTOR, - G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); - G_ENTITY_CONTACTCAPSULE, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + // int passEntityNum, int contentmask ); + G_ENTITY_CONTACTCAPSULE, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); - // 1.32 - G_FS_SEEK, + // 1.32 + G_FS_SEEK, - G_PARSE_ADD_GLOBAL_DEFINE, - G_PARSE_LOAD_SOURCE, - G_PARSE_FREE_SOURCE, - G_PARSE_READ_TOKEN, - G_PARSE_SOURCE_FILE_AND_LINE, + G_PARSE_ADD_GLOBAL_DEFINE, + G_PARSE_LOAD_SOURCE, + G_PARSE_FREE_SOURCE, + G_PARSE_READ_TOKEN, + G_PARSE_SOURCE_FILE_AND_LINE, - G_SEND_GAMESTAT, + G_SEND_GAMESTAT, - G_ADDCOMMAND, - G_REMOVECOMMAND + G_ADDCOMMAND, + G_REMOVECOMMAND, + G_FS_GETFILTEREDFILES } gameImport_t; - // // functions exported by the game subsystem // typedef enum { - GAME_INIT, // ( int levelTime, int randomSeed, int restart ); - // init and shutdown will be called every single level - // The game should call G_GET_ENTITY_TOKEN to parse through all the - // entity configuration text and spawn gentities. + GAME_INIT, // ( int levelTime, int randomSeed, int restart ); + // init and shutdown will be called every single level + // The game should call G_GET_ENTITY_TOKEN to parse through all the + // entity configuration text and spawn gentities. - GAME_SHUTDOWN, // (void); + GAME_SHUTDOWN, // (void); - GAME_CLIENT_CONNECT, // ( int clientNum, qboolean firstTime ); - // return NULL if the client is allowed to connect, otherwise return - // a text string with the reason for denial + GAME_CLIENT_CONNECT, // ( int clientNum, qboolean firstTime ); + // return NULL if the client is allowed to connect, otherwise return + // a text string with the reason for denial - GAME_CLIENT_BEGIN, // ( int clientNum ); + GAME_CLIENT_BEGIN, // ( int clientNum ); - GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum ); + GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum ); - GAME_CLIENT_DISCONNECT, // ( int clientNum ); + GAME_CLIENT_DISCONNECT, // ( int clientNum ); - GAME_CLIENT_COMMAND, // ( int clientNum ); + GAME_CLIENT_COMMAND, // ( int clientNum ); - GAME_CLIENT_THINK, // ( int clientNum ); + GAME_CLIENT_THINK, // ( int clientNum ); - GAME_RUN_FRAME, // ( int levelTime ); + GAME_RUN_FRAME, // ( int levelTime ); - GAME_CONSOLE_COMMAND // ( void ); - // ConsoleCommand will be called when a command has been issued - // that is not recognized as a builtin function. - // The game can issue trap_argc() / trap_argv() commands to get the command - // and parameters. Return qfalse if the game doesn't recognize it as a command. + GAME_CONSOLE_COMMAND // ( void ); + // ConsoleCommand will be called when a command has been issued + // that is not recognized as a builtin function. + // The game can issue trap_argc() / trap_argv() commands to get the command + // and parameters. Return qfalse if the game doesn't recognize it as a command. } gameExport_t; +#endif diff --git a/src/game/g_session.c b/src/game/g_session.c index ef78e8a..9063ce1 100644 --- a/src/game/g_session.c +++ b/src/game/g_session.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -46,20 +47,15 @@ void G_WriteClientSessionData( gclient_t *client ) const char *s; const char *var; - s = va( "%i %i %i %i %i %i %i %i %i %s", - client->sess.sessionTeam, - client->sess.restartTeam, + s = va( "%i %i %i %i %s", client->sess.spectatorTime, client->sess.spectatorState, client->sess.spectatorClient, - client->sess.wins, - client->sess.losses, - client->sess.teamLeader, - client->sess.invisible, - BG_ClientListString( &client->sess.ignoreList ) + client->sess.restartTeam, + Com_ClientListString( &client->sess.ignoreList ) ); - var = va( "session%i", client - level.clients ); + var = va( "session%i", (int)( client - level.clients ) ); trap_Cvar_Set( var, s ); } @@ -73,40 +69,26 @@ Called on a reconnect */ void G_ReadSessionData( gclient_t *client ) { - char s[ MAX_STRING_CHARS ]; + char s[ MAX_STRING_CHARS ]; const char *var; + int spectatorState; + int restartTeam; + char ignorelist[ 17 ]; - // bk001205 - format - int teamLeader; - int spectatorState; - int sessionTeam; - int restartTeam; - int invisible; - - var = va( "session%i", client - level.clients ); + var = va( "session%i", (int)( client - level.clients ) ); trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); - // FIXME: should be using BG_ClientListParse() for ignoreList, but - // bg_lib.c's sscanf() currently lacks %s - sscanf( s, "%i %i %i %i %i %i %i %i %i %x%x", - &sessionTeam, - &restartTeam, + sscanf( s, "%i %i %i %i %16s", &client->sess.spectatorTime, &spectatorState, &client->sess.spectatorClient, - &client->sess.wins, - &client->sess.losses, - &teamLeader, - &invisible, - &client->sess.ignoreList.hi, - &client->sess.ignoreList.lo + &restartTeam, + ignorelist ); - // bk001205 - format issues - client->sess.sessionTeam = (team_t)sessionTeam; - client->sess.restartTeam = (pTeam_t)restartTeam; + client->sess.spectatorState = (spectatorState_t)spectatorState; - client->sess.teamLeader = (qboolean)teamLeader; - client->sess.invisible = (qboolean)invisible; + client->sess.restartTeam = (team_t)restartTeam; + Com_ClientListParse( &client->sess.ignoreList, ignorelist ); } @@ -129,18 +111,18 @@ void G_InitSessionData( gclient_t *client, char *userinfo ) if( value[ 0 ] == 's' ) { // a willing spectator, not a waiting-in-line - sess->sessionTeam = TEAM_SPECTATOR; + sess->spectatorState = SPECTATOR_FREE; } else { if( g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer ) - sess->sessionTeam = TEAM_SPECTATOR; + sess->spectatorState = SPECTATOR_FREE; else - sess->sessionTeam = TEAM_FREE; + sess->spectatorState = SPECTATOR_NOT; } - sess->restartTeam = PTE_NONE; + sess->restartTeam = TEAM_NONE; sess->spectatorState = SPECTATOR_FREE; sess->spectatorTime = level.time; sess->spectatorClient = -1; @@ -160,7 +142,7 @@ void G_WriteSessionData( void ) { int i; - //TA: ? + //FIXME: What's this for? trap_Cvar_Set( "session", va( "%i", 0 ) ); for( i = 0 ; i < level.maxclients ; i++ ) diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index 028c39f..f7eea93 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -31,6 +32,7 @@ qboolean G_SpawnString( const char *key, const char *defaultString, char **out ) { *out = (char *)defaultString; // G_Error( "G_SpawnString() called while not spawning" ); + return qfalse; } for( i = 0; i < level.numSpawnVars; i++ ) @@ -95,55 +97,45 @@ 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_STRING, F_VECTOR, - F_VECTOR4, //TA - F_ANGLEHACK, - F_ENTITY, // index on disk, pointer in memory - F_ITEM, // index on disk, pointer in memory - F_CLIENT, // index on disk, pointer in memory - F_IGNORE + F_VECTOR4, + F_ANGLEHACK } fieldtype_t; typedef struct { char *name; - int ofs; + size_t ofs; fieldtype_t type; - int flags; } field_t; field_t fields[ ] = { - {"classname", FOFS(classname), F_LSTRING}, - {"origin", FOFS(s.origin), F_VECTOR}, - {"model", FOFS(model), F_LSTRING}, - {"model2", FOFS(model2), F_LSTRING}, - {"spawnflags", FOFS(spawnflags), F_INT}, - {"speed", FOFS(speed), F_FLOAT}, - {"target", FOFS(target), F_LSTRING}, - {"targetname", FOFS(targetname), F_LSTRING}, - {"message", FOFS(message), F_LSTRING}, - {"team", FOFS(team), F_LSTRING}, - {"wait", FOFS(wait), F_FLOAT}, - {"random", FOFS(random), F_FLOAT}, + {"acceleration", FOFS(acceleration), F_VECTOR}, + {"alpha", FOFS(pos1), F_VECTOR}, + {"angle", FOFS(s.apos.trBase), F_ANGLEHACK}, + {"angles", FOFS(s.apos.trBase), F_VECTOR}, + {"animation", FOFS(animation), F_VECTOR4}, + {"bounce", FOFS(physicsBounce), F_FLOAT}, + {"classname", FOFS(classname), F_STRING}, {"count", FOFS(count), F_INT}, - {"health", FOFS(health), F_INT}, - {"light", 0, F_IGNORE}, {"dmg", FOFS(damage), F_INT}, - {"angles", FOFS(s.angles), F_VECTOR}, - {"angle", FOFS(s.angles), F_ANGLEHACK}, - {"bounce", FOFS(physicsBounce), F_FLOAT}, - {"alpha", FOFS(pos1), F_VECTOR}, + {"health", FOFS(health), F_INT}, + {"message", FOFS(message), F_STRING}, + {"model", FOFS(model), F_STRING}, + {"model2", FOFS(model2), F_STRING}, + {"origin", FOFS(s.pos.trBase), F_VECTOR}, {"radius", FOFS(pos2), F_VECTOR}, - {"acceleration", FOFS(acceleration), F_VECTOR}, - {"animation", FOFS(animation), F_VECTOR4}, + {"random", FOFS(random), F_FLOAT}, {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT}, - {"targetShaderName", FOFS(targetShaderName), F_LSTRING}, - {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING}, - - {NULL} + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"target", FOFS(target), F_STRING}, + {"targetname", FOFS(targetname), F_STRING}, + {"targetShaderName", FOFS(targetShaderName), F_STRING}, + {"targetShaderNewName", FOFS(targetShaderNewName), F_STRING}, + {"wait", FOFS(wait), F_FLOAT} }; @@ -160,11 +152,6 @@ void SP_info_player_intermission( gentity_t *ent ); void SP_info_alien_intermission( gentity_t *ent ); void SP_info_human_intermission( gentity_t *ent ); -void SP_info_firstplace( gentity_t *ent ); -void SP_info_secondplace( gentity_t *ent ); -void SP_info_thirdplace( gentity_t *ent ); -void SP_info_podium( gentity_t *ent ); - void SP_func_plat( gentity_t *ent ); void SP_func_static( gentity_t *ent ); void SP_func_rotating( gentity_t *ent ); @@ -194,7 +181,6 @@ void SP_trigger_ammo( gentity_t *ent ); void SP_target_delay( gentity_t *ent ); void SP_target_speaker( gentity_t *ent ); void SP_target_print( gentity_t *ent ); -void SP_target_character( gentity_t *ent ); void SP_target_score( gentity_t *ent ); void SP_target_teleporter( gentity_t *ent ); void SP_target_relay( gentity_t *ent ); @@ -210,7 +196,6 @@ void SP_target_hurt( gentity_t *ent ); void SP_light( gentity_t *self ); void SP_info_null( gentity_t *self ); void SP_info_notnull( gentity_t *self ); -void SP_info_camp( gentity_t *self ); void SP_path_corner( gentity_t *self ); void SP_misc_teleporter_dest( gentity_t *self ); @@ -218,41 +203,60 @@ void SP_misc_model( gentity_t *ent ); void SP_misc_portal_camera( gentity_t *ent ); void SP_misc_portal_surface( gentity_t *ent ); -void SP_shooter_rocket( gentity_t *ent ); -void SP_shooter_plasma( gentity_t *ent ); -void SP_shooter_grenade( gentity_t *ent ); - void SP_misc_particle_system( gentity_t *ent ); void SP_misc_anim_model( gentity_t *ent ); void SP_misc_light_flare( gentity_t *ent ); spawn_t spawns[ ] = { + { "func_bobbing", SP_func_bobbing }, + { "func_button", SP_func_button }, + { "func_door", SP_func_door }, + { "func_door_model", SP_func_door_model }, + { "func_door_rotating", SP_func_door_rotating }, + { "func_group", SP_info_null }, + { "func_pendulum", SP_func_pendulum }, + { "func_plat", SP_func_plat }, + { "func_rotating", SP_func_rotating }, + { "func_static", SP_func_static }, + { "func_timer", SP_func_timer }, // rename trigger_timer? + { "func_train", SP_func_train }, + // info entities don't do anything at all, but provide positional // information for things controlled by other processes - { "info_player_start", SP_info_player_start }, - { "info_player_deathmatch", SP_info_player_deathmatch }, - { "info_player_intermission", SP_info_player_intermission }, - - //TA: extra bits { "info_alien_intermission", SP_info_alien_intermission }, { "info_human_intermission", SP_info_human_intermission }, - - { "info_null", SP_info_null }, { "info_notnull", SP_info_notnull }, // use target_position instead + { "info_null", SP_info_null }, + { "info_player_deathmatch", SP_info_player_deathmatch }, + { "info_player_intermission", SP_info_player_intermission }, + { "info_player_start", SP_info_player_start }, + { "light", SP_light }, + { "misc_anim_model", SP_misc_anim_model }, + { "misc_light_flare", SP_misc_light_flare }, + { "misc_model", SP_misc_model }, + { "misc_particle_system", SP_misc_particle_system }, + { "misc_portal_camera", SP_misc_portal_camera }, + { "misc_portal_surface", SP_misc_portal_surface }, + { "misc_teleporter_dest", SP_misc_teleporter_dest }, + { "path_corner", SP_path_corner }, - { "func_plat", SP_func_plat }, - { "func_button", SP_func_button }, - { "func_door", SP_func_door }, - { "func_door_rotating", SP_func_door_rotating }, //TA - { "func_door_model", SP_func_door_model }, //TA - { "func_static", SP_func_static }, - { "func_rotating", SP_func_rotating }, - { "func_bobbing", SP_func_bobbing }, - { "func_pendulum", SP_func_pendulum }, - { "func_train", SP_func_train }, - { "func_group", SP_info_null }, - { "func_timer", SP_func_timer }, // rename trigger_timer? + // targets perform no action by themselves, but must be triggered + // by another entity + { "target_alien_win", SP_target_alien_win }, + { "target_delay", SP_target_delay }, + { "target_human_win", SP_target_human_win }, + { "target_hurt", SP_target_hurt }, + { "target_kill", SP_target_kill }, + { "target_location", SP_target_location }, + { "target_position", SP_target_position }, + { "target_print", SP_target_print }, + { "target_push", SP_target_push }, + { "target_relay", SP_target_relay }, + { "target_rumble", SP_target_rumble }, + { "target_score", SP_target_score }, + { "target_speaker", SP_target_speaker }, + { "target_teleporter", SP_target_teleporter }, // Triggers are brush objects that cause an effect when contacted // by a living player, usually involving firing targets. @@ -260,49 +264,18 @@ spawn_t spawns[ ] = // a single trigger class and different targets, triggered effects // could not be client side predicted (push and teleport). { "trigger_always", SP_trigger_always }, - { "trigger_multiple", SP_trigger_multiple }, - { "trigger_push", SP_trigger_push }, - { "trigger_teleport", SP_trigger_teleport }, - { "trigger_hurt", SP_trigger_hurt }, - { "trigger_stage", SP_trigger_stage }, - { "trigger_win", SP_trigger_win }, + { "trigger_ammo", SP_trigger_ammo }, { "trigger_buildable", SP_trigger_buildable }, { "trigger_class", SP_trigger_class }, { "trigger_equipment", SP_trigger_equipment }, { "trigger_gravity", SP_trigger_gravity }, { "trigger_heal", SP_trigger_heal }, - { "trigger_ammo", SP_trigger_ammo }, - - // targets perform no action by themselves, but must be triggered - // by another entity - { "target_delay", SP_target_delay }, - { "target_speaker", SP_target_speaker }, - { "target_print", SP_target_print }, - { "target_score", SP_target_score }, - { "target_teleporter", SP_target_teleporter }, - { "target_relay", SP_target_relay }, - { "target_kill", SP_target_kill }, - { "target_position", SP_target_position }, - { "target_location", SP_target_location }, - { "target_push", SP_target_push }, - { "target_rumble", SP_target_rumble }, - { "target_alien_win", SP_target_alien_win }, - { "target_human_win", SP_target_human_win }, - { "target_hurt", SP_target_hurt }, - - { "light", SP_light }, - { "path_corner", SP_path_corner }, - - { "misc_teleporter_dest", SP_misc_teleporter_dest }, - { "misc_model", SP_misc_model }, - { "misc_portal_surface", SP_misc_portal_surface }, - { "misc_portal_camera", SP_misc_portal_camera }, - - { "misc_particle_system", SP_misc_particle_system }, - { "misc_anim_model", SP_misc_anim_model }, - { "misc_light_flare", SP_misc_light_flare }, - - { NULL, 0 } + { "trigger_hurt", SP_trigger_hurt }, + { "trigger_multiple", SP_trigger_multiple }, + { "trigger_push", SP_trigger_push }, + { "trigger_stage", SP_trigger_stage }, + { "trigger_teleport", SP_trigger_teleport }, + { "trigger_win", SP_trigger_win } }; /* @@ -325,16 +298,18 @@ qboolean G_CallSpawn( gentity_t *ent ) } //check buildable spawn functions - if( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE ) + buildable = BG_BuildableByEntityName( ent->classname )->number; + if( buildable != BA_NONE ) { // don't spawn built-in buildings if we are using a custom layout if( level.layout[ 0 ] && Q_stricmp( level.layout, "*BUILTIN*" ) ) - return qtrue; + return qfalse; if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN ) { - ent->s.angles[ YAW ] += 180.0f; - AngleNormalize360( ent->s.angles[ YAW ] ); + ent->r.currentAngles[ YAW ] += 180.0f; + AngleNormalize360( ent->r.currentAngles[ YAW ] ); + ent->s.apos.trBase[ YAW ] = ent->r.currentAngles[ YAW ]; } G_SpawnBuildable( ent, buildable ); @@ -342,14 +317,13 @@ qboolean G_CallSpawn( gentity_t *ent ) } // check normal spawn functions - for( s = spawns; s->name; s++ ) + s = bsearch( ent->classname, spawns, ARRAY_LEN( spawns ), + sizeof( spawn_t ), cmdcmp ); + if( s ) { - if( !strcmp( s->name, ent->classname ) ) - { - // found it - s->spawn( ent ); - return qtrue; - } + // found it + s->spawn( ent ); + return qtrue; } G_Printf( "%s doesn't have a spawn function\n", ent->classname ); @@ -371,7 +345,7 @@ char *G_NewString( const char *string ) l = strlen( string ) + 1; - newb = G_Alloc( l ); + newb = BG_Alloc( l ); new_p = newb; @@ -412,58 +386,49 @@ void G_ParseField( const char *key, const char *value, gentity_t *ent ) vec3_t vec; vec4_t vec4; - for( f = fields; f->name; f++ ) + f = bsearch( key, fields, ARRAY_LEN( fields ), + sizeof( field_t ), cmdcmp ); + if( !f ) + return; + b = (byte *)ent; + + switch( f->type ) { - if( !Q_stricmp( f->name, key ) ) - { - // found it - b = (byte *)ent; - - switch( f->type ) - { - case F_LSTRING: - *(char **)( b + f->ofs ) = G_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_VECTOR4: - sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] ); - - ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ]; - ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ]; - ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ]; - ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ]; - 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; - - default: - case F_IGNORE: - break; - } - - return; - } + case F_STRING: + *(char **)( b + f->ofs ) = G_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_VECTOR4: + sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] ); + + ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ]; + ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ]; + ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ]; + ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ]; + 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; } } @@ -497,9 +462,8 @@ void G_SpawnGEntityFromSpawnVars( void ) return; } - // move editor origin to pos - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - VectorCopy( ent->s.origin, ent->r.currentOrigin ); + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); // if we didn't get a classname, don't bother spawning anything if( !G_CallSpawn( ent ) ) @@ -617,38 +581,23 @@ void SP_worldspawn( void ) trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day - G_SpawnString( "gravity", "800", &s ); - trap_Cvar_Set( "g_gravity", s ); - - G_SpawnString( "humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, &s ); - trap_Cvar_Set( "g_humanBuildPoints", s ); - - G_SpawnString( "humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, &s ); - trap_Cvar_Set( "g_humanMaxStage", s ); + if( G_SpawnString( "gravity", "", &s ) ) + trap_Cvar_Set( "g_gravity", s ); - G_SpawnString( "humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, &s ); - trap_Cvar_Set( "g_humanStage2Threshold", s ); + if( G_SpawnString( "humanMaxStage", "", &s ) ) + trap_Cvar_Set( "g_humanMaxStage", s ); - G_SpawnString( "humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, &s ); - trap_Cvar_Set( "g_humanStage3Threshold", s ); + if( G_SpawnString( "alienMaxStage", "", &s ) ) + trap_Cvar_Set( "g_alienMaxStage", s ); - G_SpawnString( "alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, &s ); - trap_Cvar_Set( "g_alienBuildPoints", s ); + if( G_SpawnString( "humanRepeaterBuildPoints", "", &s ) ) + trap_Cvar_Set( "g_humanRepeaterBuildPoints", s ); - G_SpawnString( "alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, &s ); - trap_Cvar_Set( "g_alienMaxStage", s ); + if( G_SpawnString( "humanBuildPoints", "", &s ) ) + trap_Cvar_Set( "g_humanBuildPoints", s ); - G_SpawnString( "alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, &s ); - trap_Cvar_Set( "g_alienStage2Threshold", s ); - - G_SpawnString( "alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, &s ); - trap_Cvar_Set( "g_alienStage3Threshold", s ); - - G_SpawnString( "enableDust", "0", &s ); - trap_Cvar_Set( "g_enableDust", s ); - - G_SpawnString( "enableBreath", "0", &s ); - trap_Cvar_Set( "g_enableBreath", s ); + if( G_SpawnString( "alienBuildPoints", "", &s ) ) + trap_Cvar_Set( "g_alienBuildPoints", s ); G_SpawnString( "disabledEquipment", "", &s ); trap_Cvar_Set( "g_disabledEquipment", s ); @@ -660,11 +609,25 @@ void SP_worldspawn( void ) trap_Cvar_Set( "g_disabledBuildables", s ); g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD; + g_entities[ ENTITYNUM_WORLD ].r.ownerNum = ENTITYNUM_NONE; g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn"; + g_entities[ ENTITYNUM_NONE ].s.number = ENTITYNUM_NONE; + g_entities[ ENTITYNUM_NONE ].r.ownerNum = ENTITYNUM_NONE; + g_entities[ ENTITYNUM_NONE ].classname = "nothing"; + if( g_restarted.integer ) trap_Cvar_Set( "g_restarted", "0" ); + // see if we want a warmup time + trap_SetConfigstring( CS_WARMUP, "-1" ); + if( g_doWarmup.integer ) + { + level.warmupTime = level.time - level.startTime + ( g_warmup.integer * 1000 ); + trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); + G_LogPrintf( "Warmup: %i\n", g_warmup.integer ); + } + } @@ -677,8 +640,6 @@ Parses textual entity definitions out of an entstring and spawns gentities. */ void G_SpawnEntitiesFromString( void ) { - // allow calls to G_Spawn*() - level.spawning = qtrue; level.numSpawnVars = 0; // the worldspawn is not an actual entity, but it still @@ -692,7 +653,4 @@ void G_SpawnEntitiesFromString( void ) // parse ents while( G_ParseSpawnVars( ) ) G_SpawnGEntityFromSpawnVars( ); - - level.spawning = qfalse; // any future calls to G_Spawn*() will be errors } - diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c index f0d45b1..3760fdd 100644 --- a/src/game/g_svcmds.c +++ b/src/game/g_svcmds.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -25,315 +26,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "g_local.h" - -/* -============================================================================== - -PACKET FILTERING - - -You can add or remove addresses from the filter list with: - -addip -removeip - -The ip address is specified in dot format, and you can use '*' to match any value -so you can specify an entire class C network with "addip 192.246.40.*" - -Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. - -listip -Prints the current list of filters. - -g_filterban <0 or 1> - -If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. - -If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. - -TTimo NOTE: for persistence, bans are stored in g_banIPs cvar MAX_CVAR_VALUE_STRING -The size of the cvar string buffer is limiting the banning to around 20 masks -this could be improved by putting some g_banIPs2 g_banIps3 etc. maybe -still, you should rely on PB for banning instead - -============================================================================== -*/ - -// extern vmCvar_t g_banIPs; -// extern vmCvar_t g_filterBan; - - -typedef struct ipFilter_s -{ - unsigned mask; - unsigned compare; -} ipFilter_t; - -#define MAX_IPFILTERS 1024 - -static ipFilter_t ipFilters[ MAX_IPFILTERS ]; -static int numIPFilters; - -/* -================= -StringToFilter -================= -*/ -static qboolean StringToFilter( char *s, ipFilter_t *f ) -{ - char num[ 128 ]; - int i, j; - byte b[ 4 ]; - byte m[ 4 ]; - - for( i = 0; i < 4; i++ ) - { - b[ i ] = 0; - m[ i ] = 0; - } - - for( i = 0; i < 4; i++ ) - { - if( *s < '0' || *s > '9' ) - { - if( *s == '*' ) // 'match any' - { - //b[ i ] and m[ i ] to 0 - s++; - if ( !*s ) - break; - - s++; - continue; - } - - G_Printf( "Bad filter address: %s\n", s ); - return qfalse; - } - - j = 0; - while( *s >= '0' && *s <= '9' ) - num[ j++ ] = *s++; - - num[ j ] = 0; - b[ i ] = atoi( num ); - - m[ i ] = 255; - - if( !*s ) - break; - - s++; - } - - f->mask = *(unsigned *)m; - f->compare = *(unsigned *)b; - - return qtrue; -} - -/* -================= -UpdateIPBans -================= -*/ -static void UpdateIPBans( void ) -{ - byte b[ 4 ]; - byte m[ 4 ]; - int i, j; - char iplist_final[ MAX_CVAR_VALUE_STRING ]; - char ip[ 64 ]; - - *iplist_final = 0; - - for( i = 0 ; i < numIPFilters ; i++ ) - { - if( ipFilters[ i ].compare == 0xffffffff ) - continue; - - *(unsigned *)b = ipFilters[ i ].compare; - *(unsigned *)m = ipFilters[ i ].mask; - *ip = 0; - - for( j = 0 ; j < 4 ; j++ ) - { - if( m[ j ] != 255 ) - Q_strcat( ip, sizeof( ip ), "*" ); - else - Q_strcat( ip, sizeof( ip ), va( "%i", b[ j ] ) ); - - Q_strcat( ip, sizeof( ip ), ( j < 3 ) ? "." : " " ); - } - - if( strlen( iplist_final ) + strlen( ip ) < MAX_CVAR_VALUE_STRING ) - Q_strcat( iplist_final, sizeof( iplist_final ), ip ); - else - { - Com_Printf( "g_banIPs overflowed at MAX_CVAR_VALUE_STRING\n" ); - break; - } - } - - trap_Cvar_Set( "g_banIPs", iplist_final ); -} - -/* -================= -G_FilterPacket -================= -*/ -qboolean G_FilterPacket( char *from ) -{ - int i; - unsigned in; - byte m[ 4 ]; - char *p; - - i = 0; - p = from; - while( *p && i < 4 ) - { - m[ i ] = 0; - while( *p >= '0' && *p <= '9' ) - { - m[ i ] = m[ i ] * 10 + ( *p - '0' ); - p++; - } - - if( !*p || *p == ':' ) - break; - - i++, p++; - } - - in = *(unsigned *)m; - - for( i = 0; i < numIPFilters; i++ ) - if( ( in & ipFilters[ i ].mask ) == ipFilters[ i ].compare ) - return g_filterBan.integer != 0; - - return g_filterBan.integer == 0; -} - -/* -================= -AddIP -================= -*/ -static void AddIP( char *str ) -{ - int i; - - for( i = 0 ; i < numIPFilters ; i++ ) - if( ipFilters[ i ].compare == 0xffffffff ) - break; // free spot - - if( i == numIPFilters ) - { - if( numIPFilters == MAX_IPFILTERS ) - { - G_Printf( "IP filter list is full\n" ); - return; - } - - numIPFilters++; - } - - if( !StringToFilter( str, &ipFilters[ i ] ) ) - ipFilters[ i ].compare = 0xffffffffu; - - UpdateIPBans( ); -} - -/* -================= -G_ProcessIPBans -================= -*/ -void G_ProcessIPBans( void ) -{ - char *s, *t; - char str[ MAX_CVAR_VALUE_STRING ]; - - Q_strncpyz( str, g_banIPs.string, sizeof( str ) ); - - for( t = s = g_banIPs.string; *t; /* */ ) - { - s = strchr( s, ' ' ); - - if( !s ) - break; - - while( *s == ' ' ) - *s++ = 0; - - if( *t ) - AddIP( t ); - - t = s; - } -} - - -/* -================= -Svcmd_AddIP_f -================= -*/ -void Svcmd_AddIP_f( void ) -{ - char str[ MAX_TOKEN_CHARS ]; - - if( trap_Argc( ) < 2 ) - { - G_Printf( "Usage: addip \n" ); - return; - } - - trap_Argv( 1, str, sizeof( str ) ); - - AddIP( str ); -} - -/* -================= -Svcmd_RemoveIP_f -================= -*/ -void Svcmd_RemoveIP_f( void ) -{ - ipFilter_t f; - int i; - char str[ MAX_TOKEN_CHARS ]; - - if( trap_Argc( ) < 2 ) - { - G_Printf( "Usage: sv removeip \n" ); - return; - } - - trap_Argv( 1, str, sizeof( str ) ); - - if( !StringToFilter( str, &f ) ) - return; - - for( i = 0; i < numIPFilters; i++ ) - { - if( ipFilters[ i ].mask == f.mask && - ipFilters[ i ].compare == f.compare) - { - ipFilters[ i ].compare = 0xffffffffu; - G_Printf ( "Removed.\n" ); - - UpdateIPBans( ); - return; - } - } - - G_Printf ( "Didn't find %s.\n", str ); -} - /* =================== Svcmd_EntityList_f @@ -367,6 +59,12 @@ void Svcmd_EntityList_f( void ) case ET_BUILDABLE: G_Printf( "ET_BUILDABLE " ); break; + case ET_RANGE_MARKER: + G_Printf( "ET_RANGE_MARKER " ); + break; + case ET_LOCATION: + G_Printf( "ET_LOCATION " ); + break; case ET_MISSILE: G_Printf( "ET_MISSILE " ); break; @@ -394,8 +92,26 @@ void Svcmd_EntityList_f( void ) case ET_GRAPPLE: G_Printf( "ET_GRAPPLE " ); break; + case ET_CORPSE: + G_Printf( "ET_CORPSE " ); + break; + case ET_PARTICLE_SYSTEM: + G_Printf( "ET_PARTICLE_SYSTEM " ); + break; + case ET_ANIMMAPOBJ: + G_Printf( "ET_ANIMMAPOBJ " ); + break; + case ET_MODELDOOR: + G_Printf( "ET_MODELDOOR " ); + break; + case ET_LIGHTFLARE: + G_Printf( "ET_LIGHTFLARE " ); + break; + case ET_LEV2_ZAP_CHAIN: + G_Printf( "ET_LEV2_ZAP_CHAIN " ); + break; default: - G_Printf( "%3i ", check->s.eType ); + G_Printf( "%-3i ", check->s.eType ); break; } @@ -406,48 +122,52 @@ void Svcmd_EntityList_f( void ) } } -gclient_t *ClientForString( const char *s ) +static gclient_t *ClientForString( char *s ) { - gclient_t *cl; - int i; - int idnum; + int idnum; + char err[ MAX_STRING_CHARS ]; - // numeric values are just slot numbers - if( s[ 0 ] >= '0' && s[ 0 ] <= '9' ) + idnum = G_ClientNumberFromString( s, err, sizeof( err ) ); + if( idnum == -1 ) { - idnum = atoi( s ); - - if( idnum < 0 || idnum >= level.maxclients ) - { - Com_Printf( "Bad client slot: %i\n", idnum ); - return NULL; - } - - cl = &level.clients[ idnum ]; + G_Printf( "%s", err ); + return NULL; + } - if( cl->pers.connected == CON_DISCONNECTED ) - { - G_Printf( "Client %i is not connected\n", idnum ); - return NULL; - } + return &level.clients[ idnum ]; +} - return cl; - } +static void Svcmd_Status_f( void ) +{ + int i; + gclient_t *cl; + char userinfo[ MAX_INFO_STRING ]; - // check for a name match - for( i = 0; i < level.maxclients; i++ ) + G_Printf( "slot score ping address rate name\n" ); + G_Printf( "---- ----- ---- ------- ---- ----\n" ); + for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) { - cl = &level.clients[ i ]; if( cl->pers.connected == CON_DISCONNECTED ) continue; - if( !Q_stricmp( cl->pers.netname, s ) ) - return cl; - } + G_Printf( "%-4d ", i ); + G_Printf( "%-5d ", cl->ps.persistant[ PERS_SCORE ] ); - G_Printf( "User %s is not on the server\n", s ); + if( cl->pers.connected == CON_CONNECTING ) + G_Printf( "CNCT " ); + else + G_Printf( "%-4d ", cl->ps.ping ); - return NULL; + trap_GetUserinfo( i, userinfo, sizeof( userinfo ) ); + G_Printf( "%-21s ", Info_ValueForKey( userinfo, "ip" ) ); + G_Printf( "%-8d ", atoi( Info_ValueForKey( userinfo, "rate" ) ) ); + G_Printf( "%s\n", cl->pers.netname ); // Info_ValueForKey( userinfo, "name" ) + } +} + +static void Svcmd_SMR_f( void ) +{ + G_Printf( "unrecognized Schachtmeister response: %s\n", ConcatArgs( 1 ) ); } /* @@ -457,22 +177,32 @@ Svcmd_ForceTeam_f forceteam =================== */ -void Svcmd_ForceTeam_f( void ) +static void Svcmd_ForceTeam_f( void ) { gclient_t *cl; char str[ MAX_TOKEN_CHARS ]; + team_t team; + + if( trap_Argc( ) != 3 ) + { + G_Printf( "usage: forceteam \n" ); + return; + } - // find the player trap_Argv( 1, str, sizeof( str ) ); cl = ClientForString( str ); if( !cl ) return; - // set the team trap_Argv( 2, str, sizeof( str ) ); - /*SetTeam( &g_entities[cl - level.clients], str );*/ - //FIXME: tremulise this + team = G_TeamFromString( str ); + if( team == NUM_TEAMS ) + { + G_Printf( "forceteam: invalid team \"%s\"\n", str ); + return; + } + G_ChangeTeam( &g_entities[ cl - level.clients ], team ); } /* @@ -482,37 +212,40 @@ Svcmd_LayoutSave_f layoutsave =================== */ -void Svcmd_LayoutSave_f( void ) +static void Svcmd_LayoutSave_f( void ) { char str[ MAX_QPATH ]; char str2[ MAX_QPATH - 4 ]; char *s; int i = 0; + qboolean pipeEncountered = qfalse; if( trap_Argc( ) != 2 ) { - G_Printf( "usage: layoutsave LAYOUTNAME\n" ); + G_Printf( "usage: layoutsave \n" ); return; } trap_Argv( 1, str, sizeof( str ) ); // sanitize name + str2[ 0 ] = '\0'; s = &str[ 0 ]; while( *s && i < sizeof( str2 ) - 1 ) { - if( ( *s >= '0' && *s <= '9' ) || - ( *s >= 'a' && *s <= 'z' ) || - ( *s >= 'A' && *s <= 'Z' ) || *s == '-' || *s == '_' ) + if( isalnum( *s ) || *s == '-' || *s == '_' || + ( ( *s == '|' || *s == ',' ) && !pipeEncountered ) ) { str2[ i++ ] = *s; str2[ i ] = '\0'; + if( *s == '|' ) + pipeEncountered = qtrue; } s++; } if( !str2[ 0 ] ) { - G_Printf("layoutsave: invalid name \"%s\"\n", str ); + G_Printf( "layoutsave: invalid name \"%s\"\n", str ); return; } @@ -528,18 +261,27 @@ Svcmd_LayoutLoad_f layoutload [ [ [ ...\n" ); + return; + } + s = ConcatArgs( 1 ); Q_strncpyz( layouts, s, sizeof( layouts ) ); - trap_Cvar_Set( "g_layouts", layouts ); + trap_Cvar_Set( "g_nextLayout", layouts ); + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + G_MapConfigs( map ); trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); level.restarted = qtrue; } @@ -555,225 +297,363 @@ static void Svcmd_AdmitDefeat_f( void ) return; } trap_Argv( 1, teamNum, sizeof( teamNum ) ); - team = atoi( teamNum ); - if( team == PTE_ALIENS || teamNum[ 0 ] == 'a' ) + team = G_TeamFromString( teamNum ); + if( team == TEAM_ALIENS ) { - level.surrenderTeam = PTE_ALIENS; - G_BaseSelfDestruct( PTE_ALIENS ); - G_TeamCommand( PTE_ALIENS, "cp \"Hivemind Link Broken\" 1"); + G_TeamCommand( TEAM_ALIENS, "cp \"Hivemind Link Broken\" 1"); trap_SendServerCommand( -1, "print \"Alien team has admitted defeat\n\"" ); } - else if( team == PTE_HUMANS || teamNum[ 0 ] == 'h' ) + else if( team == TEAM_HUMANS ) { - level.surrenderTeam = PTE_HUMANS; - G_BaseSelfDestruct( PTE_HUMANS ); - G_TeamCommand( PTE_HUMANS, "cp \"Life Support Terminated\" 1"); + G_TeamCommand( TEAM_HUMANS, "cp \"Life Support Terminated\" 1"); trap_SendServerCommand( -1, "print \"Human team has admitted defeat\n\"" ); } else { G_Printf("admitdefeat: invalid team\n"); - } + return; + } + level.surrenderTeam = team; + G_BaseSelfDestruct( team ); } -/* -================= -ConsoleCommand - -================= -*/ -qboolean ConsoleCommand( void ) +static void Svcmd_TeamWin_f( void ) { - char cmd[ MAX_TOKEN_CHARS ]; - + // this is largely made redundant by admitdefeat + char cmd[ 6 ]; trap_Argv( 0, cmd, sizeof( cmd ) ); - if( Q_stricmp( cmd, "entitylist" ) == 0 ) + switch( G_TeamFromString( cmd ) ) { - Svcmd_EntityList_f( ); - return qtrue; - } + case TEAM_ALIENS: + G_BaseSelfDestruct( TEAM_HUMANS ); + break; - if( Q_stricmp( cmd, "forceteam" ) == 0 ) - { - Svcmd_ForceTeam_f( ); - return qtrue; - } + case TEAM_HUMANS: + G_BaseSelfDestruct( TEAM_ALIENS ); + break; - if( Q_stricmp( cmd, "game_memory" ) == 0 ) - { - Svcmd_GameMem_f( ); - return qtrue; + default: + return; } +} - if( Q_stricmp( cmd, "addip" ) == 0 ) - { - Svcmd_AddIP_f( ); - return qtrue; - } +static void Svcmd_Evacuation_f( void ) +{ + if( level.exited ) + return; + trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" ); + level.lastWin = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "Evacuation" ); + LogExit( "Evacuation." ); +} + +static void Svcmd_MapRotation_f( void ) +{ + char rotationName[ MAX_QPATH ]; - if( Q_stricmp( cmd, "removeip" ) == 0 ) + if( trap_Argc( ) != 2 ) { - Svcmd_RemoveIP_f( ); - return qtrue; + G_Printf( "usage: maprotation \n" ); + return; } - if( Q_stricmp( cmd, "listip" ) == 0 ) + G_ClearRotationStack( ); + + trap_Argv( 1, rotationName, sizeof( rotationName ) ); + if( !G_StartMapRotation( rotationName, qfalse, qtrue, qfalse, 0 ) ) + G_Printf( "maprotation: invalid map rotation \"%s\"\n", rotationName ); +} + +static void Svcmd_TeamMessage_f( void ) +{ + char teamNum[ 2 ]; + team_t team; + + if( trap_Argc( ) < 3 ) { - trap_SendConsoleCommand( EXEC_NOW, "g_banIPs\n" ); - return qtrue; + G_Printf( "usage: say_team \n" ); + return; } - if( Q_stricmp( cmd, "mapRotation" ) == 0 ) + trap_Argv( 1, teamNum, sizeof( teamNum ) ); + team = G_TeamFromString( teamNum ); + + if( team == NUM_TEAMS ) { - char *rotationName = ConcatArgs( 1 ); + G_Printf( "say_team: invalid team \"%s\"\n", teamNum ); + return; + } - if( !G_StartMapRotation( rotationName, qfalse ) ) - G_Printf( "Can't find map rotation %s\n", rotationName ); + G_TeamCommand( team, va( "chat -1 %d \"%s\"", SAY_TEAM, ConcatArgs( 2 ) ) ); + G_LogPrintf( "SayTeam: -1 \"console\": %s\n", ConcatArgs( 2 ) ); +} - return qtrue; +static void Svcmd_CenterPrint_f( void ) +{ + if( trap_Argc( ) < 2 ) + { + G_Printf( "usage: cp \n" ); + return; } - if( Q_stricmp( cmd, "stopMapRotation" ) == 0 ) - { - G_StopMapRotation( ); + trap_SendServerCommand( -1, va( "cp \"%s\"", ConcatArgs( 1 ) ) ); +} - return qtrue; - } +static void Svcmd_EjectClient_f( void ) +{ + char *reason, name[ MAX_STRING_CHARS ]; - if( Q_stricmp( cmd, "advanceMapRotation" ) == 0 ) + if( trap_Argc( ) < 2 ) { - G_AdvanceMapRotation( ); - - return qtrue; + G_Printf( "usage: eject \n" ); + return; } - if( Q_stricmp( cmd, "alienWin" ) == 0 ) - { - int i; - gentity_t *e; + trap_Argv( 1, name, sizeof( name ) ); + reason = ConcatArgs( 2 ); - for( i = 1, e = g_entities + i; i < level.num_entities; i++, e++ ) + if( atoi( name ) == -1 ) + { + int i; + for( i = 0; i < level.maxclients; i++ ) { - if( e->s.modelindex == BA_H_SPAWN ) - G_Damage( e, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + if( level.clients[ i ].pers.localClient ) + continue; + trap_DropClient( i, reason ); } - - return qtrue; } - - if( Q_stricmp( cmd, "humanWin" ) == 0 ) + else { - int i; - gentity_t *e; - - for( i = 1, e = g_entities + i; i < level.num_entities; i++, e++ ) + gclient_t *cl = ClientForString( name ); + if( !cl ) + return; + if( cl->pers.localClient ) { - if( e->s.modelindex == BA_A_SPAWN ) - G_Damage( e, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + G_Printf( "eject: cannot eject local clients\n" ); + return; } + trap_DropClient( cl-level.clients, reason ); + } +} - return qtrue; +static void Svcmd_DumpUser_f( void ) +{ + char name[ MAX_STRING_CHARS ], userinfo[ MAX_INFO_STRING ]; + char key[ BIG_INFO_KEY ], value[ BIG_INFO_VALUE ]; + const char *info; + gclient_t *cl; + + if( trap_Argc( ) != 2 ) + { + G_Printf( "usage: dumpuser \n" ); + return; } - if( !Q_stricmp( cmd, "layoutsave" ) ) + trap_Argv( 1, name, sizeof( name ) ); + cl = ClientForString( name ); + if( !cl ) + return; + + trap_GetUserinfo( cl-level.clients, userinfo, sizeof( userinfo ) ); + info = &userinfo[ 0 ]; + G_Printf( "userinfo\n--------\n" ); + //Info_Print( userinfo ); + while( 1 ) { - Svcmd_LayoutSave_f( ); - return qtrue; + Info_NextPair( &info, key, value ); + if( !*info ) + return; + + G_Printf( "%-20s%s\n", key, value ); } - - if( !Q_stricmp( cmd, "layoutload" ) ) +} + +static void Svcmd_Pr_f( void ) +{ + char targ[ 4 ]; + int cl; + + if( trap_Argc( ) < 3 ) { - Svcmd_LayoutLoad_f( ); - return qtrue; + G_Printf( "usage: \n" ); + return; } - - if( !Q_stricmp( cmd, "admitdefeat" ) ) + + trap_Argv( 1, targ, sizeof( targ ) ); + cl = atoi( targ ); + + if( cl >= MAX_CLIENTS || cl < -1 ) { - Svcmd_AdmitDefeat_f( ); - return qtrue; + G_Printf( "invalid clientnum %d\n", cl ); + return; } - if( !Q_stricmp( cmd, "evacuation" ) ) + trap_SendServerCommand( cl, va( "print \"%s\n\"", ConcatArgs( 2 ) ) ); +} + +static void Svcmd_PrintQueue_f( void ) +{ + char team[ MAX_STRING_CHARS ]; + + if( trap_Argc() != 2 ) { - trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" ); - level.lastWin = PTE_NONE; - trap_SetConfigstring( CS_WINNER, "Evacuation" ); - LogExit( "Evacuation." ); - G_admin_maplog_result( "d" ); - return qtrue; + G_Printf( "usage: printqueue \n" ); + return; } - if( !Q_stricmp( cmd, "smr" ) ) + trap_Argv( 1, team, sizeof( team ) ); + + switch( G_TeamFromString( team ) ) { - if( trap_Argc() >= 2 ) - { - char arg[ 32 ]; - trap_Argv( 1, arg, sizeof( arg ) ); + case TEAM_ALIENS: + G_PrintSpawnQueue( &level.alienSpawnQueue ); + break; - if( !Q_stricmp( arg, "ipa" ) && trap_Argc() >= 4 ) - { - int rating; - const char *comment = NULL; + case TEAM_HUMANS: + G_PrintSpawnQueue( &level.humanSpawnQueue ); + break; - trap_Argv( 3, arg, sizeof( arg ) ); - rating = atoi( arg ); - if( trap_Argc() >= 5 ) - comment = ConcatArgs( 4 ); - trap_Argv( 2, arg, sizeof( arg ) ); + default: + G_Printf( "unknown team\n" ); + } +} - G_admin_IPA_judgement( arg, rating, comment ); +// dumb wrapper for "a", "m", "chat", and "say" +static void Svcmd_MessageWrapper( void ) +{ + char cmd[ 5 ]; + trap_Argv( 0, cmd, sizeof( cmd ) ); - return qtrue; - } - } + if( !Q_stricmp( cmd, "a" ) ) + Cmd_AdminMessage_f( NULL ); + else if( !Q_stricmp( cmd, "m" ) ) + Cmd_PrivateMessage_f( NULL ); + else if( !Q_stricmp( cmd, "say" ) ) + G_Say( NULL, SAY_ALL, ConcatArgs( 1 ) ); + else if( !Q_stricmp( cmd, "chat" ) ) + G_Say( NULL, SAY_RAW, ConcatArgs( 1 ) ); +} - G_Printf( "unrecognized Schachtmeister response: %s\n", ConcatArgs( 1 ) ); - return qtrue; - } +static void Svcmd_ListMapsWrapper( void ) +{ + Cmd_ListMaps_f( NULL ); +} + +static void Svcmd_SuddenDeath_f( void ) +{ + char secs[ 5 ]; + int offset; + trap_Argv( 1, secs, sizeof( secs ) ); + offset = atoi( secs ); + + level.suddenDeathBeginTime = level.time - level.startTime + offset * 1000; + trap_SendServerCommand( -1, + va( "cp \"Sudden Death will begin in %d second%s\"", + offset, offset == 1 ? "" : "s" ) ); +} - // see if this is a a admin command - if( G_admin_cmd_check( NULL, qfalse ) ) - return qtrue; +static void Svcmd_G_AdvanceMapRotation_f( void ) +{ + G_AdvanceMapRotation( 0 ); +} - if( g_dedicated.integer ) +struct svcmd +{ + char *cmd; + qboolean dedicated; + void ( *function )( void ); +} svcmds[ ] = { + { "a", qtrue, Svcmd_MessageWrapper }, + { "admitDefeat", qfalse, Svcmd_AdmitDefeat_f }, + { "advanceMapRotation", qfalse, Svcmd_G_AdvanceMapRotation_f }, + { "alienWin", qfalse, Svcmd_TeamWin_f }, + { "chat", qtrue, Svcmd_MessageWrapper }, + { "cp", qtrue, Svcmd_CenterPrint_f }, + { "dumpuser", qfalse, Svcmd_DumpUser_f }, + { "eject", qfalse, Svcmd_EjectClient_f }, + { "entityList", qfalse, Svcmd_EntityList_f }, + { "evacuation", qfalse, Svcmd_Evacuation_f }, + { "forceTeam", qfalse, Svcmd_ForceTeam_f }, + { "game_memory", qfalse, BG_MemoryInfo }, + { "humanWin", qfalse, Svcmd_TeamWin_f }, + { "layoutLoad", qfalse, Svcmd_LayoutLoad_f }, + { "layoutSave", qfalse, Svcmd_LayoutSave_f }, + { "listmaps", qtrue, Svcmd_ListMapsWrapper }, + { "loadcensors", qfalse, G_LoadCensors }, + { "m", qtrue, Svcmd_MessageWrapper }, + { "mapRotation", qfalse, Svcmd_MapRotation_f }, + { "pr", qfalse, Svcmd_Pr_f }, + { "printqueue", qfalse, Svcmd_PrintQueue_f }, + { "say", qtrue, Svcmd_MessageWrapper }, + { "say_team", qtrue, Svcmd_TeamMessage_f }, + { "smr", qfalse, Svcmd_SMR_f }, + { "status", qfalse, Svcmd_Status_f }, + { "stopMapRotation", qfalse, G_StopMapRotation }, + { "suddendeath", qfalse, Svcmd_SuddenDeath_f } +}; + +/* +================= +ConsoleCommand + +================= +*/ +qboolean ConsoleCommand( void ) +{ + char cmd[ MAX_TOKEN_CHARS ]; + struct svcmd *command; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + command = bsearch( cmd, svcmds, ARRAY_LEN( svcmds ), + sizeof( struct svcmd ), cmdcmp ); + + if( !command ) { - if( Q_stricmp( cmd, "say" ) == 0 ) - { - trap_SendServerCommand( -1, va( "print \"server: %s\n\"", ConcatArgs( 1 ) ) ); - return qtrue; - } - else if( !Q_stricmp( cmd, "chat" ) ) - { - trap_SendServerCommand( -1, va( "chat \"%s\" -1 0", ConcatArgs( 1 ) ) ); - G_Printf( "chat: %s\n", ConcatArgs( 1 ) ); - return qtrue; - } - else if( !Q_stricmp( cmd, "cp" ) ) - { - G_CP( NULL ); - return qtrue; - } - else if( !Q_stricmp( cmd, "m" ) ) - { - G_PrivateMessage( NULL ); - return qtrue; - } - else if( !Q_stricmp( cmd, "a" ) || !Q_stricmp( cmd, "say_admins" )) - { - G_Say( NULL, NULL, SAY_ADMINS, ConcatArgs( 1 ) ); + // see if this is an admin command + if( G_admin_cmd_check( NULL ) ) return qtrue; - } - else if( !Q_stricmp( cmd, "ha" ) || !Q_stricmp( cmd, "say_hadmins" )) - { - G_Say( NULL, NULL, SAY_HADMINS, ConcatArgs( 1 ) ); - return qtrue; - } - G_Printf( "unknown command: %s\n", cmd ); - return qtrue; + if( g_dedicated.integer ) + G_Printf( "unknown command: %s\n", cmd ); + + return qfalse; + } + + if( command->dedicated && !g_dedicated.integer ) + return qfalse; + + command->function( ); + return qtrue; +} + +void G_RegisterCommands( void ) +{ + int i; + + for( i = 0; i < ARRAY_LEN( svcmds ); i++ ) + { + if( svcmds[ i ].dedicated && !g_dedicated.integer ) + continue; + trap_AddCommand( svcmds[ i ].cmd ); } - return qfalse; + G_admin_register_cmds( ); } +void G_UnregisterCommands( void ) +{ + int i; + + for( i = 0; i < ARRAY_LEN( svcmds ); i++ ) + { + if( svcmds[ i ].dedicated && !g_dedicated.integer ) + continue; + trap_RemoveCommand( svcmds[ i ].cmd ); + } + + G_admin_unregister_cmds( ); +} diff --git a/src/game/g_syscalls.asm b/src/game/g_syscalls.asm index 242c2ad..4057923 100644 --- a/src/game/g_syscalls.asm +++ b/src/game/g_syscalls.asm @@ -1,6 +1,6 @@ code -equ trap_Printf -1 +equ trap_Print -1 equ trap_Error -2 equ trap_Milliseconds -3 equ trap_Cvar_Register -4 @@ -54,6 +54,7 @@ equ trap_SendGameStat -49 equ trap_AddCommand -50 equ trap_RemoveCommand -51 +equ trap_FS_GetFilteredFiles -52 equ memset -101 equ memcpy -102 diff --git a/src/game/g_syscalls.c b/src/game/g_syscalls.c index 6e3dc26..7c658be 100644 --- a/src/game/g_syscalls.c +++ b/src/game/g_syscalls.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -36,12 +37,12 @@ Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) int PASSFLOAT( float x ) { - float floatTemp; - floatTemp = x; - return *(int *)&floatTemp; + floatint_t fi; + fi.f = x; + return fi.i; } -void trap_Printf( const char *fmt ) +void trap_Print( const char *fmt ) { syscall( G_PRINT, fmt ); } @@ -49,6 +50,8 @@ void trap_Printf( const char *fmt ) void trap_Error( const char *fmt ) { syscall( G_ERROR, fmt ); + // shut up GCC warning about returning functions, because we know better + exit(1); } int trap_Milliseconds( void ) @@ -65,7 +68,7 @@ void trap_Argv( int n, char *buffer, int bufferLength ) syscall( G_ARGV, n, buffer, bufferLength ); } -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode ) { return syscall( G_FS_FOPEN_FILE, qpath, f, mode ); } @@ -147,6 +150,11 @@ void trap_GetConfigstring( int num, char *buffer, int bufferSize ) syscall( G_GET_CONFIGSTRING, num, buffer, bufferSize ); } +void trap_SetConfigstringRestrictions( int num, const clientList_t *clientList ) +{ + syscall( G_SET_CONFIGSTRING_RESTRICTIONS, num, clientList ); +} + void trap_GetUserinfo( int num, char *buffer, int bufferSize ) { syscall( G_GET_USERINFO, num, buffer, bufferSize ); @@ -248,13 +256,6 @@ int trap_RealTime( qtime_t *qtime ) void trap_SnapVector( float *v ) { syscall( G_SNAPVECTOR, v ); - return; -} - -void trap_SendGameStat( const char *data ) -{ - syscall( G_SEND_GAMESTAT, data ); - return; } int trap_Parse_AddGlobalDefine( char *define ) @@ -282,3 +283,17 @@ int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) return syscall( G_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); } +void trap_AddCommand( const char *cmdName ) +{ + syscall( G_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) +{ + syscall( G_REMOVECOMMAND, cmdName ); +} + +int trap_FS_GetFilteredFiles( const char *path, const char *extension, const char *filter, char *listbuf, int bufsize ) +{ + return syscall( G_FS_GETFILTEREDFILES, path, extension, filter, listbuf, bufsize ); +} diff --git a/src/game/g_target.c b/src/game/g_target.c index 467920a..f2a3f0f 100644 --- a/src/game/g_target.c +++ b/src/game/g_target.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -31,6 +32,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ void Think_Target_Delay( gentity_t *ent ) { + if( ent->activator && !ent->activator->inuse ) + ent->activator = NULL; G_UseTargets( ent, ent->activator ); } @@ -86,18 +89,19 @@ If "private", only the activator gets the message. If no checks, all clients ge */ void Use_Target_Print( gentity_t *ent, gentity_t *other, gentity_t *activator ) { - if( activator && activator->client && ( ent->spawnflags & 4 ) ) + if( ent->spawnflags & 4 ) { - trap_SendServerCommand( activator-g_entities, va( "cp \"%s\"", ent->message ) ); + if( activator && activator->client ) + trap_SendServerCommand( activator-g_entities, va( "cp \"%s\"", ent->message ) ); return; } if( ent->spawnflags & 3 ) { if( ent->spawnflags & 1 ) - G_TeamCommand( PTE_HUMANS, va( "cp \"%s\"", ent->message ) ); + G_TeamCommand( TEAM_HUMANS, va( "cp \"%s\"", ent->message ) ); if( ent->spawnflags & 2 ) - G_TeamCommand( PTE_ALIENS, va( "cp \"%s\"", ent->message ) ); + G_TeamCommand( TEAM_ALIENS, va( "cp \"%s\"", ent->message ) ); return; } @@ -156,9 +160,9 @@ void SP_target_speaker( gentity_t *ent ) G_SpawnFloat( "random", "0", &ent->random ); if( !G_SpawnString( "noise", "NOSOUND", &s ) ) - G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) ); + G_Error( "target_speaker without a noise key at %s", vtos( ent->r.currentOrigin ) ); - // force all client reletive sounds to be "activator" speakers that + // force all client relative sounds to be "activator" speakers that // play on the entity that activates it if( s[ 0 ] == '*' ) ent->spawnflags |= 8; @@ -186,8 +190,6 @@ void SP_target_speaker( gentity_t *ent ) if( ent->spawnflags & 4 ) ent->r.svFlags |= SVF_BROADCAST; - VectorCopy( ent->s.origin, ent->s.pos.trBase ); - // must link the entity so we get areas and clusters so // the server can determine who to send updates to trap_LinkEntity( ent ); @@ -210,7 +212,7 @@ void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activa return; } - TeleportPlayer( activator, dest->s.origin, dest->s.angles ); + TeleportPlayer( activator, dest->r.currentOrigin, dest->r.currentAngles, self->speed ); } /*QUAKED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8) @@ -219,7 +221,9 @@ The activator will be teleported away. void SP_target_teleporter( gentity_t *self ) { if( !self->targetname ) - G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->s.origin ) ); + G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->r.currentOrigin ) ); + + G_SpawnFloat( "speed", "400", &self->speed ); self->use = target_teleporter_use; } @@ -235,11 +239,11 @@ if RANDOM is checked, only one of the targets will be fired, not all of them void target_relay_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if( ( self->spawnflags & 1 ) && activator && activator->client && - activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) + activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; if( ( self->spawnflags & 2 ) && activator && activator->client && - activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) return; if( self->spawnflags & 4 ) @@ -285,36 +289,7 @@ Used as a positional target for in-game calculation, like jumppad targets. */ void SP_target_position( gentity_t *self ) { - G_SetOrigin( self, self->s.origin ); -} - -static void target_location_linkup( gentity_t *ent ) -{ - int i; - int n; - - if( level.locationLinked ) - return; - - level.locationLinked = qtrue; - - level.locationHead = NULL; - - trap_SetConfigstring( CS_LOCATIONS, "unknown" ); - - for( i = 0, ent = g_entities, n = 1; i < level.num_entities; i++, ent++) - { - if( ent->classname && !Q_stricmp( ent->classname, "target_location" ) ) - { - // lets overload some variables! - ent->health = n; // use for location marking - trap_SetConfigstring( CS_LOCATIONS + n, ent->message ); - n++; - ent->nextTrain = level.locationHead; - level.locationHead = ent; - } - } - // All linked together now + G_SetOrigin( self, self->r.currentOrigin ); } /*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) @@ -327,10 +302,36 @@ in site, closest in distance */ void SP_target_location( gentity_t *self ) { - self->think = target_location_linkup; - self->nextthink = level.time + 200; // Let them all spawn first + static int n = 0; + const char *message; + self->s.eType = ET_LOCATION; + self->r.svFlags = SVF_BROADCAST; + trap_LinkEntity( self ); // make the server send them to the clients + if( n == MAX_LOCATIONS ) + { + G_Printf( S_COLOR_YELLOW "too many target_locations\n" ); + return; + } + if( self->count ) + { + if( self->count < 0 ) + self->count = 0; + + if( self->count > 7 ) + self->count = 7; - G_SetOrigin( self, self->s.origin ); + message = va( "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, self->count + '0', + (const char*)self->message); + } + else + message = self->message; + trap_SetConfigstring( CS_LOCATIONS + n, message ); + self->nextTrain = level.locationHead; + self->s.generic1 = n; // use for location marking + level.locationHead = self; + n++; + + G_SetOrigin( self, self->r.currentOrigin ); } @@ -391,7 +392,7 @@ void SP_target_rumble( gentity_t *self ) if( !self->targetname ) { G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname, - vtos( self->s.origin ) ); + vtos( self->r.currentOrigin ) ); } if( !self->count ) @@ -411,7 +412,8 @@ target_alien_win_use */ void target_alien_win_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { - level.uncondAlienWin = qtrue; + if( !level.uncondHumanWin ) + level.uncondAlienWin = qtrue; } /* @@ -431,7 +433,8 @@ target_human_win_use */ void target_human_win_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { - level.uncondHumanWin = qtrue; + if( !level.uncondAlienWin ) + level.uncondHumanWin = qtrue; } /* @@ -468,7 +471,7 @@ void SP_target_hurt( gentity_t *self ) if( !self->targetname ) { G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname, - vtos( self->s.origin ) ); + vtos( self->r.currentOrigin ) ); } if( !self->damage ) diff --git a/src/game/g_team.c b/src/game/g_team.c index 1d8d102..4af6235 100644 --- a/src/game/g_team.c +++ b/src/game/g_team.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,35 +17,54 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "g_local.h" -// NULL for everyone -void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... ) -{ - char msg[ 1024 ]; - va_list argptr; - char *p; - - va_start( argptr,fmt ); +/* +================ +G_TeamFromString - if( vsprintf( msg, fmt, argptr ) > sizeof( msg ) ) - G_Error ( "PrintMsg overrun" ); +Return the team referenced by a string +================ +*/ +team_t G_TeamFromString( char *str ) +{ + switch( tolower( *str ) ) + { + case '0': case 's': return TEAM_NONE; + case '1': case 'a': return TEAM_ALIENS; + case '2': case 'h': return TEAM_HUMANS; + default: return NUM_TEAMS; + } +} - va_end( argptr ); +/* +================ +G_TeamCommand - // double quotes are bad - while( ( p = strchr( msg, '"' ) ) != NULL ) - *p = '\''; +Broadcasts a command to only a specific team +================ +*/ +void G_TeamCommand( team_t team, const char *cmd ) +{ + int i; - trap_SendServerCommand( ( ( ent == NULL ) ? -1 : ent-g_entities ), va( "print \"%s\"", msg ) ); + for( i = 0 ; i < level.maxclients ; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + { + if( level.clients[ i ].pers.teamSelection == team || + ( level.clients[ i ].pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) + trap_SendServerCommand( i, cmd ); + } + } } - /* ============== OnSameTeam @@ -62,156 +82,312 @@ qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ) } /* -=========== -Team_GetLocation +================== +G_ClientListForTeam +================== +*/ +static clientList_t G_ClientListForTeam( team_t team ) +{ + int i; + clientList_t clientList; -Report a location for the player. Uses placed nearby target_location entities -============ + Com_Memset( &clientList, 0, sizeof( clientList_t ) ); + + for( i = 0; i < g_maxclients.integer; i++ ) + { + gentity_t *ent = g_entities + i; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + + if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == team ) ) + Com_ClientListAdd( &clientList, ent->client->ps.clientNum ); + } + + return clientList; +} + +/* +================== +G_UpdateTeamConfigStrings +================== */ -gentity_t *Team_GetLocation( gentity_t *ent ) +void G_UpdateTeamConfigStrings( void ) { - gentity_t *eloc, *best; - float bestlen, len; - vec3_t origin; + clientList_t alienTeam = G_ClientListForTeam( TEAM_ALIENS ); + clientList_t humanTeam = G_ClientListForTeam( TEAM_HUMANS ); - best = NULL; - bestlen = 3.0f * 8192.0f * 8192.0f; + if( level.intermissiontime ) + { + // No restrictions once the game has ended + Com_Memset( &alienTeam, 0, sizeof( clientList_t ) ); + Com_Memset( &humanTeam, 0, sizeof( clientList_t ) ); + } - VectorCopy( ent->r.currentOrigin, origin ); + trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_ALIENS, &humanTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_ALIENS, &humanTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_ALIENS, &humanTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_ALIENS, &humanTeam ); - for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain ) + trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_HUMANS, &alienTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_HUMANS, &alienTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_HUMANS, &alienTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_HUMANS, &alienTeam ); + + trap_SetConfigstringRestrictions( CS_ALIEN_STAGES, &humanTeam ); + trap_SetConfigstringRestrictions( CS_HUMAN_STAGES, &alienTeam ); +} + +/* +================== +G_LeaveTeam +================== +*/ +void G_LeaveTeam( gentity_t *self ) +{ + team_t team = self->client->pers.teamSelection; + gentity_t *ent; + int i; + + if( team == TEAM_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum ); + else if( team == TEAM_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum ); + else { - len = ( origin[ 0 ] - eloc->r.currentOrigin[ 0 ] ) * ( origin[ 0 ] - eloc->r.currentOrigin[ 0 ] ) - + ( origin[ 1 ] - eloc->r.currentOrigin[ 1 ] ) * ( origin[ 1 ] - eloc->r.currentOrigin[ 1 ] ) - + ( origin[ 2 ] - eloc->r.currentOrigin[ 2 ] ) * ( origin[ 2 ] - eloc->r.currentOrigin[ 2 ] ); + if( self->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( self ); + return; + } - if( len > bestlen ) - continue; + // stop any following clients + G_StopFromFollowing( self ); - if( !trap_InPVS( origin, eloc->r.currentOrigin ) ) + G_Vote( self, team, qfalse ); + self->suicideTime = 0; + + for( i = 0; i < level.num_entities; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->inuse ) continue; - bestlen = len; - best = eloc; + if( ent->client && ent->client->pers.connected == CON_CONNECTED ) + { + // cure poison + if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED && + ent->client->lastPoisonClient == self ) + ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + } + else if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number ) + G_FreeEntity( ent ); } - return best; + // cut all relevant zap beams + G_ClearPlayerZapEffects( self ); + + G_namelog_update_score( self->client ); } +/* +================= +G_ChangeTeam +================= +*/ +void G_ChangeTeam( gentity_t *ent, team_t newTeam ) +{ + team_t oldTeam = ent->client->pers.teamSelection; + + if( oldTeam == newTeam ) + return; + + G_LeaveTeam( ent ); + ent->client->pers.teamChangeTime = level.time; + ent->client->pers.teamSelection = newTeam; + ent->client->pers.classSelection = PCL_NONE; + ClientSpawn( ent, NULL, NULL, NULL ); + + if( oldTeam == TEAM_HUMANS && newTeam == TEAM_ALIENS ) + { + // Convert from human to alien credits + ent->client->pers.credit = + (int)( ent->client->pers.credit * + ALIEN_MAX_CREDITS / HUMAN_MAX_CREDITS + 0.5f ); + } + else if( oldTeam == TEAM_ALIENS && newTeam == TEAM_HUMANS ) + { + // Convert from alien to human credits + ent->client->pers.credit = + (int)( ent->client->pers.credit * + HUMAN_MAX_CREDITS / ALIEN_MAX_CREDITS + 0.5f ); + } + + if( !g_cheats.integer ) + { + if( ent->client->noclip ) + { + ent->client->noclip = qfalse; + ent->r.contents = ent->client->cliprcontents; + } + ent->flags &= ~( FL_GODMODE | FL_NOTARGET ); + } + + // Copy credits to ps for the client + ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + + G_UpdateTeamConfigStrings( ); + + G_LogPrintf( "ChangeTeam: %d %s: %s" S_COLOR_WHITE " switched teams\n", + (int)( ent - g_entities ), BG_TeamName( newTeam ), ent->client->pers.netname ); + + G_namelog_update_score( ent->client ); + TeamplayInfoMessage( ent ); +} /* =========== -Team_GetLocationMsg +Team_GetLocation -Report a location message for the player. Uses placed nearby target_location entities +Report a location for the player. Uses placed nearby target_location entities ============ */ -qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen ) +gentity_t *Team_GetLocation( gentity_t *ent ) { - gentity_t *best; - - best = Team_GetLocation( ent ); + gentity_t *eloc, *best; + float bestlen, len; - if( !best ) - return qfalse; + best = NULL; + bestlen = 3.0f * 8192.0f * 8192.0f; - if( best->count ) + for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain ) { - if( best->count < 0 ) - best->count = 0; + len = DistanceSquared( ent->r.currentOrigin, eloc->r.currentOrigin ); + + if( len > bestlen ) + continue; - if( best->count > 7 ) - best->count = 7; + if( !trap_InPVS( ent->r.currentOrigin, eloc->r.currentOrigin ) ) + continue; - Com_sprintf( loc, loclen, "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message ); + bestlen = len; + best = eloc; } - else - Com_sprintf( loc, loclen, "%s", best->message ); - return qtrue; + return best; } /*---------------------------------------------------------------------------*/ -static int QDECL SortClients( const void *a, const void *b ) -{ - return *(int *)a - *(int *)b; -} - - /* ================== -TeamplayLocationsMessage +TeamplayInfoMessage Format: - clientNum location health armor weapon powerups + clientNum location health weapon upgrade ================== */ void TeamplayInfoMessage( gentity_t *ent ) { - char entry[ 1024 ]; - char string[ 8192 ]; - int stringlength; - int i, j; + char entry[ 17 ], + string[ ( MAX_CLIENTS - 1 ) * ( sizeof( entry ) - 1 ) + 1 ]; + int i, j; + int team, stringlength; gentity_t *player; - int cnt; - int h, a = 0; - int clients[ TEAM_MAXOVERLAY ]; + gclient_t *cl; + upgrade_t upgrade = UP_NONE; + int curWeaponClass = WP_NONE ; // sends weapon for humans, class for aliens + char *format; - if( ! ent->client->pers.teamInfo ) - return; + if( !g_allowTeamOverlay.integer ) + return; - // figure out what client should be on the display - // we are limited to 8, but we want to use the top eight players - // but in client order (so they don't keep changing position on the overlay) - for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++ ) - { - player = g_entities + level.sortedClients[ i ]; + if( !ent->client->pers.teamInfo ) + return; - if( player->inuse && player->client->sess.sessionTeam == - ent->client->sess.sessionTeam ) - clients[ cnt++ ] = level.sortedClients[ i ]; + if( ent->client->pers.teamSelection == TEAM_NONE ) + { + if( ent->client->sess.spectatorState == SPECTATOR_FREE || + ent->client->sess.spectatorClient < 0 ) + return; + team = g_entities[ ent->client->sess.spectatorClient ].client-> + pers.teamSelection; } + else + team = ent->client->pers.teamSelection; - // We have the top eight players, sort them by clientNum - qsort( clients, cnt, sizeof( clients[ 0 ] ), SortClients ); + if( team == TEAM_ALIENS ) + format = " %i %i %i %i"; // aliens don't have upgrades + else + format = " %i %i %i %i %i"; - // send the latest information on all clients - string[ 0 ] = 0; + string[ 0 ] = '\0'; stringlength = 0; - for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) + for( i = 0; i < level.maxclients; i++) { - player = g_entities + i; + player = g_entities + i ; + cl = player->client; - if( player->inuse && player->client->sess.sessionTeam == - ent->client->sess.sessionTeam ) - { - h = player->client->ps.stats[ STAT_HEALTH ]; + if( ent == player || !cl || team != cl->pers.teamSelection || + !player->inuse ) + continue; - if( h < 0 ) - h = 0; + // only update if changed since last time + if( cl->pers.infoChangeTime <= ent->client->pers.teamInfo ) + continue; - Com_sprintf( entry, sizeof( entry ), - " %i %i %i %i %i %i", -// level.sortedClients[i], player->client->pers.teamState.location, h, a, - i, player->client->pers.teamState.location, h, a, - player->client->ps.weapon, player->s.misc ); + if( cl->sess.spectatorState != SPECTATOR_NOT ) + { + curWeaponClass = WP_NONE; + upgrade = UP_NONE; + } + else if ( cl->pers.teamSelection == TEAM_HUMANS ) + { + curWeaponClass = cl->ps.weapon; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) + upgrade = UP_BATTLESUIT; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) + upgrade = UP_JETPACK; + else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) + upgrade = UP_BATTPACK; + else if( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) ) + upgrade = UP_HELMET; + else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) + upgrade = UP_LIGHTARMOUR; + else + upgrade = UP_NONE; + } + else if( cl->pers.teamSelection == TEAM_ALIENS ) + { + curWeaponClass = cl->ps.stats[ STAT_CLASS ]; + upgrade = UP_NONE; + } - j = strlen( entry ); + Com_sprintf( entry, sizeof( entry ), format, i, + cl->pers.location, + cl->ps.stats[ STAT_HEALTH ] < 1 ? 0 : cl->ps.stats[ STAT_HEALTH ], + curWeaponClass, + upgrade ); - if( stringlength + j > sizeof( string ) ) - break; + j = strlen( entry ); - strcpy( string + stringlength, entry ); - stringlength += j; - cnt++; - } + // this should not happen if entry and string sizes are correct + if( stringlength + j >= sizeof( string ) ) + break; + + strcpy( string + stringlength, entry ); + stringlength += j; } - trap_SendServerCommand( ent - g_entities, va( "tinfo %i %s", cnt, string ) ); + if( string[ 0 ] ) + { + trap_SendServerCommand( ent - g_entities, va( "tinfo%s", string ) ); + ent->client->pers.teamInfo = level.time; + } } void CheckTeamStatus( void ) @@ -229,16 +405,25 @@ void CheckTeamStatus( void ) if( ent->client->pers.connected != CON_CONNECTED ) continue; - if( ent->inuse && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS || - ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ) + if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS || + ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) ) { loc = Team_GetLocation( ent ); if( loc ) - ent->client->pers.teamState.location = loc->health; - else - ent->client->pers.teamState.location = 0; + { + if( ent->client->pers.location != loc->s.generic1 ) + { + ent->client->pers.infoChangeTime = level.time; + ent->client->pers.location = loc->s.generic1; + } + } + else if( ent->client->pers.location != 0 ) + { + ent->client->pers.infoChangeTime = level.time; + ent->client->pers.location = 0; + } } } @@ -248,29 +433,35 @@ void CheckTeamStatus( void ) if( ent->client->pers.connected != CON_CONNECTED ) continue; - if( ent->inuse && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS || - ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ) + if( ent->inuse ) TeamplayInfoMessage( ent ); } } - //Warn on unbalanced teams - if ( g_teamImbalanceWarnings.integer && !level.intermissiontime && level.time - level.lastTeamUnbalancedTime > ( g_teamImbalanceWarnings.integer * 1000 ) && level.numTeamWarnings<3 ) + // Warn on imbalanced teams + if( g_teamImbalanceWarnings.integer && !level.intermissiontime && + ( level.time - level.lastTeamImbalancedTime > + ( g_teamImbalanceWarnings.integer * 1000 ) ) && + level.numTeamImbalanceWarnings < 3 && !level.restarted ) { - level.lastTeamUnbalancedTime = level.time; - if (level.numAlienSpawns > 0 && level.numHumanClients - level.numAlienClients > 2) - { - trap_SendServerCommand (-1, "print \"Teams are unbalanced. Humans have more players.\n Humans will keep their points when switching teams.\n\""); - level.numTeamWarnings++; - } - else if (level.numHumanSpawns > 0 && level.numAlienClients - level.numHumanClients > 2) - { - trap_SendServerCommand (-1, "print \"Teams are unbalanced. Aliens have more players.\n Aliens will keep their points when switching teams.\n\""); - level.numTeamWarnings++; - } - else - { - level.numTeamWarnings = 0; - } + level.lastTeamImbalancedTime = level.time; + if( level.numAlienSpawns > 0 && + level.numHumanClients - level.numAlienClients > 2 ) + { + trap_SendServerCommand( -1, "print \"Teams are imbalanced. " + "Humans have more players.\n\""); + level.numTeamImbalanceWarnings++; + } + else if( level.numHumanSpawns > 0 && + level.numAlienClients - level.numHumanClients > 2 ) + { + trap_SendServerCommand ( -1, "print \"Teams are imbalanced. " + "Aliens have more players.\n\""); + level.numTeamImbalanceWarnings++; + } + else + { + level.numTeamImbalanceWarnings = 0; + } } } diff --git a/src/game/g_trigger.c b/src/game/g_trigger.c index 0ac34bb..8d7c518 100644 --- a/src/game/g_trigger.c +++ b/src/game/g_trigger.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -26,8 +27,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA void InitTrigger( gentity_t *self ) { - if( !VectorCompare( self->s.angles, vec3_origin ) ) - G_SetMovedir( self->s.angles, self->movedir ); + if( !VectorCompare( self->r.currentAngles, vec3_origin ) ) + G_SetMovedir( self->r.currentAngles, self->movedir ); trap_SetBrushModel( self, self->model ); self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap_SetBrushModel @@ -42,6 +43,24 @@ void multi_wait( gentity_t *ent ) } +void trigger_check_wait( gentity_t *self ) +{ + if( self->wait > 0 ) + { + self->think = multi_wait; + self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + self->touch = 0; + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + } +} + + // 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 @@ -51,32 +70,19 @@ void multi_trigger( gentity_t *ent, gentity_t *activator ) if( ent->nextthink ) return; // can't retrigger until the wait is over - if( activator->client ) + if( activator && activator->client ) { if( ( ent->spawnflags & 1 ) && - activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) + activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; if( ( ent->spawnflags & 2 ) && - activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) return; } G_UseTargets( ent, ent->activator ); - - if( ent->wait > 0 ) - { - ent->think = multi_wait; - ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000; - } - else - { - // we can't just remove (self) here, because this is a touch function - // called while looping through area links... - ent->touch = 0; - ent->nextthink = level.time + FRAMETIME; - ent->think = G_FreeEntity; - } + trigger_check_wait( ent ); } void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) @@ -184,7 +190,7 @@ void AimAtTarget( gentity_t *self ) return; } - height = ent->s.origin[ 2 ] - origin[ 2 ]; + height = ent->r.currentOrigin[ 2 ] - origin[ 2 ]; gravity = g_gravity.value; time = sqrt( height / ( 0.5 * gravity ) ); @@ -195,7 +201,7 @@ void AimAtTarget( gentity_t *self ) } // set s.origin2 to the push velocity - VectorSubtract( ent->s.origin, origin, self->s.origin2 ); + VectorSubtract( ent->r.currentOrigin, origin, self->s.origin2 ); self->s.origin2[ 2 ] = 0; dist = VectorNormalize( self->s.origin2 ); @@ -227,7 +233,7 @@ void SP_trigger_push( gentity_t *self ) void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) { - if( !activator->client ) + if( !activator || !activator->client ) return; if( activator->client->ps.pm_type != PM_NORMAL ) @@ -246,13 +252,13 @@ void SP_target_push( gentity_t *self ) if( !self->speed ) self->speed = 1000; - G_SetMovedir( self->s.angles, self->s.origin2 ); + G_SetMovedir( self->r.currentAngles, self->s.origin2 ); VectorScale( self->s.origin2, self->speed, self->s.origin2 ); if( self->target ) { - VectorCopy( self->s.origin, self->r.absmin ); - VectorCopy( self->s.origin, self->r.absmax ); + VectorCopy( self->r.currentOrigin, self->r.absmin ); + VectorCopy( self->r.currentOrigin, self->r.absmax ); self->think = AimAtTarget; self->nextthink = level.time + FRAMETIME; } @@ -283,7 +289,7 @@ void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace // Spectators only? if( ( self->spawnflags & 1 ) && - other->client->sess.sessionTeam != TEAM_SPECTATOR ) + other->client->sess.spectatorState == SPECTATOR_NOT ) return; @@ -295,7 +301,7 @@ void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace return; } - TeleportPlayer( other, dest->s.origin, dest->s.angles ); + TeleportPlayer( other, dest->r.currentOrigin, dest->r.currentAngles, self->speed ); } /* @@ -321,6 +327,8 @@ void SP_trigger_teleport( gentity_t *self ) { InitTrigger( self ); + G_SpawnFloat( "speed", "400", &self->speed ); + // unlike other triggers, we need to send this one to the client // unless is a spectator trigger if( self->spawnflags & 1 ) @@ -407,11 +415,12 @@ void SP_trigger_hurt( gentity_t *self ) self->r.contents = CONTENTS_TRIGGER; - if( self->spawnflags & 2 ) - self->use = hurt_use; + self->use = hurt_use; // link in to the world if starting active - if( !( self->spawnflags & 1 ) ) + if( self->spawnflags & 1 ) + trap_UnlinkEntity( self ); + else trap_LinkEntity( self ); } @@ -469,7 +478,7 @@ void SP_func_timer( gentity_t *self ) if( self->random >= self->wait ) { self->random = self->wait - FRAMETIME; - G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) ); + G_Printf( "func_timer at %s has random >= wait\n", vtos( self->r.currentOrigin ) ); } if( self->spawnflags & 1 ) @@ -489,7 +498,7 @@ G_Checktrigger_stages Called when stages change =============== */ -void G_Checktrigger_stages( pTeam_t team, stage_t stage ) +void G_Checktrigger_stages( team_t team, stage_t stage ) { int i; gentity_t *ent; @@ -558,6 +567,9 @@ qboolean trigger_buildable_match( gentity_t *self, gentity_t *activator ) { int i = 0; + if( !activator ) + return qfalse; + //if there is no buildable list every buildable triggers if( self->bTriggers[ i ] == BA_NONE ) return qtrue; @@ -592,26 +604,18 @@ void trigger_buildable_trigger( gentity_t *self, gentity_t *activator ) if( self->s.eFlags & EF_DEAD ) { if( !trigger_buildable_match( self, activator ) ) + { G_UseTargets( self, activator ); + trigger_check_wait( self ); + } } else { if( trigger_buildable_match( self, activator ) ) + { G_UseTargets( self, activator ); - } - - if( self->wait > 0 ) - { - self->think = multi_wait; - self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; - } - else - { - // we can't just remove (self) here, because this is a touch function - // called while looping through area links... - self->touch = 0; - self->nextthink = level.time + FRAMETIME; - self->think = G_FreeEntity; + trigger_check_wait( self ); + } } } @@ -686,6 +690,9 @@ qboolean trigger_class_match( gentity_t *self, gentity_t *activator ) { int i = 0; + if( !activator ) + return qfalse; + //if there is no class list every class triggers (stupid case) if( self->cTriggers[ i ] == PCL_NONE ) return qtrue; @@ -694,7 +701,7 @@ qboolean trigger_class_match( gentity_t *self, gentity_t *activator ) //otherwise check against the list for( i = 0; self->cTriggers[ i ] != PCL_NONE; i++ ) { - if( activator->client->ps.stats[ STAT_PCLASS ] == self->cTriggers[ i ] ) + if( activator->client->ps.stats[ STAT_CLASS ] == self->cTriggers[ i ] ) return qtrue; } } @@ -710,10 +717,10 @@ trigger_class_trigger void trigger_class_trigger( gentity_t *self, gentity_t *activator ) { //sanity check - if( !activator->client ) + if( !activator || !activator->client ) return; - if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) return; if( self->s.eFlags & EF_NODRAW ) @@ -726,27 +733,20 @@ void trigger_class_trigger( gentity_t *self, gentity_t *activator ) if( self->s.eFlags & EF_DEAD ) { if( !trigger_class_match( self, activator ) ) + { G_UseTargets( self, activator ); + trigger_check_wait( self ); + } } else { if( trigger_class_match( self, activator ) ) + { G_UseTargets( self, activator ); + trigger_check_wait( self ); + } } - if( self->wait > 0 ) - { - self->think = multi_wait; - self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; - } - else - { - // we can't just remove (self) here, because this is a touch function - // called while looping through area links... - self->touch = 0; - self->nextthink = level.time + FRAMETIME; - self->think = G_FreeEntity; - } } /* @@ -820,6 +820,9 @@ qboolean trigger_equipment_match( gentity_t *self, gentity_t *activator ) { int i = 0; + if( !activator ) + return qfalse; + //if there is no equipment list all equipment triggers (stupid case) if( self->wTriggers[ i ] == WP_NONE && self->uTriggers[ i ] == UP_NONE ) return qtrue; @@ -850,10 +853,10 @@ trigger_equipment_trigger void trigger_equipment_trigger( gentity_t *self, gentity_t *activator ) { //sanity check - if( !activator->client ) + if( !activator || !activator->client ) return; - if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) + if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; if( self->s.eFlags & EF_NODRAW ) @@ -866,26 +869,18 @@ void trigger_equipment_trigger( gentity_t *self, gentity_t *activator ) if( self->s.eFlags & EF_DEAD ) { if( !trigger_equipment_match( self, activator ) ) + { G_UseTargets( self, activator ); + trigger_check_wait( self ); + } } else { if( trigger_equipment_match( self, activator ) ) + { G_UseTargets( self, activator ); - } - - if( self->wait > 0 ) - { - self->think = multi_wait; - self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; - } - else - { - // we can't just remove (self) here, because this is a touch function - // called while looping through area links... - self->touch = 0; - self->nextthink = level.time + FRAMETIME; - self->think = G_FreeEntity; + trigger_check_wait( self ); + } } } @@ -1061,7 +1056,9 @@ void SP_trigger_heal( gentity_t *self ) InitTrigger( self ); // link in to the world if starting active - if( !( self->spawnflags & 1 ) ) + if( self->spawnflags & 1 ) + trap_UnlinkEntity( self ); + else trap_LinkEntity( self ); } @@ -1073,12 +1070,13 @@ trigger_ammo_touch */ void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { - int ammo, clips, maxClips, maxAmmo; + int maxClips, maxAmmo; + weapon_t weapon; if( !other->client ) return; - if( other->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) + if( other->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; if( self->timestamp > level.time ) @@ -1087,10 +1085,11 @@ void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace ) if( other->client->ps.weaponstate != WEAPON_READY ) return; - if( BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 2 ) + weapon = other->client->ps.stats[ STAT_WEAPON ]; + if( BG_Weapon( weapon )->usesEnergy && self->spawnflags & 2 ) return; - if( !BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 4 ) + if( !BG_Weapon( weapon )->usesEnergy && self->spawnflags & 4 ) return; if( self->spawnflags & 1 ) @@ -1098,25 +1097,21 @@ void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace ) else self->timestamp = level.time + FRAMETIME; - BG_FindAmmoForWeapon( other->client->ps.weapon, &maxAmmo, &maxClips ); - ammo = other->client->ps.ammo; - clips = other->client->ps.clips; + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; - if( ( ammo + self->damage ) > maxAmmo ) + if( ( other->client->ps.ammo + self->damage ) > maxAmmo ) { - if( clips < maxClips ) + if( other->client->ps.clips < maxClips ) { - clips++; - ammo = 1; + other->client->ps.clips++; + other->client->ps.ammo = 1; } else - ammo = maxAmmo; + other->client->ps.ammo = maxAmmo; } else - ammo += self->damage; - - other->client->ps.ammo = ammo; - other->client->ps.clips = clips; + other->client->ps.ammo += self->damage; } /* diff --git a/src/game/g_utils.c b/src/game/g_utils.c index a74df3f..5c74188 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -93,7 +94,7 @@ G_FindConfigstringIndex ================ */ -int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) { int i; char s[ MAX_STRING_CHARS ]; @@ -122,55 +123,28 @@ int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) return i; } -//TA: added ParticleSystemIndex -int G_ParticleSystemIndex( char *name ) +int G_ParticleSystemIndex( const char *name ) { return G_FindConfigstringIndex( name, CS_PARTICLE_SYSTEMS, MAX_GAME_PARTICLE_SYSTEMS, qtrue ); } -//TA: added ShaderIndex -int G_ShaderIndex( char *name ) +int G_ShaderIndex( const char *name ) { return G_FindConfigstringIndex( name, CS_SHADERS, MAX_GAME_SHADERS, qtrue ); } -int G_ModelIndex( char *name ) +int G_ModelIndex( const char *name ) { return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue ); } -int G_SoundIndex( char *name ) +int G_SoundIndex( const char *name ) { return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue ); } //===================================================================== - -/* -================ -G_TeamCommand - -Broadcasts a command to only a specific team -================ -*/ -void G_TeamCommand( pTeam_t team, char *cmd ) -{ - int i; - - for( i = 0 ; i < level.maxclients ; i++ ) - { - if( level.clients[ i ].pers.connected == CON_CONNECTED ) - { - if( level.clients[ i ].pers.teamSelection == team || - ( level.clients[ i ].pers.teamSelection == PTE_NONE && - G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) - trap_SendServerCommand( i, cmd ); - } - } -} - - /* ============= G_Find @@ -249,7 +223,7 @@ gentity_t *G_PickTarget( char *targetname ) return NULL; } - return choice[ rand( ) % num_choices ]; + return choice[ rand( ) / ( RAND_MAX / num_choices + 1 ) ]; } @@ -268,9 +242,6 @@ void G_UseTargets( gentity_t *ent, gentity_t *activator ) { gentity_t *t; - if( !ent ) - return; - if( ent->targetShaderName && ent->targetShaderNewName ) { float f = level.time * 0.001; @@ -505,7 +476,6 @@ qboolean G_EntitiesFree( void ) return qfalse; } - /* ================= G_FreeEntity @@ -526,6 +496,181 @@ void G_FreeEntity( gentity_t *ent ) ent->inuse = qfalse; } +/* +================= +G_RemoveEntity + +Safely remove an entity, perform reasonable cleanup logic +================= +*/ +void G_RemoveEntity( gentity_t *ent ) +{ + gentity_t *e; + + if( ent->client ) + { + // removing a player causes the player to "unspawn" + class_t class = ent->client->pers.classSelection; // back up the spawn queue choice + weapon_t weapon = ent->client->pers.humanItemSelection; // back up + ent->client->pers.classSelection = PCL_NONE; + ent->client->pers.humanItemSelection = WP_NONE; + ent->suicideTime = 0; // cancel any timed suicides + ClientSpawn( ent, NULL, NULL, NULL ); + ent->client->pers.classSelection = class; // restore the spawn queue choice + ent->client->pers.humanItemSelection = weapon; // restore + return; + } + else if( ent->s.eType == ET_RANGE_MARKER ) + { + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) + { + if( e->rangeMarker == ent ) + { + // clear the buildable's reference to this range marker + e->rangeMarker = NULL; + break; + } + } + } + else if( ent->s.eType == ET_BUILDABLE ) + { + // the range marker (if any) goes away with the buildable + G_RemoveRangeMarkerFrom( ent ); + } + else if( !strcmp( ent->classname, "lev2zapchain" ) ) + { + zap_t *z; + for( z = &zaps[ 0 ]; z < &zaps[ MAX_ZAPS ]; ++z ) + { + if( z->used && z->effectChannel == ent ) + { + // free the zap slot occupied by this zap effect + z->used = qfalse; + break; + } + } + } + else if( ent->s.eType == ET_MOVER ) + { + if( !strcmp( ent->classname, "func_door" ) || + !strcmp( ent->classname, "func_door_rotating" ) || + !strcmp( ent->classname, "func_door_model" ) || + !strcmp( ent->classname, "func_door_model_clip_brush" ) || + !strcmp( ent->classname, "func_plat" ) ) + { + // each func_door_model entity is paired with a clip brush, remove the other + if( ent->clipBrush != NULL ) + G_FreeEntity( ent->clipBrush ); + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) + { + if( e->parent == ent ) + { + // this mover has a trigger area brush + if( ent->teammaster != NULL && ent->teammaster->teamchain != NULL ) + { + // the mover is part of a team of at least 2 + e->parent = ent->teammaster->teamchain; // hand the brush over to the next mover in command + } + else + G_FreeEntity( e ); // remove the teamless or to-be-orphaned brush + break; + } + } + } + // removing a mover opens the relevant portal + trap_AdjustAreaPortalState( ent, qtrue ); + } + else if( !strcmp( ent->classname, "path_corner" ) ) + { + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) + { + if( e->nextTrain == ent ) + e->nextTrain = ent->nextTrain; // redirect func_train and path_corner entities + } + } + else if( !strcmp( ent->classname, "info_player_intermission" ) || + !strcmp( ent->classname, "info_player_deathmatch" ) ) + { + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) + { + if( e->inuse && e != ent && + ( !strcmp( e->classname, "info_player_intermission" ) || + !strcmp( e->classname, "info_player_deathmatch" ) ) ) + { + break; + } + } + // refuse to remove the last info_player_intermission/info_player_deathmatch entity + // (because it is required for initial camera placement) + if( e >= &g_entities[ level.num_entities ] ) + return; + } + else if( !strcmp( ent->classname, "target_location" ) ) + { + if( ent == level.locationHead ) + level.locationHead = ent->nextTrain; + else + { + for( e = level.locationHead; e != NULL; e = e->nextTrain ) + { + if( e->nextTrain == ent ) + { + e->nextTrain = ent->nextTrain; + break; + } + } + } + } + else if( !Q_stricmp( ent->classname, "misc_portal_camera" ) ) + { + for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) + { + if( e->r.ownerNum == ent - g_entities ) + { + // disown the surface + e->r.ownerNum = ENTITYNUM_NONE; + } + } + } + + if( ent->teammaster != NULL ) + { + // this entity is part of a mover team + if( ent == ent->teammaster ) + { + // the entity is the master + gentity_t *snd = ent->teamchain; + for( e = snd; e != NULL; e = e->teamchain ) + e->teammaster = snd; // put the 2nd entity (if any) in command + if( snd ) + { + if( !strcmp( ent->classname, snd->classname ) ) + { + // transfer certain activity properties + snd->think = ent->think; + snd->nextthink = ent->nextthink; + } + snd->flags &= ~FL_TEAMSLAVE; // put the 2nd entity (if any) in command + } + } + else + { + // the entity is a slave + for( e = ent->teammaster; e != NULL; e = e->teamchain ) + { + if( e->teamchain == ent ) + { + // unlink it from the chain + e->teamchain = ent->teamchain; + break; + } + } + } + } + + G_FreeEntity( ent ); +} + /* ================= G_TempEntity @@ -535,7 +680,7 @@ The origin will be snapped to save net bandwidth, so care must be taken if the origin is right on a surface (snap towards start vector first) ================= */ -gentity_t *G_TempEntity( vec3_t origin, int event ) +gentity_t *G_TempEntity( const vec3_t origin, int event ) { gentity_t *e; vec3_t snapped; @@ -582,18 +727,18 @@ void G_KillBox( gentity_t *ent ) gentity_t *hit; vec3_t mins, maxs; - VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); - VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { hit = &g_entities[ touch[ i ] ]; - if( !hit->client ) + if( ent->client && !hit->client ) // players can telefrag only other players continue; - //TA: impossible to telefrag self + // impossible to telefrag self if( ent == hit ) continue; @@ -644,8 +789,8 @@ void G_AddEvent( gentity_t *ent, int event, int eventParm ) // eventParm is converted to uint8_t (0 - 255) in msg.c if( eventParm & ~0xFF ) { - G_Printf( S_COLOR_YELLOW "WARNING: G_AddEvent: event %d " - " eventParm uint8_t overflow (given %d)\n", event, eventParm ); + G_Printf( S_COLOR_YELLOW "WARNING: G_AddEvent( %s ) has eventParm %d, " + "which will overflow\n", BG_EventName( event ), eventParm ); } // clients need to add the event in playerState_t instead of entityState_t @@ -728,7 +873,7 @@ G_SetOrigin Sets the pos trajectory for a fixed position ================ */ -void G_SetOrigin( gentity_t *ent, vec3_t origin ) +void G_SetOrigin( gentity_t *ent, const vec3_t origin ) { VectorCopy( origin, ent->s.pos.trBase ); ent->s.pos.trType = TR_STATIONARY; @@ -737,10 +882,9 @@ void G_SetOrigin( gentity_t *ent, vec3_t origin ) VectorClear( ent->s.pos.trDelta ); VectorCopy( origin, ent->r.currentOrigin ); - VectorCopy( origin, ent->s.origin ); //TA: if shit breaks - blame this line } -//TA: from quakestyle.telefragged.com +// from quakestyle.telefragged.com // (NOBODY): Code helper function // gentity_t *G_FindRadius( gentity_t *from, vec3_t org, float rad ) @@ -777,16 +921,14 @@ G_Visible Test for a LOS between two entities =============== */ -qboolean G_Visible( gentity_t *ent1, gentity_t *ent2 ) +qboolean G_Visible( gentity_t *ent1, gentity_t *ent2, int contents ) { trace_t trace; - trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, ent1->s.number, MASK_SHOT ); - - if( trace.contents & CONTENTS_SOLID ) - return qfalse; + trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, + ent1->s.number, contents ); - return qtrue; + return trace.fraction >= 1.0f || trace.entityNum == ent2 - g_entities; } /* @@ -799,15 +941,21 @@ Test a list of entities for the closest to a particular point gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities ) { int i; - float nd, d = 1000000.0f; - gentity_t *closestEnt = NULL; + float nd, d; + gentity_t *closestEnt; + + if( numEntities <= 0 ) + return NULL; + + closestEnt = entities[ 0 ]; + d = DistanceSquared( origin, closestEnt->r.currentOrigin ); - for( i = 0; i < numEntities; i++ ) + for( i = 1; i < numEntities; i++ ) { gentity_t *ent = entities[ i ]; - nd = DistanceSquared( origin, ent->s.origin ); - if( i == 0 || nd < d ) + nd = DistanceSquared( origin, ent->r.currentOrigin ); + if( nd < d ) { d = nd; closestEnt = ent; @@ -828,10 +976,24 @@ void G_TriggerMenu( int clientNum, dynMenu_t menu ) { char buffer[ 32 ]; - Com_sprintf( buffer, 32, "servermenu %d", menu ); + Com_sprintf( buffer, sizeof( buffer ), "servermenu %d", menu ); trap_SendServerCommand( clientNum, buffer ); } +/* +=============== +G_TriggerMenuArgs + +Trigger a menu on some client and passes an argument +=============== +*/ +void G_TriggerMenuArgs( int clientNum, dynMenu_t menu, int arg ) +{ + char buffer[ 64 ]; + + Com_sprintf( buffer, sizeof( buffer ), "servermenu %d %d", menu, arg ); + trap_SendServerCommand( clientNum, buffer ); +} /* =============== @@ -847,3 +1009,179 @@ void G_CloseMenus( int clientNum ) Com_sprintf( buffer, 32, "serverclosemenus" ); trap_SendServerCommand( clientNum, buffer ); } + + +/* +=============== +G_AddressParse + +Make an IP address more usable +=============== +*/ +static const char *addr4parse( const char *str, addr_t *addr ) +{ + int i; + int octet = 0; + int num = 0; + memset( addr, 0, sizeof( addr_t ) ); + addr->type = IPv4; + for( i = 0; octet < 4; i++ ) + { + if( isdigit( str[ i ] ) ) + num = num * 10 + str[ i ] - '0'; + else + { + if( num < 0 || num > 255 ) + return NULL; + addr->addr[ octet ] = (byte)num; + octet++; + if( str[ i ] != '.' || str[ i + 1 ] == '.' ) + break; + num = 0; + } + } + if( octet < 1 ) + return NULL; + return str + i; +} + +static const char *addr6parse( const char *str, addr_t *addr ) +{ + int i; + qboolean seen = qfalse; + /* keep track of the parts before and after the :: + it's either this or even uglier hacks */ + byte a[ ADDRLEN ], b[ ADDRLEN ]; + size_t before = 0, after = 0; + int num = 0; + /* 8 hexadectets unless :: is present */ + for( i = 0; before + after <= 8; i++ ) + { + //num = num << 4 | str[ i ] - '0'; + if( isdigit( str[ i ] ) ) + num = num * 16 + str[ i ] - '0'; + else if( str[ i ] >= 'A' && str[ i ] <= 'F' ) + num = num * 16 + 10 + str[ i ] - 'A'; + else if( str[ i ] >= 'a' && str[ i ] <= 'f' ) + num = num * 16 + 10 + str[ i ] - 'a'; + else + { + if( num < 0 || num > 65535 ) + return NULL; + if( i == 0 ) + { + // + } + else if( seen ) // :: has been seen already + { + b[ after * 2 ] = num >> 8; + b[ after * 2 + 1 ] = num & 0xff; + after++; + } + else + { + a[ before * 2 ] = num >> 8; + a[ before * 2 + 1 ] = num & 0xff; + before++; + } + if( !str[ i ] ) + break; + if( str[ i ] != ':' || before + after == 8 ) + break; + if( str[ i + 1 ] == ':' ) + { + // ::: or multiple :: + if( seen || str[ i + 2 ] == ':' ) + break; + seen = qtrue; + i++; + } + else if( i == 0 ) // starts with : but not :: + return NULL; + num = 0; + } + } + if( seen ) + { + // there have to be fewer than 8 hexadectets when :: is present + if( before + after == 8 ) + return NULL; + } + else if( before + after < 8 ) // require exactly 8 hexadectets + return NULL; + memset( addr, 0, sizeof( addr_t ) ); + addr->type = IPv6; + if( before ) + memcpy( addr->addr, a, before * 2 ); + if( after ) + memcpy( addr->addr + ADDRLEN - 2 * after, b, after * 2 ); + return str + i; +} + +qboolean G_AddressParse( const char *str, addr_t *addr ) +{ + const char *p; + int max; + if( strchr( str, ':' ) ) + { + p = addr6parse( str, addr ); + max = 128; + } + else if( strchr( str, '.' ) ) + { + p = addr4parse( str, addr ); + max = 32; + } + else + return qfalse; + Q_strncpyz( addr->str, str, sizeof( addr->str ) ); + if( !p ) + return qfalse; + if( *p == '/' ) + { + addr->mask = atoi( p + 1 ); + if( addr->mask < 1 || addr->mask > max ) + addr->mask = max; + } + else + { + if( *p ) + return qfalse; + addr->mask = max; + } + return qtrue; +} + +/* +=============== +G_AddressCompare + +Based largely on NET_CompareBaseAdrMask from ioq3 revision 1557 +=============== +*/ +qboolean G_AddressCompare( const addr_t *a, const addr_t *b ) +{ + int i, netmask; + if( a->type != b->type ) + return qfalse; + netmask = a->mask; + if( a->type == IPv4 ) + { + if( netmask < 1 || netmask > 32 ) + netmask = 32; + } + else if( a->type == IPv6 ) + { + if( netmask < 1 || netmask > 128 ) + netmask = 128; + } + for( i = 0; netmask > 7; i++, netmask -= 8 ) + if( a->addr[ i ] != b->addr[ i ] ) + return qfalse; + if( netmask ) + { + netmask = ( ( 1 << netmask ) - 1 ) << ( 8 - netmask ); + return ( a->addr[ i ] & netmask ) == ( b->addr[ i ] & netmask ); + } + return qtrue; +} diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index ddad4af..302c47e 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -36,51 +37,24 @@ G_ForceWeaponChange */ void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon ) { - int i; - - if( !ent ) - return; - - if( ent->client->ps.weaponstate == WEAPON_RELOADING ) - { - ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE; - ent->client->ps.weaponTime = 250; - ent->client->ps.weaponstate = WEAPON_READY; - } - - ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH; + playerState_t *ps = &ent->client->ps; - if( weapon == WP_NONE - || !BG_InventoryContainsWeapon( weapon, ent->client->ps.stats )) + // stop a reload in progress + if( ps->weaponstate == WEAPON_RELOADING ) { - //switch to the first non blaster weapon - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( i == WP_BLASTER ) - continue; - - if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) - { - ent->client->ps.persistant[ PERS_NEWWEAPON ] = i; - break; - } - } - - //only got the blaster to switch to - if( i == WP_NUM_WEAPONS ) - ent->client->ps.persistant[ PERS_NEWWEAPON ] = WP_BLASTER; + ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE; + ps->weaponTime = 250; + ps->weaponstate = WEAPON_READY; } - else - ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon; - // Lak: The following hack has been moved to PM_BeginWeaponChange, but I'm going to - // redundantly leave it here as well just in case there's a case I'm forgetting - // because I don't want to face the gameplay consequences such an error would have + ps->persistant[ PERS_NEWWEAPON ] = ( weapon == WP_BLASTER ) ? + WP_BLASTER : ps->stats[ STAT_WEAPON ]; // force this here to prevent flamer effect from continuing - ent->client->ps.generic1 = WPM_NOTFIRING; + ps->generic1 = WPM_NOTFIRING; - ent->client->ps.weapon = ent->client->ps.persistant[ PERS_NEWWEAPON ]; + // The PMove will do an animated drop, raise, and set the new weapon + ps->pm_flags |= PMF_WEAPON_SWITCH; } /* @@ -90,43 +64,35 @@ G_GiveClientMaxAmmo */ void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ) { - int i; - int maxAmmo, maxClips; - qboolean weaponType, restoredAmmo = qfalse; + int maxAmmo; + weapon_t weapon = ent->client->ps.stats[ STAT_WEAPON ]; - // GH FIXME + if( BG_Weapon( weapon )->infiniteAmmo ) + return; - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( buyingEnergyAmmo ) - weaponType = BG_FindUsesEnergyForWeapon( i ); - else - weaponType = !BG_FindUsesEnergyForWeapon( i ); + if( buyingEnergyAmmo && !BG_Weapon( weapon )->usesEnergy ) + return; - if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && - weaponType && !BG_FindInfinteAmmoForWeapon( i ) && - !BG_WeaponIsFull( i, ent->client->ps.stats, - ent->client->ps.ammo, ent->client->ps.clips ) ) - { - BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips ); + if( BG_WeaponIsFull( weapon, ent->client->ps.stats, ent->client->ps.ammo, + ent->client->ps.clips ) ) + return; - if( buyingEnergyAmmo ) - { - G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); + maxAmmo = BG_Weapon( weapon )->maxAmmo; - if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) - maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); - } + // Apply battery pack modifier + if( BG_Weapon( weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) + { + maxAmmo *= BATTPACK_MODIFIER; + } - ent->client->ps.ammo = maxAmmo; - ent->client->ps.clips = maxClips; + ent->client->ps.ammo = maxAmmo; + ent->client->ps.clips = BG_Weapon( weapon )->maxClips; - restoredAmmo = qtrue; - } - } + G_ForceWeaponChange( ent, ent->client->ps.weapon ); - if( restoredAmmo ) - G_ForceWeaponChange( ent, ent->client->ps.weapon ); + if( BG_Weapon( weapon )->usesEnergy ) + G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); } /* @@ -155,50 +121,46 @@ Trace a bounding box against entities, but not the world Also check there is a line of sight between the start and end point ================ */ -static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target ) +static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, + float width, float height, gentity_t **target ) { vec3_t mins, maxs; vec3_t end; - VectorSet( mins, -width, -width, -width ); - VectorSet( maxs, width, width, width ); + VectorSet( mins, -width, -width, -height ); + VectorSet( maxs, width, width, height ); *target = NULL; if( !ent->client ) return; - // Set aiming directions - AngleVectors( ent->client->ps.viewangles, forward, right, up ); - CalcMuzzlePoint( ent, forward, right, up, muzzle ); - VectorMA( muzzle, range, forward, end ); + G_UnlaggedOn( ent, muzzle, range + width ); - G_UnlaggedOn( ent, muzzle, range ); + VectorMA( muzzle, range, forward, end ); // Trace against entities trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY ); if( tr->entityNum != ENTITYNUM_NONE ) - { *target = &g_entities[ tr->entityNum ]; - // Set range to the trace length plus the width, so that the end of the - // LOS trace is close to the exterior of the target's bounding box - range = Distance( muzzle, tr->endpos ) + width; - VectorMA( muzzle, range, forward, end ); + // Set range to the trace length plus the width, so that the end of the + // LOS trace is close to the exterior of the target's bounding box + range = Distance( muzzle, tr->endpos ) + width; + VectorMA( muzzle, range, forward, end ); - // Trace for line of sight against the world - trap_Trace( tr, muzzle, NULL, NULL, end, 0, CONTENTS_SOLID ); - if( tr->fraction < 1.0f ) - *target = NULL; - } + // Trace for line of sight against the world + trap_Trace( tr, muzzle, NULL, NULL, end, ent->s.number, CONTENTS_SOLID ); + if( tr->entityNum != ENTITYNUM_NONE ) + *target = &g_entities[ tr->entityNum ]; G_UnlaggedOff( ); } - /* ====================== SnapVectorTowards +SnapVectorNormal Round a vector to integers for more efficient network transmission, but make sure that it rounds towards a given point @@ -212,57 +174,120 @@ void SnapVectorTowards( vec3_t v, vec3_t to ) for( i = 0 ; i < 3 ; i++ ) { - if( to[ i ] <= v[ i ] ) - v[ i ] = (int)v[ i ]; + if( v[ i ] >= 0 ) + v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? 0 : 1 ) ); + else + v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? -1 : 0 ) ); + } +} + +void SnapVectorNormal( vec3_t v, vec3_t normal ) +{ + int i; + + for( i = 0 ; i < 3 ; i++ ) + { + if( v[ i ] >= 0 ) + v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? 0 : 1 ) ); else - v[ i ] = (int)v[ i ] + 1; + v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? -1 : 0 ) ); } } /* =============== -meleeAttack +BloodSpurt + +Generates a blood spurt event for traces with accurate end points =============== */ -void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod ) +static void BloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr ) { - trace_t tr; - vec3_t end; gentity_t *tent; - gentity_t *traceEnt; - vec3_t mins, maxs; - VectorSet( mins, -width, -width, -width ); - VectorSet( maxs, width, width, width ); + if( !attacker->client ) + return; - // set aiming directions - AngleVectors( ent->client->ps.viewangles, forward, right, up ); + if( victim->health <= 0 ) + return; - CalcMuzzlePoint( ent, forward, right, up, muzzle ); + tent = G_TempEntity( tr->endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = victim->s.number; + tent->s.eventParm = DirToByte( tr->plane.normal ); + tent->s.weapon = attacker->s.weapon; + tent->s.generic1 = attacker->s.generic1; // weaponMode +} - VectorMA( muzzle, range, forward, end ); +/* +=============== +WideBloodSpurt - G_UnlaggedOn( ent, muzzle, range ); - trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); - G_UnlaggedOff( ); +Calculates the position of a blood spurt for wide traces and generates an event +=============== +*/ +static void WideBloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr ) +{ + gentity_t *tent; + vec3_t normal, origin; + float mag, radius; - if( tr.surfaceFlags & SURF_NOIMPACT ) + if( !attacker->client ) return; - traceEnt = &g_entities[ tr.entityNum ]; + if( victim->health <= 0 ) + return; - // send blood impact - if( traceEnt->takedamage && traceEnt->client ) + if( tr ) + VectorSubtract( tr->endpos, victim->r.currentOrigin, normal ); + else + VectorSubtract( attacker->client->ps.origin, + victim->r.currentOrigin, normal ); + + // Normalize the horizontal components of the vector difference to the + // "radius" of the bounding box + mag = sqrt( normal[ 0 ] * normal[ 0 ] + normal[ 1 ] * normal[ 1 ] ); + radius = victim->r.maxs[ 0 ] * 1.21f; + if( mag > radius ) { - tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); - tent->s.otherEntityNum = traceEnt->s.number; - tent->s.eventParm = DirToByte( tr.plane.normal ); - tent->s.weapon = ent->s.weapon; - tent->s.generic1 = ent->s.generic1; //weaponMode + normal[ 0 ] = normal[ 0 ] / mag * radius; + normal[ 1 ] = normal[ 1 ] / mag * radius; } - if( traceEnt->takedamage ) - G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, mod ); + // Clamp origin to be within bounding box vertically + if( normal[ 2 ] > victim->r.maxs[ 2 ] ) + normal[ 2 ] = victim->r.maxs[ 2 ]; + if( normal[ 2 ] < victim->r.mins[ 2 ] ) + normal[ 2 ] = victim->r.mins[ 2 ]; + + VectorAdd( victim->r.currentOrigin, normal, origin ); + VectorNegate( normal, normal ); + VectorNormalize( normal ); + + // Create the blood spurt effect entity + tent = G_TempEntity( origin, EV_MISSILE_HIT ); + tent->s.eventParm = DirToByte( normal ); + tent->s.otherEntityNum = victim->s.number; + tent->s.weapon = attacker->s.weapon; + tent->s.generic1 = attacker->s.generic1; // weaponMode +} + +/* +=============== +meleeAttack +=============== +*/ +void meleeAttack( gentity_t *ent, float range, float width, float height, + int damage, meansOfDeath_t mod ) +{ + trace_t tr; + gentity_t *traceEnt; + + G_WideTrace( &tr, ent, range, width, height, &traceEnt ); + if( traceEnt == NULL || !traceEnt->takedamage ) + return; + + WideBloodSpurt( ent, traceEnt, &tr ); + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod ); } /* @@ -308,7 +333,9 @@ void bulletFire( gentity_t *ent, float spread, int damage, int mod ) SnapVectorTowards( tr.endpos, muzzle ); // send bullet impact - if( traceEnt->takedamage && traceEnt->client ) + if( traceEnt->takedamage && + (traceEnt->s.eType == ET_PLAYER || + traceEnt->s.eType == ET_BUILDABLE ) ) { tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); tent->s.eventParm = traceEnt->s.number; @@ -356,7 +383,7 @@ void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) { r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; - VectorMA( origin, 8192 * 16, forward, end ); + VectorMA( origin, SHOTGUN_RANGE, forward, end ); VectorMA( end, r, right, end ); VectorMA( end, u, up, end ); @@ -381,9 +408,9 @@ void shotgunFire( gentity_t *ent ) tent = G_TempEntity( muzzle, EV_SHOTGUN ); VectorScale( forward, 4096, tent->s.origin2 ); SnapVector( tent->s.origin2 ); - tent->s.eventParm = rand() & 255; // seed for spread pattern + tent->s.eventParm = rand() / ( RAND_MAX / 0x100 + 1 ); // seed for spread pattern tent->s.otherEntityNum = ent->s.number; - G_UnlaggedOn( ent, muzzle, 8192 * 16 ); + G_UnlaggedOn( ent, muzzle, SHOTGUN_RANGE ); ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); G_UnlaggedOff(); } @@ -403,9 +430,9 @@ void massDriverFire( gentity_t *ent ) gentity_t *tent; gentity_t *traceEnt; - VectorMA( muzzle, 8192 * 16, forward, end ); + VectorMA( muzzle, 8192.0f * 16.0f, forward, end ); - G_UnlaggedOn( ent, muzzle, 8192 * 16 ); + G_UnlaggedOn( ent, muzzle, 8192.0f * 16.0f ); trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); G_UnlaggedOff( ); @@ -418,13 +445,11 @@ void massDriverFire( gentity_t *ent ) SnapVectorTowards( tr.endpos, muzzle ); // send impact - if( traceEnt->takedamage && traceEnt->client ) + if( traceEnt->takedamage && + (traceEnt->s.eType == ET_BUILDABLE || + traceEnt->s.eType == ET_PLAYER ) ) { - tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); - tent->s.otherEntityNum = traceEnt->s.number; - tent->s.eventParm = DirToByte( tr.plane.normal ); - tent->s.weapon = ent->s.weapon; - tent->s.generic1 = ent->s.generic1; //weaponMode + BloodSpurt( ent, traceEnt, &tr ); } else { @@ -451,11 +476,7 @@ LOCKBLOB void lockBlobLauncherFire( gentity_t *ent ) { - gentity_t *m; - - m = fire_lockblob( ent, muzzle, forward ); - -// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + fire_lockblob( ent, muzzle, forward ); } /* @@ -468,11 +489,12 @@ HIVE void hiveFire( gentity_t *ent ) { - gentity_t *m; - - m = fire_hive( ent, muzzle, forward ); + vec3_t origin; -// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + // Fire from the hive tip, not the center + VectorMA( muzzle, ent->r.maxs[ 2 ], ent->s.origin2, origin ); + + fire_hive( ent, origin, forward ); } /* @@ -485,11 +507,7 @@ BLASTER PISTOL void blasterFire( gentity_t *ent ) { - gentity_t *m; - - m = fire_blaster( ent, muzzle, forward ); - -// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + fire_blaster( ent, muzzle, forward ); } /* @@ -502,11 +520,7 @@ PULSE RIFLE void pulseRifleFire( gentity_t *ent ) { - gentity_t *m; - - m = fire_pulseRifle( ent, muzzle, forward ); - -// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + fire_pulseRifle( ent, muzzle, forward ); } /* @@ -519,9 +533,15 @@ FLAME THROWER void flamerFire( gentity_t *ent ) { - gentity_t *m; + vec3_t origin; + + // Correct muzzle so that the missile does not start in the ceiling + VectorMA( muzzle, -7.0f, up, origin ); + + // Correct muzzle so that the missile fires from the player's hand + VectorMA( origin, 4.5f, right, origin ); - m = fire_flamer( ent, muzzle, forward ); + fire_flamer( ent, origin, forward ); } /* @@ -534,9 +554,7 @@ GRENADE void throwGrenade( gentity_t *ent ) { - gentity_t *m; - - m = launch_grenade( ent, muzzle, forward ); + launch_grenade( ent, muzzle, forward ); } /* @@ -574,13 +592,11 @@ void lasGunFire( gentity_t *ent ) SnapVectorTowards( tr.endpos, muzzle ); // send impact - if( traceEnt->takedamage && traceEnt->client ) + if( traceEnt->takedamage && + (traceEnt->s.eType == ET_BUILDABLE || + traceEnt->s.eType == ET_PLAYER ) ) { - tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); - tent->s.otherEntityNum = traceEnt->s.number; - tent->s.eventParm = DirToByte( tr.plane.normal ); - tent->s.weapon = ent->s.weapon; - tent->s.generic1 = ent->s.generic1; //weaponMode + BloodSpurt( ent, traceEnt, &tr ); } else { @@ -605,50 +621,32 @@ PAIN SAW void painSawFire( gentity_t *ent ) { trace_t tr; - vec3_t end; - gentity_t *tent; - gentity_t *traceEnt; - - // set aiming directions - AngleVectors( ent->client->ps.viewangles, forward, right, up ); - - CalcMuzzlePoint( ent, forward, right, up, muzzle ); - - VectorMA( muzzle, PAINSAW_RANGE, forward, end ); - - G_UnlaggedOn( ent, muzzle, PAINSAW_RANGE ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); - G_UnlaggedOff( ); + vec3_t temp; + gentity_t *tent, *traceEnt; - if( tr.surfaceFlags & SURF_NOIMPACT ) + G_WideTrace( &tr, ent, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT, + &traceEnt ); + if( !traceEnt || !traceEnt->takedamage ) return; - traceEnt = &g_entities[ tr.entityNum ]; + // hack to line up particle system with weapon model + tr.endpos[ 2 ] -= 5.0f; // send blood impact - if( traceEnt->takedamage ) + if( traceEnt->s.eType == ET_PLAYER || traceEnt->s.eType == ET_BUILDABLE ) + { + BloodSpurt( ent, traceEnt, &tr ); + } + else { - vec3_t temp; - - //hack to get the particle system to line up with the weapon VectorCopy( tr.endpos, temp ); - temp[ 2 ] -= 10.0f; - - if( traceEnt->client ) - { - tent = G_TempEntity( temp, EV_MISSILE_HIT ); - tent->s.otherEntityNum = traceEnt->s.number; - } - else - tent = G_TempEntity( temp, EV_MISSILE_MISS ); - + tent = G_TempEntity( temp, EV_MISSILE_MISS ); tent->s.eventParm = DirToByte( tr.plane.normal ); tent->s.weapon = ent->s.weapon; tent->s.generic1 = ent->s.generic1; //weaponMode } - if( traceEnt->takedamage ) - G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, 0, MOD_PAINSAW ); + G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW ); } /* @@ -666,19 +664,14 @@ LCChargeFire */ void LCChargeFire( gentity_t *ent, qboolean secondary ) { - gentity_t *m; - - if( secondary ) - { - m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, - LCANNON_SECONDARY_RADIUS ); - ent->client->ps.weaponTime = LCANNON_REPEAT; - } + if( secondary && ent->client->ps.stats[ STAT_MISC ] <= 0 ) + fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, + LCANNON_SECONDARY_RADIUS, LCANNON_SECONDARY_SPEED ); else - { - m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS ); - ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT; - } + fire_luciferCannon( ent, muzzle, forward, + ent->client->ps.stats[ STAT_MISC ] * + LCANNON_DAMAGE / LCANNON_CHARGE_TIME_MAX, + LCANNON_RADIUS, LCANNON_SPEED ); ent->client->ps.stats[ STAT_MISC ] = 0; } @@ -692,49 +685,44 @@ TESLA GENERATOR */ -void teslaFire( gentity_t *ent ) +void teslaFire( gentity_t *self ) { - trace_t tr; - vec3_t end; - gentity_t *traceEnt, *tent; - - VectorMA( muzzle, TESLAGEN_RANGE, forward, end ); - - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + trace_t tr; + vec3_t origin, target; + gentity_t *tent; - if( tr.entityNum == ENTITYNUM_NONE ) + if( !self->enemy ) return; - traceEnt = &g_entities[ tr.entityNum ]; + // Move the muzzle from the entity origin up a bit to fire over turrets + VectorMA( muzzle, self->r.maxs[ 2 ], self->s.origin2, origin ); - if( !traceEnt->client ) - return; + // Don't aim for the center, aim at the top of the bounding box + VectorCopy( self->enemy->r.currentOrigin, target ); + target[ 2 ] += self->enemy->r.maxs[ 2 ]; - if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + // Trace to the target entity + trap_Trace( &tr, origin, NULL, NULL, target, self->s.number, MASK_SHOT ); + if( tr.entityNum != self->enemy->s.number ) return; - //so the client side knows - ent->s.eFlags |= EF_FIRING; + // Client side firing effect + self->s.eFlags |= EF_FIRING; - if( traceEnt->takedamage ) + // Deal damage + if( self->enemy->takedamage ) { - G_Damage( traceEnt, ent, ent, forward, tr.endpos, - TESLAGEN_DMG, 0, MOD_TESLAGEN ); - } + vec3_t dir; - // snap the endpos to integers to save net bandwidth, but nudged towards the line - SnapVectorTowards( tr.endpos, muzzle ); + VectorSubtract( target, origin, dir ); + G_Damage( self->enemy, self, self, dir, tr.endpos, + TESLAGEN_DMG, 0, MOD_TESLAGEN ); + } - // send railgun beam effect + // Send tesla zap trail tent = G_TempEntity( tr.endpos, EV_TESLATRAIL ); - - VectorCopy( muzzle, tent->s.origin2 ); - - tent->s.generic1 = ent->s.number; //src - tent->s.clientNum = traceEnt->s.number; //dest - - // move origin a bit to come closer to the drawn gun muzzle - VectorMA( tent->s.origin2, 28, up, tent->s.origin2 ); + tent->s.misc = self->s.number; // src + tent->s.clientNum = self->enemy->s.number; // dest } @@ -745,65 +733,62 @@ BUILD GUN ====================================================================== */ - -/* -=============== -cancelBuildFire -=============== -*/ -void cancelBuildFire( gentity_t *ent ) +void CheckCkitRepair( gentity_t *ent ) { - vec3_t forward, end; + vec3_t viewOrigin, forward, end; trace_t tr; gentity_t *traceEnt; int bHealth; - if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) - { - ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + if( ent->client->ps.weaponTime > 0 || + ent->client->ps.stats[ STAT_MISC ] > 0 ) return; - } - //repair buildable - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); - VectorMA( ent->client->ps.origin, 100, forward, end ); + BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( viewOrigin, 100, forward, end ); - trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; + trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; - if( tr.fraction < 1.0 && - ( traceEnt->s.eType == ET_BUILDABLE ) && - ( traceEnt->biteam == ent->client->ps.stats[ STAT_PTEAM ] ) && - ( ( ent->client->ps.weapon >= WP_HBUILD2 ) && - ( ent->client->ps.weapon <= WP_HBUILD ) ) && - traceEnt->spawned && traceEnt->health > 0 ) + if( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && + traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) + { + bHealth = BG_Buildable( traceEnt->s.modelindex )->health; + if( traceEnt->health < bHealth ) { - if( ent->client->ps.stats[ STAT_MISC ] > 0 ) - { - G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); - return; - } - - bHealth = BG_FindHealthForBuildable( traceEnt->s.modelindex ); - traceEnt->health += HBUILD_HEALRATE; - ent->client->pers.statscounters.repairspoisons++; - level.humanStatsCounters.repairspoisons++; - - if( traceEnt->health > bHealth ) + if( traceEnt->health >= bHealth ) + { traceEnt->health = bHealth; - - if( traceEnt->health == bHealth ) G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); + } else G_AddEvent( ent, EV_BUILD_REPAIR, 0 ); + + ent->client->ps.weaponTime += BG_Weapon( ent->client->ps.weapon )->repeatRate1; } } - else if( ent->client->ps.weapon == WP_ABUILD2 ) +} + +/* +=============== +cancelBuildFire +=============== +*/ +void cancelBuildFire( gentity_t *ent ) +{ + // Cancel ghost buildable + if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) + { + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + return; + } + + if( ent->client->ps.weapon == WP_ABUILD || + ent->client->ps.weapon == WP_ABUILD2 ) meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH, - ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); //melee attack for alien builder + ABUILDER_CLAW_WIDTH, ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); } /* @@ -813,7 +798,10 @@ buildFire */ void buildFire( gentity_t *ent, dynMenu_t menu ) { - if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) + buildable_t buildable = ( ent->client->ps.stats[ STAT_BUILDABLE ] + & ~SB_VALID_TOGGLEBIT ); + + if( buildable > BA_NONE ) { if( ent->client->ps.stats[ STAT_MISC ] > 0 ) { @@ -821,33 +809,17 @@ void buildFire( gentity_t *ent, dynMenu_t menu ) return; } - if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) + if( G_BuildIfValid( ent, buildable ) ) { - if( g_cheats.integer ) - { - ent->client->ps.stats[ STAT_MISC ] = 0; - } - else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) ) - { - ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; - } - else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_IsPowered( muzzle ) && - ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack + if( !g_cheats.integer ) { ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; + BG_Buildable( buildable )->buildTime; } - else - ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ); ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - - // don't want it bigger than 32k - if( ent->client->ps.stats[ STAT_MISC ] > 30000 ) - ent->client->ps.stats[ STAT_MISC ] = 30000; } + return; } @@ -856,11 +828,7 @@ void buildFire( gentity_t *ent, dynMenu_t menu ) void slowBlobFire( gentity_t *ent ) { - gentity_t *m; - - m = fire_slowBlob( ent, muzzle, forward ); - -// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + fire_slowBlob( ent, muzzle, forward ); } @@ -880,65 +848,51 @@ CheckVenomAttack qboolean CheckVenomAttack( gentity_t *ent ) { trace_t tr; - vec3_t end; - gentity_t *tent; gentity_t *traceEnt; - vec3_t mins, maxs; int damage = LEVEL0_BITE_DMG; - VectorSet( mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH ); - VectorSet( maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH ); + if( ent->client->ps.weaponTime ) + return qfalse; - // set aiming directions + // Calculate muzzle point AngleVectors( ent->client->ps.viewangles, forward, right, up ); - CalcMuzzlePoint( ent, forward, right, up, muzzle ); - VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end ); - - G_UnlaggedOn( ent, muzzle, LEVEL0_BITE_RANGE ); - trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); - G_UnlaggedOff( ); + G_WideTrace( &tr, ent, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH, + LEVEL0_BITE_WIDTH, &traceEnt ); - if( tr.surfaceFlags & SURF_NOIMPACT ) + if( traceEnt == NULL ) return qfalse; - traceEnt = &g_entities[ tr.entityNum ]; - if( !traceEnt->takedamage ) return qfalse; - //allow bites to work against defensive buildables only + if( traceEnt->health <= 0 ) + return qfalse; + + // only allow bites to work against buildings as they are constructing if( traceEnt->s.eType == ET_BUILDABLE ) { - if( traceEnt->s.modelindex != BA_H_MGTURRET && - traceEnt->s.modelindex != BA_H_TESLAGEN ) + if( traceEnt->spawned ) return qfalse; - //hackery - damage *= 0.5f; + if( traceEnt->buildableTeam == TEAM_ALIENS ) + return qfalse; } if( traceEnt->client ) { - if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) return qfalse; if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) return qfalse; } // send blood impact - if( traceEnt->takedamage && traceEnt->client ) - { - tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); - tent->s.otherEntityNum = traceEnt->s.number; - tent->s.eventParm = DirToByte( tr.plane.normal ); - tent->s.weapon = ent->s.weapon; - tent->s.generic1 = ent->s.generic1; //weaponMode - } - - G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LEVEL0_BITE ); + WideBloodSpurt( ent, traceEnt, &tr ); + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE ); + ent->client->ps.weaponTime += LEVEL0_BITE_REPEAT; return qtrue; } @@ -966,7 +920,10 @@ void CheckGrabAttack( gentity_t *ent ) CalcMuzzlePoint( ent, forward, right, up, muzzle ); - VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); + if( ent->client->ps.weapon == WP_ALEVEL1 ) + VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); + else if( ent->client->ps.weapon == WP_ALEVEL1_UPG ) + VectorMA( muzzle, LEVEL1_GRAB_U_RANGE, forward, end ); trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) @@ -979,7 +936,7 @@ void CheckGrabAttack( gentity_t *ent ) if( traceEnt->client ) { - if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) return; if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) @@ -1001,15 +958,6 @@ void CheckGrabAttack( gentity_t *ent ) else if( ent->client->ps.weapon == WP_ALEVEL1_UPG ) traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME; } - else if( traceEnt->s.eType == ET_BUILDABLE && - traceEnt->s.modelindex == BA_H_MGTURRET ) - { - if( !traceEnt->lev1Grabbed ) - G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 ); - - traceEnt->lev1Grabbed = qtrue; - traceEnt->lev1GrabTime = level.time; - } } /* @@ -1023,7 +971,7 @@ void poisonCloud( gentity_t *ent ) vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE }; vec3_t mins, maxs; int i, num; - gentity_t *target; + gentity_t *humanPlayer; trace_t tr; VectorAdd( ent->client->ps.origin, range, maxs ); @@ -1033,39 +981,23 @@ void poisonCloud( gentity_t *ent ) num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { - target = &g_entities[ entityList[ i ] ]; + humanPlayer = &g_entities[ entityList[ i ] ]; - if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( humanPlayer->client && + humanPlayer->client->pers.teamSelection == TEAM_HUMANS ) { - if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, target->client->ps.stats ) ) - continue; - - if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, target->client->ps.stats ) ) - continue; - - trap_Trace( &tr, muzzle, NULL, NULL, target->s.origin, target->s.number, MASK_SHOT ); + trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->r.currentOrigin, + humanPlayer->s.number, CONTENTS_SOLID ); //can't see target from here if( tr.entityNum == ENTITYNUM_WORLD ) continue; - if( !( target->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) - { - target->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; - target->client->lastPoisonCloudedTime = level.time; - target->client->lastPoisonCloudedClient = ent; - trap_SendServerCommand( target->client->ps.clientNum, "poisoncloud" ); - } - } - if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - trap_Trace( &tr, muzzle, NULL, NULL, target->s.origin, target->s.number, MASK_SOLID ); - - if( tr.entityNum == ENTITYNUM_WORLD ) - continue; + humanPlayer->client->ps.eFlags |= EF_POISONCLOUDED; + humanPlayer->client->lastPoisonCloudedTime = level.time; - target->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; - target->client->lastBoostedTime = MAX( target->client->lastBoostedTime, level.time - BOOST_TIME + LEVEL1_PCLOUD_BOOST_TIME ); + trap_SendServerCommand( humanPlayer->client->ps.clientNum, + "poisoncloud" ); } } G_UnlaggedOff( ); @@ -1080,72 +1012,60 @@ LEVEL2 ====================================================================== */ -#define MAX_ZAPS 64 - -static zap_t zaps[ MAX_CLIENTS ]; +zap_t zaps[ MAX_ZAPS ]; /* =============== -G_FindNewZapTarget +G_FindZapChainTargets =============== */ -static gentity_t *G_FindNewZapTarget( gentity_t *ent ) +static void G_FindZapChainTargets( zap_t *zap ) { + gentity_t *ent = zap->targets[ 0 ]; // the source int entityList[ MAX_GENTITIES ]; - vec3_t range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE }; + vec3_t range = { LEVEL2_AREAZAP_CHAIN_RANGE, + LEVEL2_AREAZAP_CHAIN_RANGE, + LEVEL2_AREAZAP_CHAIN_RANGE }; vec3_t mins, maxs; - int i, j, k, num; + int i, num; gentity_t *enemy; trace_t tr; + float distance; - VectorScale( range, 1.0f / M_ROOT3, range ); - VectorAdd( ent->s.origin, range, maxs ); - VectorSubtract( ent->s.origin, range, mins ); + VectorAdd( ent->r.currentOrigin, range, maxs ); + VectorSubtract( ent->r.currentOrigin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; - - if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || - ( enemy->s.eType == ET_BUILDABLE && - BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 ) + // don't chain to self; noclippers can be listed, don't chain to them either + if( enemy == ent || ( enemy->client && enemy->client->noclip ) ) + continue; + + distance = Distance( ent->r.currentOrigin, enemy->r.currentOrigin ); + + if( ( ( enemy->client && + enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || + ( enemy->s.eType == ET_BUILDABLE && + BG_Buildable( enemy->s.modelindex )->team == TEAM_HUMANS ) ) && + enemy->health > 0 && // only chain to living targets + distance <= LEVEL2_AREAZAP_CHAIN_RANGE ) { - qboolean foundOldTarget = qfalse; - - trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT ); - - //can't see target from here - if( tr.entityNum == ENTITYNUM_WORLD ) - continue; + // world-LOS check: trace against the world, ignoring other BODY entities + trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, + enemy->r.currentOrigin, ent->s.number, CONTENTS_SOLID ); - for( j = 0; j < MAX_ZAPS; j++ ) + if( tr.entityNum == ENTITYNUM_NONE ) { - zap_t *zap = &zaps[ j ]; - - for( k = 0; k < zap->numTargets; k++ ) - { - if( zap->targets[ k ] == enemy ) - { - foundOldTarget = qtrue; - break; - } - } - - if( foundOldTarget ) - break; + zap->targets[ zap->numTargets ] = enemy; + zap->distances[ zap->numTargets ] = distance; + if( ++zap->numTargets >= LEVEL2_AREAZAP_MAX_TARGETS ) + return; } - - // enemy is already targetted - if( foundOldTarget ) - continue; - - return enemy; } } - - return NULL; } /* @@ -1155,30 +1075,19 @@ G_UpdateZapEffect */ static void G_UpdateZapEffect( zap_t *zap ) { - int j; - gentity_t *effect = zap->effectChannel; - - effect->s.eType = ET_LEV2_ZAP_CHAIN; - effect->classname = "lev2zapchain"; - G_SetOrigin( effect, zap->creator->s.origin ); - effect->s.misc = zap->creator->s.number; + int i; + int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; - effect->s.time = effect->s.time2 = effect->s.constantLight = -1; + entityNums[ 0 ] = zap->creator->s.number; - for( j = 0; j < zap->numTargets; j++ ) - { - int number = zap->targets[ j ]->s.number; + for( i = 0; i < zap->numTargets; i++ ) + entityNums[ i + 1 ] = zap->targets[ i ]->s.number; - switch( j ) - { - case 0: effect->s.time = number; break; - case 1: effect->s.time2 = number; break; - case 2: effect->s.constantLight = number; break; - default: break; - } - } + BG_PackEntityNumbers( &zap->effectChannel->s, + entityNums, zap->numTargets + 1 ); - trap_LinkEntity( effect ); + VectorCopy( zap->creator->r.currentOrigin, zap->effectChannel->r.currentOrigin ); + trap_LinkEntity( zap->effectChannel ); } /* @@ -1188,38 +1097,48 @@ G_CreateNewZap */ static void G_CreateNewZap( gentity_t *creator, gentity_t *target ) { - int i, j; - zap_t *zap; + int i; + zap_t *zap; for( i = 0; i < MAX_ZAPS; i++ ) { zap = &zaps[ i ]; + if( zap->used ) + continue; - if( !zap->used ) - { - zap->used = qtrue; + zap->used = qtrue; + zap->timeToLive = LEVEL2_AREAZAP_TIME; - zap->timeToLive = LEVEL2_AREAZAP_TIME; - zap->damageUsed = 0; + zap->creator = creator; + zap->targets[ 0 ] = target; + zap->numTargets = 1; - zap->creator = creator; + // the zap chains only through living entities + if( target->health > 0 ) + { + G_Damage( target, creator, creator, forward, + target->r.currentOrigin, LEVEL2_AREAZAP_DMG, + DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, + MOD_LEVEL2_ZAP ); - zap->targets[ 0 ] = target; - zap->numTargets = 1; + G_FindZapChainTargets( zap ); - for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ ) + for( i = 1; i < zap->numTargets; i++ ) { - zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] ); - - if( zap->targets[ j ] ) - zap->numTargets++; + G_Damage( zap->targets[ i ], target, zap->creator, forward, target->r.currentOrigin, + LEVEL2_AREAZAP_DMG * ( 1 - pow( (zap->distances[ i ] / + LEVEL2_AREAZAP_CHAIN_RANGE ), LEVEL2_AREAZAP_CHAIN_FALLOFF ) ) + 1, + DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, + MOD_LEVEL2_ZAP ); } + } - zap->effectChannel = G_Spawn( ); - G_UpdateZapEffect( zap ); + zap->effectChannel = G_Spawn( ); + zap->effectChannel->s.eType = ET_LEV2_ZAP_CHAIN; + zap->effectChannel->classname = "lev2zapchain"; + G_UpdateZapEffect( zap ); - return; - } + return; } } @@ -1233,80 +1152,67 @@ void G_UpdateZaps( int msec ) { int i, j; zap_t *zap; - int damage; for( i = 0; i < MAX_ZAPS; i++ ) { zap = &zaps[ i ]; + if( !zap->used ) + continue; - if( zap->used ) + zap->timeToLive -= msec; + + // first, the disappearance of players is handled immediately in G_ClearPlayerZapEffects() + + // the deconstruction or gibbing of a directly targeted buildable destroys the whole zap effect + if( zap->timeToLive <= 0 || !zap->targets[ 0 ]->inuse ) { - //check each target is valid - for( j = 0; j < zap->numTargets; j++ ) - { - gentity_t *source; - gentity_t *target = zap->targets[ j ]; - - if( j == 0 ) - source = zap->creator; - else - source = zap->targets[ j - 1 ]; - - if( target->health <= 0 || !target->inuse || //early out - Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE ) - { - target = zap->targets[ j ] = G_FindNewZapTarget( source ); - - //couldn't find a target, so forget about the rest of the chain - if( !target ) - zap->numTargets = j; - } - } + G_FreeEntity( zap->effectChannel ); + zap->used = qfalse; + continue; + } - if( zap->numTargets ) - { - for( j = 0; j < zap->numTargets; j++ ) - { - gentity_t *source; - gentity_t *target = zap->targets[ j ]; - float r = 1.0f / zap->numTargets; - float damageFraction = 2 * r - 2 * j * r * r - r * r; - vec3_t forward; - - if( j == 0 ) - source = zap->creator; - else - source = zap->targets[ j - 1 ]; - - damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) * - LEVEL2_AREAZAP_DMG * damageFraction ); - - // don't let a high msec value inflate the total damage - if( damage + zap->damageUsed > LEVEL2_AREAZAP_DMG ) - damage = LEVEL2_AREAZAP_DMG - zap->damageUsed; - - VectorSubtract( target->s.origin, source->s.origin, forward ); - VectorNormalize( forward ); - - //do the damage - if( damage ) - { - G_Damage( target, source, zap->creator, forward, target->s.origin, - damage, DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP ); - zap->damageUsed += damage; - } - } - } + // the deconstruction or gibbing of chained buildables destroy the appropriate beams + for( j = 1; j < zap->numTargets; j++ ) + { + if( !zap->targets[ j ]->inuse ) + zap->targets[ j-- ] = zap->targets[ --zap->numTargets ]; + } - G_UpdateZapEffect( zap ); + G_UpdateZapEffect( zap ); + } +} - zap->timeToLive -= msec; +/* +=============== +G_ClearPlayerZapEffects - if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 ) - { - zap->used = qfalse; - G_FreeEntity( zap->effectChannel ); - } +called from G_LeaveTeam() and TeleportPlayer() +=============== +*/ +void G_ClearPlayerZapEffects( gentity_t *player ) +{ + int i, j; + zap_t *zap; + + for( i = 0; i < MAX_ZAPS; i++ ) + { + zap = &zaps[ i ]; + if( !zap->used ) + continue; + + // the disappearance of the creator or the first target destroys the whole zap effect + if( zap->creator == player || zap->targets[ 0 ] == player ) + { + G_FreeEntity( zap->effectChannel ); + zap->used = qfalse; + continue; + } + + // the disappearance of chained players destroy the appropriate beams + for( j = 1; j < zap->numTargets; j++ ) + { + if( zap->targets[ j ] == player ) + zap->targets[ j-- ] = zap->targets[ --zap->numTargets ]; } } } @@ -1319,32 +1225,16 @@ areaZapFire void areaZapFire( gentity_t *ent ) { trace_t tr; - vec3_t end; gentity_t *traceEnt; - vec3_t mins, maxs; - - VectorSet( mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH ); - VectorSet( maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH ); - - // set aiming directions - AngleVectors( ent->client->ps.viewangles, forward, right, up ); - - CalcMuzzlePoint( ent, forward, right, up, muzzle ); - VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end ); + G_WideTrace( &tr, ent, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, &traceEnt ); - G_UnlaggedOn( ent, muzzle, LEVEL2_AREAZAP_RANGE ); - trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); - G_UnlaggedOff( ); - - if( tr.surfaceFlags & SURF_NOIMPACT ) + if( traceEnt == NULL ) return; - traceEnt = &g_entities[ tr.entityNum ]; - - if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || + if( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || ( traceEnt->s.eType == ET_BUILDABLE && - BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 ) + BG_Buildable( traceEnt->s.modelindex )->team == TEAM_HUMANS ) ) { G_CreateNewZap( ent, traceEnt ); } @@ -1366,61 +1256,51 @@ CheckPounceAttack */ qboolean CheckPounceAttack( gentity_t *ent ) { - trace_t tr; - gentity_t *tent; + trace_t tr; gentity_t *traceEnt; - int damage; - - if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) - { - ent->client->allowedToPounce = qfalse; - ent->client->pmext.pouncePayload = 0; - } + int damage, timeMax, pounceRange, payload; - if( !ent->client->allowedToPounce ) + if( ent->client->pmext.pouncePayload <= 0 ) return qfalse; - if( ent->client->ps.weaponTime ) - return qfalse; + // In case the goon lands on his target, he gets one shot after landing + payload = ent->client->pmext.pouncePayload; + if( !( ent->client->ps.pm_flags & PMF_CHARGE ) ) + ent->client->pmext.pouncePayload = 0; - G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt ); + // Calculate muzzle point + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + // Trace from muzzle to see what we hit + pounceRange = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_RANGE : + LEVEL3_POUNCE_UPG_RANGE; + G_WideTrace( &tr, ent, pounceRange, LEVEL3_POUNCE_WIDTH, + LEVEL3_POUNCE_WIDTH, &traceEnt ); if( traceEnt == NULL ) return qfalse; - // send blood impact - if( traceEnt->takedamage && traceEnt->client ) - { - tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); - tent->s.otherEntityNum = traceEnt->s.number; - tent->s.eventParm = DirToByte( tr.plane.normal ); - tent->s.weapon = ent->s.weapon; - tent->s.generic1 = ent->s.generic1; //weaponMode - } + // Send blood impact + if( traceEnt->takedamage ) + WideBloodSpurt( ent, traceEnt, &tr ); if( !traceEnt->takedamage ) return qfalse; - - damage = (int)( ( (float)ent->client->pmext.pouncePayload - / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG ); - + + // Deal damage + timeMax = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : + LEVEL3_POUNCE_TIME_UPG; + damage = payload * LEVEL3_POUNCE_DMG / timeMax; ent->client->pmext.pouncePayload = 0; - G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, - DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE ); - - ent->client->allowedToPounce = qfalse; + DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE ); return qtrue; } void bounceBallFire( gentity_t *ent ) { - gentity_t *m; - - m = fire_bounceBall( ent, muzzle, forward ); - -// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + fire_bounceBall( ent, muzzle, forward ); } @@ -1434,39 +1314,96 @@ LEVEL4 /* =============== -ChargeAttack +G_ChargeAttack =============== */ -void ChargeAttack( gentity_t *ent, gentity_t *victim ) +void G_ChargeAttack( gentity_t *ent, gentity_t *victim ) { - gentity_t *tent; int damage; - vec3_t forward, normal; + int i; + vec3_t forward; - if( level.time < victim->chargeRepeat ) + if( ent->client->ps.stats[ STAT_MISC ] <= 0 || + !( ent->client->ps.stats[ STAT_STATE ] & SS_CHARGING ) || + ent->client->ps.weaponTime ) return; - victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT; - - VectorSubtract( victim->s.origin, ent->s.origin, forward ); + VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, forward ); VectorNormalize( forward ); - VectorNegate( forward, normal ); - if( victim->client ) + if( !victim->takedamage ) + return; + + // For buildables, track the last MAX_TRAMPLE_BUILDABLES_TRACKED buildables + // hit, and do not do damage if the current buildable is in that list + // in order to prevent dancing over stuff to kill it very quickly + if( !victim->client ) { - tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT ); - tent->s.otherEntityNum = victim->s.number; - tent->s.eventParm = DirToByte( normal ); - tent->s.weapon = ent->s.weapon; - tent->s.generic1 = ent->s.generic1; //weaponMode + for( i = 0; i < MAX_TRAMPLE_BUILDABLES_TRACKED; i++ ) + { + if( ent->client->trampleBuildablesHit[ i ] == victim - g_entities ) + return; + } + + ent->client->trampleBuildablesHit[ + ent->client->trampleBuildablesHitPos++ % MAX_TRAMPLE_BUILDABLES_TRACKED ] = + victim - g_entities; } - if( !victim->takedamage ) + WideBloodSpurt( ent, victim, NULL ); + + damage = LEVEL4_TRAMPLE_DMG * ent->client->ps.stats[ STAT_MISC ] / + LEVEL4_TRAMPLE_DURATION; + + G_Damage( victim, ent, ent, forward, victim->r.currentOrigin, damage, + DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_TRAMPLE ); + + ent->client->ps.weaponTime += LEVEL4_TRAMPLE_REPEAT; +} + +/* +=============== +G_CrushAttack + +Should only be called if there was an impact between a tyrant and another player +=============== +*/ +void G_CrushAttack( gentity_t *ent, gentity_t *victim ) +{ + vec3_t dir; + float jump; + int damage; + + if( !victim->takedamage || + ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] < + victim->r.currentOrigin[ 2 ] + victim->r.maxs[ 2 ] || + ( victim->client && + victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) ) return; - damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG ); + // Deal velocity based damage to target + jump = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->jumpMagnitude; + damage = ( ent->client->pmext.fallVelocity + jump ) * + -LEVEL4_CRUSH_DAMAGE_PER_V; + + if( damage < 0 ) + damage = 0; + + // Players also get damaged periodically + if( victim->client && + ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time ) + { + ent->client->lastCrushTime = level.time; + damage += LEVEL4_CRUSH_DAMAGE; + } + + if( damage < 1 ) + return; - G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE ); + // Crush the victim over a period of time + VectorSubtract( victim->r.currentOrigin, ent->client->ps.origin, dir ); + G_Damage( victim, ent, ent, dir, victim->r.currentOrigin, damage, + DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH ); } //====================================================================== @@ -1480,10 +1417,12 @@ set muzzle location relative to pivoting eye */ void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { - VectorCopy( ent->s.pos.trBase, muzzlePoint ); - muzzlePoint[ 2 ] += ent->client->ps.viewheight; + vec3_t normal; + + VectorCopy( ent->client->ps.origin, muzzlePoint ); + BG_GetClientNormal( &ent->client->ps, normal ); + VectorMA( muzzlePoint, ent->client->ps.viewheight, normal, muzzlePoint ); VectorMA( muzzlePoint, 1, forward, muzzlePoint ); - VectorMA( muzzlePoint, 1, right, muzzlePoint ); // snap to integer coordinates for more efficient network bandwidth usage SnapVector( muzzlePoint ); } @@ -1548,18 +1487,18 @@ void FireWeapon2( gentity_t *ent ) case WP_ALEVEL1_UPG: poisonCloud( ent ); break; - case WP_ALEVEL2_UPG: - areaZapFire( ent ); - break; case WP_LUCIFER_CANNON: LCChargeFire( ent, qtrue ); break; + case WP_ALEVEL2_UPG: + areaZapFire( ent ); + break; + case WP_ABUILD: case WP_ABUILD2: case WP_HBUILD: - case WP_HBUILD2: cancelBuildFire( ent ); break; default: @@ -1574,13 +1513,11 @@ FireWeapon */ void FireWeapon( gentity_t *ent ) { - if( level.paused ) return; - if( ent->client ) { // set aiming directions AngleVectors( ent->client->ps.viewangles, forward, right, up ); - CalcMuzzlePoint( ent, forward, right, up, muzzle ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); } else { @@ -1592,21 +1529,32 @@ void FireWeapon( gentity_t *ent ) switch( ent->s.weapon ) { case WP_ALEVEL1: + meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH, + LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); + break; case WP_ALEVEL1_UPG: - meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); + meleeAttack( ent, LEVEL1_CLAW_U_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH, + LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); break; case WP_ALEVEL3: + meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_WIDTH, + LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); + break; case WP_ALEVEL3_UPG: - meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); + meleeAttack( ent, LEVEL3_CLAW_UPG_RANGE, LEVEL3_CLAW_WIDTH, + LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); break; case WP_ALEVEL2: - meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); + meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH, + LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); break; case WP_ALEVEL2_UPG: - meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); + meleeAttack( ent, LEVEL2_CLAW_U_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH, + LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); break; case WP_ALEVEL4: - meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW ); + meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, + LEVEL4_CLAW_HEIGHT, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW ); break; case WP_BLASTER: @@ -1661,11 +1609,9 @@ void FireWeapon( gentity_t *ent ) buildFire( ent, MN_A_BUILD ); break; case WP_HBUILD: - case WP_HBUILD2: buildFire( ent, MN_H_BUILD ); break; default: break; } } - diff --git a/src/game/g_weapondrop.c b/src/game/g_weapondrop.c new file mode 100644 index 0000000..11884ee --- /dev/null +++ b/src/game/g_weapondrop.c @@ -0,0 +1,199 @@ +// +// Ported + rewritten ioq3 item-drop. +// +// blowFish +// +#include "g_local.h" + +#define DISABLE_TOUCH_TIME 1000 +#define MISSILE_PRESTEP_TIME 50 + +// +// Pickup Weapon +// +// ent - The "weapon" being picked up +// other - The client who picked it up +// +void Pickup_Weapon (gentity_t *ent, gentity_t *other) +{ + int w = ent->s.modelindex; + + if ( w == WP_NONE ) + return; + + other->client->ps.stats[ STAT_WEAPON ] = w; + other->client->ps.ammo = ent->item.ammo; + other->client->ps.clips = ent->item.clips; + G_ForceWeaponChange( other, w ); +} + +// +// Touch Weapon +// +// ent - The "weapon" being picked up +// other - The client who picked it up +// +void Touch_Weapon (gentity_t *ent, gentity_t *other, trace_t *trace) +{ + if( !other->client + || other->client->pers.teamSelection == TEAM_NONE + || other->client->pers.teamSelection == TEAM_ALIENS ) + return; + + if( (other->client->lastDropTime + DISABLE_TOUCH_TIME) > level.time) + return; + + if ( other->health < 1 ) + return; + + Pickup_Weapon(ent, other); + + // dropped items will not respawn + if ( ent->flags & FL_DROPPED_ITEM ) + { + ent->freeAfterEvent = qtrue; + } + + ent->r.svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + + trap_LinkEntity( ent ); +} + +#define ITEM_RADIUS 15 + +// +// Launch Weapon +// +// Spawn a weapon and toss it into the world. +// +gentity_t *LaunchWeapon (gentity_t* client, weapon_t weap, vec3_t origin, vec3_t velocity) +{ + gentity_t *dropped; + + dropped = G_Spawn(); + + dropped->s.eType = ET_WEAPON_DROP; + dropped->s.modelindex = weap; // store weapon number in modelindex + dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item + + dropped->classname = BG_Weapon(weap)->name; + VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); + VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); + dropped->r.contents = CONTENTS_TRIGGER; + + dropped->item.ammo = client->client->ps.ammo; + dropped->item.clips = client->client->ps.clips; + + dropped->touch = Touch_Weapon; + + G_SetOrigin( dropped, origin ); + dropped->s.pos.trType = TR_GRAVITY; + dropped->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; + VectorCopy( velocity, dropped->s.pos.trDelta ); + + dropped->s.eFlags |= EF_BOUNCE_HALF; + dropped->think = G_FreeEntity; + dropped->nextthink = level.time + 30000; + + dropped->flags = FL_DROPPED_ITEM; + + trap_LinkEntity (dropped); + + return dropped; +} + +// +// Drop Weapon +// +// Spawns an weapon and tosses it forward +// +gentity_t *G_DropWeapon (gentity_t *ent, weapon_t w, float angle) +{ + vec3_t velocity; + vec3_t angles; + + // set aiming directions + VectorCopy( ent->s.apos.trBase, angles ); + angles[YAW] += angle; + angles[PITCH] = 0; // always forward + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 150, velocity ); + velocity[2] += 200 + crandom() * 50; + + ent->client->lastDropTime = level.time; + return LaunchWeapon( ent, w, ent->s.pos.trBase, velocity ); +} + +// +// Run Weapon Drops +// +void G_RunWeaponDrop (gentity_t *ent) +{ + vec3_t origin; + trace_t tr; + int contents; + int mask; + + // if its groundentity has been set to none, it may have been pushed off an edge + if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) + { + if ( ent->s.pos.trType != TR_GRAVITY ) + { + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + } + } + + if ( ent->s.pos.trType == TR_STATIONARY ) + { + // check think function + G_RunThink( ent ); + return; + } + + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + // trace a line from the previous position to the current position + if ( ent->clipmask ) + { + mask = ent->clipmask; + } + else + { + mask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, + ent->r.ownerNum, mask ); + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if ( tr.startsolid ) + { + tr.fraction = 0; + } + + trap_LinkEntity( ent ); // FIXME: avoid this for stationary? + + // check think function + G_RunThink( ent ); + + if ( tr.fraction == 1 ) + { + return; + } + + // if it is in a nodrop volume, remove it + contents = trap_PointContents( ent->r.currentOrigin, -1 ); + if ( contents & CONTENTS_NODROP ) + { + G_FreeEntity( ent ); + return; + } + + G_BounceMissile( ent, &tr ); +} diff --git a/src/game/tremulous.h b/src/game/tremulous.h index 423d465..f5f5e16 100644 --- a/src/game/tremulous.h +++ b/src/game/tremulous.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,11 +17,13 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ +#ifndef _TREMULOUS_H_ +#define _TREMULOUS_H_ /* * ALIEN weapons @@ -41,86 +44,101 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ABUILDER_CLAW_WIDTH 4.0f #define ABUILDER_CLAW_REPEAT 1000 #define ABUILDER_CLAW_K_SCALE 1.0f -#define ABUILDER_BASE_DELAY 17000 -#define ABUILDER_ADV_DELAY 12000 #define ABUILDER_BLOB_DMG ADM(4) #define ABUILDER_BLOB_REPEAT 1000 #define ABUILDER_BLOB_SPEED 800.0f #define ABUILDER_BLOB_SPEED_MOD 0.5f -#define ABUILDER_BLOB_TIME 5000 +#define ABUILDER_BLOB_TIME 2000 -#define LEVEL0_BITE_DMG ADM(48) +#define LEVEL0_BITE_DMG ADM(36) #define LEVEL0_BITE_RANGE 64.0f #define LEVEL0_BITE_WIDTH 6.0f #define LEVEL0_BITE_REPEAT 500 #define LEVEL0_BITE_K_SCALE 1.0f #define LEVEL1_CLAW_DMG ADM(32) -#define LEVEL1_CLAW_RANGE 96.0f +#define LEVEL1_CLAW_RANGE 64.0f +#define LEVEL1_CLAW_U_RANGE LEVEL1_CLAW_RANGE + 3.0f #define LEVEL1_CLAW_WIDTH 10.0f #define LEVEL1_CLAW_REPEAT 600 #define LEVEL1_CLAW_U_REPEAT 500 #define LEVEL1_CLAW_K_SCALE 1.0f #define LEVEL1_CLAW_U_K_SCALE 1.0f -#define LEVEL1_GRAB_RANGE 64.0f +#define LEVEL1_GRAB_RANGE 96.0f +#define LEVEL1_GRAB_U_RANGE LEVEL1_GRAB_RANGE + 3.0f #define LEVEL1_GRAB_TIME 300 -#define LEVEL1_GRAB_U_TIME 450 +#define LEVEL1_GRAB_U_TIME 300 #define LEVEL1_PCLOUD_DMG ADM(4) -#define LEVEL1_PCLOUD_RANGE 200.0f +#define LEVEL1_PCLOUD_RANGE 120.0f #define LEVEL1_PCLOUD_REPEAT 2000 #define LEVEL1_PCLOUD_TIME 10000 -#define LEVEL1_PCLOUD_BOOST_TIME 5000 -#define LEVEL1_REGEN_RANGE 200.0f #define LEVEL1_REGEN_MOD 2.0f +#define LEVEL1_UPG_REGEN_MOD 3.0f +#define LEVEL1_REGEN_SCOREINC AVM(100) // score added for healing per 10s +#define LEVEL1_UPG_REGEN_SCOREINC AVM(200) #define LEVEL2_CLAW_DMG ADM(40) -#define LEVEL2_CLAW_RANGE 96.0f -#define LEVEL2_CLAW_WIDTH 12.0f +#define LEVEL2_CLAW_RANGE 80.0f +#define LEVEL2_CLAW_U_RANGE LEVEL2_CLAW_RANGE + 2.0f +#define LEVEL2_CLAW_WIDTH 14.0f #define LEVEL2_CLAW_REPEAT 500 #define LEVEL2_CLAW_K_SCALE 1.0f #define LEVEL2_CLAW_U_REPEAT 400 #define LEVEL2_CLAW_U_K_SCALE 1.0f -#define LEVEL2_AREAZAP_DMG ADM(80) +#define LEVEL2_AREAZAP_DMG ADM(60) #define LEVEL2_AREAZAP_RANGE 200.0f +#define LEVEL2_AREAZAP_CHAIN_RANGE 150.0f +#define LEVEL2_AREAZAP_CHAIN_FALLOFF 8.0f #define LEVEL2_AREAZAP_WIDTH 15.0f #define LEVEL2_AREAZAP_REPEAT 1500 #define LEVEL2_AREAZAP_TIME 1000 -#define LEVEL2_AREAZAP_MAX_TARGETS 3 +#define LEVEL2_AREAZAP_MAX_TARGETS 5 #define LEVEL2_WALLJUMP_MAXSPEED 1000.0f #define LEVEL3_CLAW_DMG ADM(80) -#define LEVEL3_CLAW_RANGE 96.0f -#define LEVEL3_CLAW_WIDTH 16.0f -#define LEVEL3_CLAW_REPEAT 700 +#define LEVEL3_CLAW_RANGE 80.0f +#define LEVEL3_CLAW_UPG_RANGE LEVEL3_CLAW_RANGE + 3.0f +#define LEVEL3_CLAW_WIDTH 12.0f +#define LEVEL3_CLAW_REPEAT 900 #define LEVEL3_CLAW_K_SCALE 1.0f -#define LEVEL3_CLAW_U_REPEAT 600 +#define LEVEL3_CLAW_U_REPEAT 800 #define LEVEL3_CLAW_U_K_SCALE 1.0f #define LEVEL3_POUNCE_DMG ADM(100) -#define LEVEL3_POUNCE_RANGE 72.0f -#define LEVEL3_POUNCE_WIDTH 16.0f -#define LEVEL3_POUNCE_SPEED 700 -#define LEVEL3_POUNCE_UPG_SPEED 800 -#define LEVEL3_POUNCE_SPEED_MOD 0.75f -#define LEVEL3_POUNCE_CHARGE_TIME 700 -#define LEVEL3_POUNCE_TIME 400 +#define LEVEL3_POUNCE_RANGE 48.0f +#define LEVEL3_POUNCE_UPG_RANGE LEVEL3_POUNCE_RANGE + 3.0f +#define LEVEL3_POUNCE_WIDTH 14.0f +#define LEVEL3_POUNCE_TIME 800 // msec for full Dragoon pounce +#define LEVEL3_POUNCE_TIME_UPG 800 // msec for full Adv. Dragoon pounce +#define LEVEL3_POUNCE_TIME_MIN 200 // msec before which pounce cancels +#define LEVEL3_POUNCE_REPEAT 400 // msec before a new pounce starts +#define LEVEL3_POUNCE_SPEED_MOD 0.75f // walking speed modifier for pounce charging +#define LEVEL3_POUNCE_JUMP_MAG 700 // Dragoon pounce jump power +#define LEVEL3_POUNCE_JUMP_MAG_UPG 800 // Adv. Dragoon pounce jump power #define LEVEL3_BOUNCEBALL_DMG ADM(110) -#define LEVEL3_BOUNCEBALL_REPEAT 1000 +#define LEVEL3_BOUNCEBALL_REPEAT 1200 #define LEVEL3_BOUNCEBALL_SPEED 1000.0f +#define LEVEL3_BOUNCEBALL_RADIUS 75 +#define LEVEL3_BOUNCEBALL_REGEN 15000 // msec until new barb #define LEVEL4_CLAW_DMG ADM(100) -#define LEVEL4_CLAW_RANGE 128.0f -#define LEVEL4_CLAW_WIDTH 20.0f -#define LEVEL4_CLAW_REPEAT 750 +#define LEVEL4_CLAW_RANGE 100.0f +#define LEVEL4_CLAW_WIDTH 14.0f +#define LEVEL4_CLAW_HEIGHT 20.0f +#define LEVEL4_CLAW_REPEAT 800 #define LEVEL4_CLAW_K_SCALE 1.0f -#define LEVEL4_CHARGE_SPEED 2.0f -#define LEVEL4_CHARGE_TIME 3000 -#define LEVEL4_CHARGE_CHARGE_TIME 1500 -#define LEVEL4_MIN_CHARGE_TIME 750 -#define LEVEL4_CHARGE_CHARGE_RATIO (LEVEL4_CHARGE_TIME/LEVEL4_CHARGE_CHARGE_TIME) -#define LEVEL4_CHARGE_REPEAT 1000 -#define LEVEL4_CHARGE_DMG ADM(110) +#define LEVEL4_TRAMPLE_DMG ADM(111) +#define LEVEL4_TRAMPLE_SPEED 2.0f +#define LEVEL4_TRAMPLE_CHARGE_MIN 375 // minimum msec to start a charge +#define LEVEL4_TRAMPLE_CHARGE_MAX 1000 // msec to maximum charge stored +#define LEVEL4_TRAMPLE_CHARGE_TRIGGER 3000 // msec charge starts on its own +#define LEVEL4_TRAMPLE_DURATION 3000 // msec trample lasts on full charge +#define LEVEL4_TRAMPLE_STOP_PENALTY 1 // charge lost per msec when stopped +#define LEVEL4_TRAMPLE_REPEAT 100 // msec before a trample will rehit a player +#define LEVEL4_CRUSH_DAMAGE_PER_V 0.5f // damage per falling velocity +#define LEVEL4_CRUSH_DAMAGE 120 // to players only +#define LEVEL4_CRUSH_REPEAT 500 // player damage repeat /* * ALIEN classes @@ -138,68 +156,66 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ALIEN_VALUE_MODIFIER 1.0f #define AVM(h) ((int)((float)h*ALIEN_VALUE_MODIFIER)) -#define ABUILDER_SPEED 0.8f -#define ABUILDER_VALUE AVM(200) +#define ABUILDER_SPEED 0.9f +#define ABUILDER_VALUE AVM(240) #define ABUILDER_HEALTH AHM(50) -#define ABUILDER_REGEN 2 +#define ABUILDER_REGEN (0.04f * ABUILDER_HEALTH) #define ABUILDER_COST 0 -#define ABUILDER_UPG_SPEED 1.0f -#define ABUILDER_UPG_VALUE AVM(250) +#define ABUILDER_UPG_SPEED 0.9f +#define ABUILDER_UPG_VALUE AVM(300) #define ABUILDER_UPG_HEALTH AHM(75) -#define ABUILDER_UPG_REGEN 3 +#define ABUILDER_UPG_REGEN (0.04f * ABUILDER_UPG_HEALTH) #define ABUILDER_UPG_COST 0 -#define LEVEL0_SPEED 1.3f -#define LEVEL0_VALUE AVM(175) +#define LEVEL0_SPEED 1.4f +#define LEVEL0_VALUE AVM(180) #define LEVEL0_HEALTH AHM(25) -#define LEVEL0_REGEN 1 +#define LEVEL0_REGEN (0.05f * LEVEL0_HEALTH) #define LEVEL0_COST 0 #define LEVEL1_SPEED 1.25f -#define LEVEL1_VALUE AVM(225) -#define LEVEL1_HEALTH AHM(75) -#define LEVEL1_REGEN 2 +#define LEVEL1_VALUE AVM(270) +#define LEVEL1_HEALTH AHM(60) +#define LEVEL1_REGEN (0.03f * LEVEL1_HEALTH) #define LEVEL1_COST 1 #define LEVEL1_UPG_SPEED 1.25f -#define LEVEL1_UPG_VALUE AVM(275) -#define LEVEL1_UPG_HEALTH AHM(100) -#define LEVEL1_UPG_REGEN 3 +#define LEVEL1_UPG_VALUE AVM(330) +#define LEVEL1_UPG_HEALTH AHM(80) +#define LEVEL1_UPG_REGEN (0.03f * LEVEL1_UPG_HEALTH) #define LEVEL1_UPG_COST 1 #define LEVEL2_SPEED 1.2f -#define LEVEL2_VALUE AVM(350) +#define LEVEL2_VALUE AVM(420) #define LEVEL2_HEALTH AHM(150) -#define LEVEL2_REGEN 4 +#define LEVEL2_REGEN (0.03f * LEVEL2_HEALTH) #define LEVEL2_COST 1 #define LEVEL2_UPG_SPEED 1.2f -#define LEVEL2_UPG_VALUE AVM(450) +#define LEVEL2_UPG_VALUE AVM(540) #define LEVEL2_UPG_HEALTH AHM(175) -#define LEVEL2_UPG_REGEN 5 +#define LEVEL2_UPG_REGEN (0.03f * LEVEL2_UPG_HEALTH) #define LEVEL2_UPG_COST 1 #define LEVEL3_SPEED 1.1f -#define LEVEL3_VALUE AVM(500) +#define LEVEL3_VALUE AVM(600) #define LEVEL3_HEALTH AHM(200) -#define LEVEL3_REGEN 6 +#define LEVEL3_REGEN (0.03f * LEVEL3_HEALTH) #define LEVEL3_COST 1 #define LEVEL3_UPG_SPEED 1.1f -#define LEVEL3_UPG_VALUE AVM(600) +#define LEVEL3_UPG_VALUE AVM(720) #define LEVEL3_UPG_HEALTH AHM(250) -#define LEVEL3_UPG_REGEN 7 +#define LEVEL3_UPG_REGEN (0.03f * LEVEL3_UPG_HEALTH) #define LEVEL3_UPG_COST 1 #define LEVEL4_SPEED 1.2f -#define LEVEL4_VALUE AVM(800) -#define LEVEL4_HEALTH AHM(400) -#define LEVEL4_REGEN 7 +#define LEVEL4_VALUE AVM(960) +#define LEVEL4_HEALTH AHM(350) +#define LEVEL4_REGEN (0.025f * LEVEL4_HEALTH) #define LEVEL4_COST 2 - - /* * ALIEN buildables * @@ -216,6 +232,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ALIEN_BHLTH_MODIFIER 1.0f #define ABHM(h) ((int)((float)h*ALIEN_BHLTH_MODIFIER)) +#define ALIEN_BVALUE_MODIFIER 90.0f +#define ABVM(h) ((int)((float)h*ALIEN_BVALUE_MODIFIER)) #define CREEP_BASESIZE 700 #define CREEP_TIMEOUT 1000 @@ -223,44 +241,53 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define CREEP_ARMOUR_MODIFIER 0.75f #define CREEP_SCALEDOWN_TIME 3000 +#define PCLOUD_MODIFIER 0.5f +#define PCLOUD_ARMOUR_MODIFIER 0.75f + #define ASPAWN_BP 10 #define ASPAWN_BT 15000 #define ASPAWN_HEALTH ABHM(250) #define ASPAWN_REGEN 8 #define ASPAWN_SPLASHDAMAGE 50 -#define ASPAWN_SPLASHRADIUS 50 +#define ASPAWN_SPLASHRADIUS 100 #define ASPAWN_CREEPSIZE 120 -#define ASPAWN_VALUE 150 +#define ASPAWN_VALUE ABVM(ASPAWN_BP) -#define BARRICADE_BP 10 +#define BARRICADE_BP 8 #define BARRICADE_BT 20000 -#define BARRICADE_HEALTH ABHM(200) +#define BARRICADE_HEALTH ABHM(300) #define BARRICADE_REGEN 14 #define BARRICADE_SPLASHDAMAGE 50 -#define BARRICADE_SPLASHRADIUS 50 +#define BARRICADE_SPLASHRADIUS 100 #define BARRICADE_CREEPSIZE 120 +#define BARRICADE_SHRINKPROP 0.25f +#define BARRICADE_SHRINKTIMEOUT 500 +#define BARRICADE_VALUE ABVM(BARRICADE_BP) #define BOOSTER_BP 12 #define BOOSTER_BT 15000 #define BOOSTER_HEALTH ABHM(150) #define BOOSTER_REGEN 8 #define BOOSTER_SPLASHDAMAGE 50 -#define BOOSTER_SPLASHRADIUS 50 +#define BOOSTER_SPLASHRADIUS 100 #define BOOSTER_CREEPSIZE 120 -#define BOOSTER_INTERVAL 30000 //time in msec between uses (per player) -#define BOOSTER_REGEN_MOD 2.0f -#define BOOST_TIME 30000 +#define BOOSTER_REGEN_MOD 3.0f +#define BOOSTER_VALUE ABVM(BOOSTER_BP) +#define BOOST_TIME 20000 +#define BOOST_WARN_TIME 15000 #define ACIDTUBE_BP 8 #define ACIDTUBE_BT 15000 #define ACIDTUBE_HEALTH ABHM(125) #define ACIDTUBE_REGEN 10 -#define ACIDTUBE_SPLASHDAMAGE 6 -#define ACIDTUBE_SPLASHRADIUS 300 +#define ACIDTUBE_SPLASHDAMAGE 50 +#define ACIDTUBE_SPLASHRADIUS 100 #define ACIDTUBE_CREEPSIZE 120 +#define ACIDTUBE_DAMAGE 8 #define ACIDTUBE_RANGE 300.0f -#define ACIDTUBE_REPEAT 3000 -#define ACIDTUBE_K_SCALE 1.0f +#define ACIDTUBE_REPEAT 300 +#define ACIDTUBE_REPEAT_ANIM 2000 +#define ACIDTUBE_VALUE ABVM(ACIDTUBE_BP) #define HIVE_BP 12 #define HIVE_BT 20000 @@ -269,12 +296,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HIVE_SPLASHDAMAGE 30 #define HIVE_SPLASHRADIUS 200 #define HIVE_CREEPSIZE 120 -#define HIVE_RANGE 400.0f -#define HIVE_REPEAT 5000 +#define HIVE_SENSE_RANGE 500.0f +#define HIVE_LIFETIME 3000 +#define HIVE_REPEAT 3000 #define HIVE_K_SCALE 1.0f -#define HIVE_DMG 50 -#define HIVE_SPEED 240.0f +#define HIVE_DMG 80 +#define HIVE_SPEED 320.0f #define HIVE_DIR_CHANGE_PERIOD 500 +#define HIVE_VALUE ABVM(HIVE_BP) #define TRAPPER_BP 8 #define TRAPPER_BT 12000 @@ -285,7 +314,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TRAPPER_CREEPSIZE 30 #define TRAPPER_RANGE 400 #define TRAPPER_REPEAT 1000 -#define TRAPPER_K_SCALE 1.0f +#define TRAPPER_VALUE ABVM(TRAPPER_BP) #define LOCKBLOB_SPEED 650.0f #define LOCKBLOB_LOCKTIME 5000 #define LOCKBLOB_DOT 0.85f // max angle = acos( LOCKBLOB_DOT ) @@ -300,17 +329,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define OVERMIND_CREEPSIZE 120 #define OVERMIND_ATTACK_RANGE 150.0f #define OVERMIND_ATTACK_REPEAT 1000 -#define OVERMIND_VALUE 300 - -#define HOVEL_BP 0 -#define HOVEL_BT 15000 -#define HOVEL_HEALTH ABHM(375) -#define HOVEL_REGEN 20 -#define HOVEL_SPLASHDAMAGE 20 -#define HOVEL_SPLASHRADIUS 200 -#define HOVEL_CREEPSIZE 120 - - +#define OVERMIND_VALUE ABVM(30) /* * ALIEN misc @@ -320,14 +339,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define ALIENSENSE_RANGE 1000.0f +#define REGEN_BOOST_RANGE 200.0f -#define ALIEN_POISON_TIME 5000 +#define ALIEN_POISON_TIME 10000 #define ALIEN_POISON_DMG 5 #define ALIEN_POISON_DIVIDER (1.0f/1.32f) //about 1.0/(time`th root of damage) #define ALIEN_SPAWN_REPEAT_TIME 10000 #define ALIEN_REGEN_DAMAGE_TIME 2000 //msec since damage that regen starts again +#define ALIEN_REGEN_NOCREEP_MOD (1.0f/3.0f) //regen off creep + +#define ALIEN_MAX_FRAGS 9 +#define ALIEN_MAX_CREDITS (ALIEN_MAX_FRAGS*ALIEN_CREDITS_PER_KILL) +#define ALIEN_CREDITS_PER_KILL 400 +#define ALIEN_TK_SUICIDE_PENALTY 350 /* * HUMAN weapons @@ -347,7 +373,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define BLASTER_K_SCALE 1.0f #define BLASTER_SPREAD 200 #define BLASTER_SPEED 1400 -#define BLASTER_DMG HDM(9) +#define BLASTER_DMG HDM(10) +#define BLASTER_SIZE 5 #define RIFLE_CLIPSIZE 30 #define RIFLE_MAXCLIPS 6 @@ -361,8 +388,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define PAINSAW_PRICE 100 #define PAINSAW_REPEAT 75 #define PAINSAW_K_SCALE 1.0f -#define PAINSAW_DAMAGE HDM(15) -#define PAINSAW_RANGE 40.0f +#define PAINSAW_DAMAGE HDM(11) +#define PAINSAW_RANGE 64.0f +#define PAINSAW_WIDTH 0.0f +#define PAINSAW_HEIGHT 8.0f #define GRENADE_PRICE 200 #define GRENADE_REPEAT 0 @@ -373,13 +402,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define SHOTGUN_PRICE 150 #define SHOTGUN_SHELLS 8 -#define SHOTGUN_PELLETS 8 //used to sync server and client side +#define SHOTGUN_PELLETS 11 //used to sync server and client side #define SHOTGUN_MAXCLIPS 3 #define SHOTGUN_REPEAT 1000 #define SHOTGUN_K_SCALE 1.0f #define SHOTGUN_RELOAD 2000 -#define SHOTGUN_SPREAD 900 -#define SHOTGUN_DMG HDM(7) +#define SHOTGUN_SPREAD 700 +#define SHOTGUN_DMG HDM(5) +#define SHOTGUN_RANGE (8192 * 12) #define LASGUN_PRICE 250 #define LASGUN_AMMO 200 @@ -391,7 +421,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MDRIVER_PRICE 350 #define MDRIVER_CLIPSIZE 5 #define MDRIVER_MAXCLIPS 4 -#define MDRIVER_DMG HDM(38) +#define MDRIVER_DMG HDM(40) #define MDRIVER_REPEAT 1000 #define MDRIVER_K_SCALE 1.0f #define MDRIVER_RELOAD 2000 @@ -400,64 +430,66 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define CHAINGUN_BULLETS 300 #define CHAINGUN_REPEAT 80 #define CHAINGUN_K_SCALE 1.0f -#define CHAINGUN_SPREAD 1000 +#define CHAINGUN_SPREAD 900 #define CHAINGUN_DMG HDM(6) -#define PRIFLE_PRICE 400 -#define PRIFLE_CLIPS 50 -#define PRIFLE_MAXCLIPS 4 +#define FLAMER_PRICE 400 +#define FLAMER_GAS 200 +#define FLAMER_REPEAT 200 +#define FLAMER_K_SCALE 2.0f +#define FLAMER_DMG HDM(20) +#define FLAMER_SPLASHDAMAGE HDM(10) +#define FLAMER_RADIUS 50 // splash radius +#define FLAMER_SIZE 15 // missile bounding box +#define FLAMER_LIFETIME 700.0f +#define FLAMER_SPEED 500.0f +#define FLAMER_LAG 0.65f // the amount of player velocity that is added to the fireball + +#define PRIFLE_PRICE 450 +#define PRIFLE_CLIPS 40 +#define PRIFLE_MAXCLIPS 5 #define PRIFLE_REPEAT 100 #define PRIFLE_K_SCALE 1.0f #define PRIFLE_RELOAD 2000 #define PRIFLE_DMG HDM(9) -#define PRIFLE_SPEED 1000 - -#define FLAMER_PRICE 450 -#define FLAMER_GAS 150 -#define FLAMER_REPEAT 200 -#define FLAMER_K_SCALE 1.0f -#define FLAMER_DMG HDM(20) -#define FLAMER_RADIUS 50 -#define FLAMER_LIFETIME 800.0f -#define FLAMER_SPEED 200.0f -#define FLAMER_LAG 0.65f //the amount of player velocity that is added to the fireball +#define PRIFLE_SPEED 1200 +#define PRIFLE_SIZE 5 #define LCANNON_PRICE 600 -#define LCANNON_AMMO 90 -#define LCANNON_REPEAT 500 +#define LCANNON_AMMO 80 #define LCANNON_K_SCALE 1.0f -#define LCANNON_CHARGEREPEAT 1000 -#define LCANNON_RELOAD 2000 +#define LCANNON_REPEAT 500 +#define LCANNON_RELOAD 0 #define LCANNON_DAMAGE HDM(265) -#define LCANNON_RADIUS 150 -#define LCANNON_SECONDARY_DAMAGE HDM(27) -#define LCANNON_SECONDARY_RADIUS 75 -#define LCANNON_SPEED 350 -#define LCANNON_CHARGE_TIME 2000 -#define LCANNON_TOTAL_CHARGE 255 -#define LCANNON_MIN_CHARGE 50 +#define LCANNON_RADIUS 150 // primary splash damage radius +#define LCANNON_SIZE 5 // missile bounding box radius +#define LCANNON_SECONDARY_DAMAGE HDM(30) +#define LCANNON_SECONDARY_RADIUS 75 // secondary splash damage radius +#define LCANNON_SECONDARY_SPEED 1400 +#define LCANNON_SECONDARY_RELOAD 2000 +#define LCANNON_SECONDARY_REPEAT 1000 +#define LCANNON_SPEED 700 +#define LCANNON_CHARGE_TIME_MAX 3000 +#define LCANNON_CHARGE_TIME_MIN 100 +#define LCANNON_CHARGE_TIME_WARN 2000 +#define LCANNON_CHARGE_AMMO 10 // ammo cost of a full charge shot #define HBUILD_PRICE 0 #define HBUILD_REPEAT 1000 -#define HBUILD_DELAY 17500 #define HBUILD_HEALRATE 18 -#define HBUILD2_PRICE 0 -#define HBUILD2_REPEAT 1000 -#define HBUILD2_DELAY 15000 - - - /* * HUMAN upgrades */ #define LIGHTARMOUR_PRICE 70 #define LIGHTARMOUR_POISON_PROTECTION 1 +#define LIGHTARMOUR_PCLOUD_PROTECTION 1000 #define HELMET_PRICE 90 #define HELMET_RANGE 1000.0f -#define HELMET_POISON_PROTECTION 2 +#define HELMET_POISON_PROTECTION 1 +#define HELMET_PCLOUD_PROTECTION 1000 #define MEDKIT_PRICE 0 @@ -471,19 +503,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define JETPACK_DISABLE_CHANCE 0.3f #define BSUIT_PRICE 400 -#define BSUIT_POISON_PROTECTION 4 - -#define MGCLIP_PRICE 0 - -#define CGAMMO_PRICE 0 - -#define GAS_PRICE 0 +#define BSUIT_POISON_PROTECTION 3 +#define BSUIT_PCLOUD_PROTECTION 3000 #define MEDKIT_POISON_IMMUNITY_TIME 0 #define MEDKIT_STARTUP_TIME 4000 #define MEDKIT_STARTUP_SPEED 5 - /* * HUMAN buildables * @@ -492,7 +518,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * _SPLASHDAMGE - the amount of damage caused by this buildable when it blows up * _SPLASHRADIUS - the radius around which it does this damage * - * REACTOR_BASESIZE - the maximum distance a buildable can be from an reactor + * REACTOR_BASESIZE - the maximum distance a buildable can be from a reactor * REPEATER_BASESIZE - the maximum distance a buildable can be from a repeater * HUMAN_BHLTH_MODIFIER - overall health modifier for coarse tuning * @@ -500,6 +526,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HUMAN_BHLTH_MODIFIER 1.0f #define HBHM(h) ((int)((float)h*HUMAN_BHLTH_MODIFIER)) +#define HUMAN_BVALUE_MODIFIER 240.0f +#define HBVM(h) ((int)((float)h*(float)HUMAN_BVALUE_MODIFIER)) // remember these are measured in credits not frags (c.f. ALIEN_CREDITS_PER_KILL) #define REACTOR_BASESIZE 1000 #define REPEATER_BASESIZE 500 @@ -510,31 +538,30 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HSPAWN_HEALTH HBHM(310) #define HSPAWN_SPLASHDAMAGE 50 #define HSPAWN_SPLASHRADIUS 100 -#define HSPAWN_VALUE 1 +#define HSPAWN_VALUE HBVM(HSPAWN_BP) #define MEDISTAT_BP 8 #define MEDISTAT_BT 10000 #define MEDISTAT_HEALTH HBHM(190) #define MEDISTAT_SPLASHDAMAGE 50 #define MEDISTAT_SPLASHRADIUS 100 +#define MEDISTAT_VALUE HBVM(MEDISTAT_BP) #define MGTURRET_BP 8 #define MGTURRET_BT 10000 #define MGTURRET_HEALTH HBHM(190) #define MGTURRET_SPLASHDAMAGE 100 #define MGTURRET_SPLASHRADIUS 100 -#define MGTURRET_ANGULARSPEED 8 //degrees/think ~= 200deg/sec -#define MGTURRET_ACCURACYTOLERANCE MGTURRET_ANGULARSPEED / 1.5f //angular difference for turret to fire +#define MGTURRET_ANGULARSPEED 12 +#define MGTURRET_ACCURACY_TO_FIRE 0 #define MGTURRET_VERTICALCAP 30 // +/- maximum pitch -#define MGTURRET_REPEAT 100 +#define MGTURRET_REPEAT 150 #define MGTURRET_K_SCALE 1.0f -#define MGTURRET_RANGE 300.0f +#define MGTURRET_RANGE 400.0f #define MGTURRET_SPREAD 200 -#define MGTURRET_DMG HDM(4) -#define MGTURRET_DCC_ANGULARSPEED 10 -#define MGTURRET_DCC_ACCURACYTOLERANCE MGTURRET_DCC_ANGULARSPEED / 1.5f -#define MGTURRET_GRAB_ANGULARSPEED 3 -#define MGTURRET_GRAB_ACCURACYTOLERANCE MGTURRET_GRAB_ANGULARSPEED / 1.5f +#define MGTURRET_DMG HDM(8) +#define MGTURRET_SPINUP_TIME 750 // time between target sighted and fire +#define MGTURRET_VALUE HBVM(MGTURRET_BP) #define TESLAGEN_BP 10 #define TESLAGEN_BT 15000 @@ -543,20 +570,26 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TESLAGEN_SPLASHRADIUS 100 #define TESLAGEN_REPEAT 250 #define TESLAGEN_K_SCALE 4.0f -#define TESLAGEN_RANGE 250 -#define TESLAGEN_DMG HDM(9) +#define TESLAGEN_RANGE 200 +#define TESLAGEN_DMG HDM(10) +#define TESLAGEN_VALUE HBVM(TESLAGEN_BP) #define DC_BP 8 #define DC_BT 10000 #define DC_HEALTH HBHM(190) #define DC_SPLASHDAMAGE 50 #define DC_SPLASHRADIUS 100 +#define DC_ATTACK_PERIOD 10000 // how often to spam "under attack" +#define DC_HEALRATE 4 +#define DC_RANGE 1000 +#define DC_VALUE HBVM(DC_BP) #define ARMOURY_BP 10 #define ARMOURY_BT 10000 -#define ARMOURY_HEALTH HBHM(280) +#define ARMOURY_HEALTH HBHM(420) #define ARMOURY_SPLASHDAMAGE 50 #define ARMOURY_SPLASHRADIUS 100 +#define ARMOURY_VALUE HBVM(ARMOURY_BP) #define REACTOR_BP 0 #define REACTOR_BT 20000 @@ -566,14 +599,17 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define REACTOR_ATTACK_RANGE 100.0f #define REACTOR_ATTACK_REPEAT 1000 #define REACTOR_ATTACK_DAMAGE 40 -#define REACTOR_VALUE 2 +#define REACTOR_ATTACK_DCC_REPEAT 1000 +#define REACTOR_ATTACK_DCC_RANGE 150.0f +#define REACTOR_ATTACK_DCC_DAMAGE 40 +#define REACTOR_VALUE HBVM(30) -#define REPEATER_BP 0 +#define REPEATER_BP 4 #define REPEATER_BT 10000 #define REPEATER_HEALTH HBHM(250) #define REPEATER_SPLASHDAMAGE 50 #define REPEATER_SPLASHRADIUS 100 -#define REPEATER_INACTIVE_TIME 90000 +#define REPEATER_VALUE HBVM(REPEATER_BP) /* * HUMAN misc @@ -583,13 +619,33 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HUMAN_JOG_MODIFIER 1.0f #define HUMAN_BACK_MODIFIER 0.8f #define HUMAN_SIDE_MODIFIER 0.9f +#define HUMAN_DODGE_SIDE_MODIFIER 2.9f +#define HUMAN_DODGE_SLOWED_MODIFIER 0.9f +#define HUMAN_DODGE_UP_MODIFIER 0.5f +#define HUMAN_DODGE_TIMEOUT 500 +#define HUMAN_LAND_FRICTION 3.0f -#define STAMINA_STOP_RESTORE 25 +#define STAMINA_STOP_RESTORE 30 #define STAMINA_WALK_RESTORE 15 -#define STAMINA_SPRINT_TAKE 8 -#define STAMINA_LARMOUR_TAKE 4 +#define STAMINA_MEDISTAT_RESTORE 30 // stacked on STOP or WALK +#define STAMINA_SPRINT_TAKE 6 +#define STAMINA_JUMP_TAKE 250 +#define STAMINA_DODGE_TAKE 250 +#define STAMINA_MAX 1000 +#define STAMINA_BREATHING_LEVEL 0 +#define STAMINA_SLOW_LEVEL -500 +#define STAMINA_BLACKOUT_LEVEL -800 #define HUMAN_SPAWN_REPEAT_TIME 10000 +#define HUMAN_REGEN_DAMAGE_TIME 2000 //msec since damage before dcc repairs + +#define HUMAN_MAX_CREDITS 2000 +#define HUMAN_TK_SUICIDE_PENALTY 150 + +#define HUMAN_BUILDER_SCOREINC 50 // builders receive this many points every 10 seconds +#define ALIEN_BUILDER_SCOREINC AVM(100) // builders receive this many points every 10 seconds + +#define HUMAN_BUILDABLE_INACTIVE_TIME 90000 /* * Misc @@ -599,29 +655,27 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MAX_FALL_DISTANCE 120.0f //the fall distance at which maximum damage is dealt #define AVG_FALL_DISTANCE ((MIN_FALL_DISTANCE+MAX_FALL_DISTANCE)/2.0f) -#define HUMAN_MAXED 900 //a human with a strong selection of weapons/upgrades -#define HUMAN_MAX_CREDITS 2000 -#define ALIEN_MAX_KILLS 9 -#define ALIEN_MAX_SINGLE_KILLS 3 - -#define FREEKILL_PERIOD 120000 //msec -#define FREEKILL_ALIEN 1 +#define DEFAULT_FREEKILL_PERIOD "120" //seconds +#define FREEKILL_ALIEN ALIEN_CREDITS_PER_KILL #define FREEKILL_HUMAN LEVEL0_VALUE -#define DEFAULT_ALIEN_BUILDPOINTS "130" -#define DEFAULT_ALIEN_STAGE2_THRESH "20" -#define DEFAULT_ALIEN_STAGE3_THRESH "40" +#define DEFAULT_ALIEN_BUILDPOINTS "150" +#define DEFAULT_ALIEN_QUEUE_TIME "12000" +#define DEFAULT_ALIEN_STAGE2_THRESH "12000" +#define DEFAULT_ALIEN_STAGE3_THRESH "24000" #define DEFAULT_ALIEN_MAX_STAGE "2" -#define DEFAULT_HUMAN_BUILDPOINTS "130" -#define DEFAULT_HUMAN_STAGE2_THRESH "20" -#define DEFAULT_HUMAN_STAGE3_THRESH "40" +#define DEFAULT_HUMAN_BUILDPOINTS "100" +#define DEFAULT_HUMAN_QUEUE_TIME "8000" +#define DEFAULT_HUMAN_REPEATER_BUILDPOINTS "20" +#define DEFAULT_HUMAN_REPEATER_QUEUE_TIME "2000" +#define DEFAULT_HUMAN_REPEATER_MAX_ZONES "500" +#define DEFAULT_HUMAN_STAGE2_THRESH "6000" +#define DEFAULT_HUMAN_STAGE3_THRESH "12000" #define DEFAULT_HUMAN_MAX_STAGE "2" #define DAMAGE_FRACTION_FOR_KILL 0.5f //how much damage players (versus structures) need to //do to increment the stage kill counters + +#define MAXIMUM_BUILD_TIME 20000 // used for pie timer -// g_suddenDeathMode settings -#define SDMODE_BP 0 -#define SDMODE_NO_BUILD 1 -#define SDMODE_SELECTIVE 2 -#define SDMODE_NO_DECON 3 +#endif diff --git a/src/granger/COPYING b/src/granger/COPYING new file mode 100644 index 0000000..f755e7e --- /dev/null +++ b/src/granger/COPYING @@ -0,0 +1,622 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +a) The work must carry prominent notices stating that you modified +it, and giving a relevant date. + +b) The work must carry prominent notices stating that it is +released under this License and any conditions added under section +7. This requirement modifies the requirement in section 4 to +"keep intact all notices". + +c) You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. + +d) If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. + +b) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either (1) a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or (2) access to copy the +Corresponding Source from a network server at no charge. + +c) Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. + +d) Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. + +e) Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or + +b) Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or + +c) Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or + +d) Limiting the use for publicity purposes of names of licensors or +authors of the material; or + +e) Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or + +f) Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + diff --git a/src/granger/Dockerfile b/src/granger/Dockerfile new file mode 100644 index 0000000..11698c8 --- /dev/null +++ b/src/granger/Dockerfile @@ -0,0 +1,4 @@ +FROM ubuntu:yakkety +WORKDIR /usr/src +COPY . /usr/src/ +RUN apt update && apt install -y cmake libgl1-mesa-dev libsdl2-dev libfreetype6-dev mingw-w64 g++-mingw-w64 g++-multilib git zip vim-nox diff --git a/src/granger/README.md b/src/granger/README.md new file mode 100644 index 0000000..ef77989 --- /dev/null +++ b/src/granger/README.md @@ -0,0 +1,139 @@ +# Granger +[![AppVeyor](https://img.shields.io/appveyor/ci/jkent/granger.svg?style=flat-square)](https://ci.appveyor.com/project/jkent/granger) +[![Travis-CI](https://travis-ci.org/GrangerHub/granger.svg?branch=master "Travis-CI")](https://travis-ci.org/GrangerHub/granger) + +### Lua API documentation +`os.access(p, mode)`
+Checks that the file is executable/writable/readable. `mode` is a string of the characters "rwx". + +`os.chdir(p)`
+Change the current working directory. + +`os.copyfile(src, dst)`
+Copy a file from one location to another. + +`os.elevate()`
+Attempts to re-run process under elevated privlidges, returning true if elevated or false if unsuccessful. + +`os.get()`
+Retrieve the current operating system ID string. + +`os.getcwd()`
+Retrieve the current working directory. + +`os.is(id)`
+Check the current operating system. + +`os.is64bit()`
+Determine if the current system is running a 64-bit architecture. + +`os.isdir(p)`
+Returns true if the specified directory exists. + +`os.isfile(p)`
+Returns true if the given file exists. + +`os.matchdirs(p)`
+Performs a wildcard match to locate one or more directories. + +`os.matchfiles(p)`
+Performs a wildcard match to locate one or more files. + +`os.mkdir(p)`
+An overload of os.mkdir() function, which will create any missing subdirectories along the path. + +`os.outputof(cmd)`
+Run a shell command and return the output. + +`os.pathsearch(p, path1, ...)`
+Locates a file, given a set of search paths. + +`os.rmdir(p)`
+Remove a directory, along with any contained files or subdirectories. + +`os.stat(p)`
+Retrieve information about a file. + +`path.getbasename(p)`
+Retrieve the filename portion of a path, without any extension. + +`path.getabsolute(p)`
+Returns an absolute version of a relative path. + +`path.getdirectory(p)`
+Retrieve the directory portion of a path, or an empty string if the path does not include a directory. + +`path.getdrive(p)`
+Retrieve the drive letter, if a Windows path. + +`path.getextension(p)`
+Retrieve the file extension. + +`path.getname(p)`
+Retreive the filename portion of a path. + +`path.getrelative(p1, p2)`
+Returns a path relative to another. + +`path.isabsolute(p)`
+Determines if a path is absolute or relative. + +`path.join(...)`
+Builds a path from two or more path parts. + +`path.normalize(p)`
+Removes any wwirdness from a file system path string. + +`path.rebase(p, oldbase, newbase)`
+Takes a path which is relative to one location and makes it relative to another location instead. + +`path.translate(p, sep)`
+Convert the separators in a path from one form to another. If `sep` is nil, then a platform-specific separator is used. + +`path.wildcards(pattern)`
+Converts from a simple wildcard syntax, where * is "match any" and ** is "match recursive", to the corresponding Lua pattern. + +`string.explode(s, pattern, plain)`
+Returns an array of strings, each which is a substring of `s` formed by splitting on boundaries formed by `pattern`. + +`string.endswith(haystack, needle)`
+Returns true if `haystack` ends with `needle`. + +`string.findlast(s, pattern, plain)`
+Find the last instance of a pattern in a string. + +`string.startswith(haystack, needle)`
+Returns true if `haystack` starts with `needle`. + +`table.contains(t, value)`
+Returns true if the table contains the specified value. + +`table.extract(arr, fname)`
+Enumerates an array of objects and returns a new table containing only the value of one particular field. + +`table.flatten(arr)`
+Flattens a hierarchy of tables into a single array containing all of the values. + +`table.implode(arr, before, after, between)`
+Merges an array of items into a string. + +`table.insertflat(tbl, values)`
+Inserts a value of array of values into a table. If the value is itself a table, its contents are enumerated and added instead. So these inputs give these outputs:
+"x" -> { "x" }
+{ "x", "y" } -> { "x", "y" }
+{ "x", { "y" }} -> { "x", "y" }
+ +`table.isempty(t)`
+Returns true of the table is empty, and contains no indexed or keyed values. + +`table.join(...)`
+Adds the values from one array to the end of another and returns the result. + +`table.keys(tbl)`
+Return a list of all the keys used in a table. + +`table.merge(...)`
+Adds the key-value associations from one table into another and returns the resulting merged table. + +`table.translate(arr, translation)`
+Translates the values contained in array, using specified translation table and returns the results in a new array. diff --git a/src/granger/appveyor.yml b/src/granger/appveyor.yml new file mode 100644 index 0000000..c283de6 --- /dev/null +++ b/src/granger/appveyor.yml @@ -0,0 +1,32 @@ +platform: + - x86 + - x64 + +configuration: + - Debug + - Release + +os: Visual Studio 2015 + +clone_folder: c:\projects\granger + +build_script: + # show settings + - cmake -version + - echo %platform% + - echo %configuration% + + # generate a solution file + - cd c:\projects\granger + - mkdir build + - cd build + - if "%platform%" == "x64" set cmake_platform=%platform% + - cmake -g "Visual Studio 14 2015" .. -DCMAKE_GENERATOR_PLATFORM=%cmake_platform% + + # build it + - if "%platform%" == "x86" set msbuild_platform=Win32 + - if "%platform%" == "x64" set msbuild_platform=%platform% + - msbuild granger.sln /p:Configuration=%configuration% /toolsversion:14.0 /p:PlatformToolset=v140 /p:Platform=%msbuild_platform% + +test_script: + - if "%configuration%" == "Debug" ctest -VV --schedule-random -C Debug diff --git a/src/granger/misc/docker-build.sh b/src/granger/misc/docker-build.sh new file mode 100644 index 0000000..fe3232a --- /dev/null +++ b/src/granger/misc/docker-build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +[[ -d build ]] \ + || mkdir build + +cd build +cmake .. +make diff --git a/src/granger/src/CMakeLists.txt b/src/granger/src/CMakeLists.txt new file mode 100644 index 0000000..f59ae9c --- /dev/null +++ b/src/granger/src/CMakeLists.txt @@ -0,0 +1,60 @@ +add_subdirectory(lua) +add_subdirectory(nettle) +add_subdirectory(premake) + +add_executable(granger + getopt.h + lnettlelib.c + lnettlelib.h + main.c + strvec.c + strvec.h + ) + +if(APPLE) + add_definitions(-DLUA_USE_MACOSX) +endif(APPLE) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_definitions(-DLUA_USE_LINUX) +endif() + +add_definitions ( + #-DLUA_COMPAT_5_2 + -DNDEBUG + -mfpmath=sse + -ffast-math + -DGRANGER + ) + +include_directories ( + include + ) +target_link_libraries(granger granger_lua granger_nettle premake) + +if (NOT WIN32) + target_link_libraries(granger m dl) +endif() + +if(WIN32) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif(WIN32) + +if (APPLE) + macro(ADD_FRAMEWORK fwname appname) + find_library(FRAMEWORK_${fwname} + NAMES ${fwname} + PATHS ${CMAKE_OSX_SYSROOT}/System/Library + PATH_SUFFIXES Frameworks + NO_DEFAULT_PATH) + if( ${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND) + MESSAGE(ERROR ": Framework ${fwname} not found") + else() + TARGET_LINK_LIBRARIES(${appname} "${FRAMEWORK_${fwname}}/${fwname}") + MESSAGE(STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}") + endif() + endmacro(ADD_FRAMEWORK) + + add_framework(CoreServices granger) + add_framework(Security granger) +endif() diff --git a/src/granger/src/getopt.h b/src/granger/src/getopt.h new file mode 100644 index 0000000..c15cbd9 --- /dev/null +++ b/src/granger/src/getopt.h @@ -0,0 +1,653 @@ +#ifndef __GETOPT_H__ +/** + * DISCLAIMER + * This file is part of the mingw-w64 runtime package. + * + * The mingw-w64 runtime package and its code is distributed in the hope that it + * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR + * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to + * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + /* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma warning(disable:4996); + +#define __GETOPT_H__ + +/* All the headers include this file. */ +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ + +#ifdef REPLACE_GETOPT +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +#undef optreset /* see getopt.h */ +#define optreset __mingw_optreset +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ +#endif + +//extern int optind; /* index of first non-option in argv */ +//extern int optopt; /* single option character, as parsed */ +//extern int opterr; /* flag to enable built-in diagnostics... */ +// /* (user may set to zero, to suppress) */ +// +//extern char *optarg; /* pointer to argument of current option */ + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#ifndef __CYGWIN__ +#define __progname __argv[0] +#else +extern char __declspec(dllimport) *__progname; +#endif + +#ifdef __CYGWIN__ +static char EMSG[] = ""; +#else +#define EMSG "" +#endif + +struct option /* specification for a long form option... */ +{ + const char *name; /* option name, without leading hyphens */ + int has_arg; /* does it take an argument? */ + int *flag; /* where to save its status, or NULL */ + int val; /* its associated status value */ +}; + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +static void +_vwarnx(const char *fmt,va_list ap) +{ + (void)fprintf(stderr,"%s: ",__progname); + if (fmt != NULL) + (void)vfprintf(stderr,fmt,ap); + (void)fprintf(stderr,"\n"); +} + +static void +warnx(const char *fmt,...) +{ + va_list ap; + va_start(ap,fmt); + _vwarnx(fmt,ap); + va_end(ap); +} + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +#ifdef REPLACE_GETOPT +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} +#endif /* REPLACE_GETOPT */ + +//extern int getopt(int nargc, char * const *nargv, const char *options); + +#ifdef _BSD_SOURCE +/* + * BSD adds the non-standard `optreset' feature, for reinitialisation + * of `getopt' parsing. We support this feature, for applications which + * proclaim their BSD heritage, before including this header; however, + * to maintain portability, developers are advised to avoid it. + */ +# define optreset __mingw_optreset +extern int optreset; +#endif +#ifdef __cplusplus +} +#endif +/* + * POSIX requires the `getopt' API to be specified in `unistd.h'; + * thus, `unistd.h' includes this header. However, we do not want + * to expose the `getopt_long' or `getopt_long_only' APIs, when + * included in this manner. Thus, close the standard __GETOPT_H__ + * declarations block, and open an additional __GETOPT_LONG_H__ + * specific block, only when *not* __UNISTD_H_SOURCED__, in which + * to declare the extended API. + */ +#endif /* !defined(__GETOPT_H__) */ + +#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) +#define __GETOPT_LONG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +enum /* permitted values for its `has_arg' field... */ +{ + no_argument = 0, /* option never takes an argument */ + required_argument, /* option always requires an argument */ + optional_argument /* option may take an argument */ +}; + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, ambiguous, match; + +#define IDENTICAL_INTERPRETATION(_x, _y) \ + (long_options[(_x)].has_arg == long_options[(_y)].has_arg && \ + long_options[(_x)].flag == long_options[(_y)].flag && \ + long_options[(_x)].val == long_options[(_y)].val) + + current_argv = place; + match = -1; + ambiguous = 0; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + ambiguous = 0; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else if (!IDENTICAL_INTERPRETATION(i, match)) + ambiguous = 1; + } + if (ambiguous) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +#undef IDENTICAL_INTERPRETATION +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + * + * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or + * optreset != 0 for GNU compatibility. + */ + if (posixly_correct == -1 || optreset != 0) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = (char*)strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} + +//extern int getopt_long(int nargc, char * const *nargv, const char *options, +// const struct option *long_options, int *idx); +//extern int getopt_long_only(int nargc, char * const *nargv, const char *options, +// const struct option *long_options, int *idx); +/* + * Previous MinGW implementation had... + */ +#ifndef HAVE_DECL_GETOPT +/* + * ...for the long form API only; keep this for compatibility. + */ +# define HAVE_DECL_GETOPT 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */ diff --git a/src/granger/src/lnettlelib.c b/src/granger/src/lnettlelib.c new file mode 100644 index 0000000..787c75f --- /dev/null +++ b/src/granger/src/lnettlelib.c @@ -0,0 +1,181 @@ +/* + * This file is part of Granger. + * Copyright (c) 2016 Jeff Kent + * + * Granger 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 3 of the License, or + * (at your option) any later version. + * + * Granger 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 Granger. If not, see . + */ + +#include + +#include "lua.h" +#include "lauxlib.h" +#include "nettle/md5.h" +#include "nettle/sha2.h" + +#define MD5_CTX "md5_ctx*" +#define SHA256_CTX "sha256_ctx*" + +static int lmd5(lua_State *L) +{ + struct md5_ctx *ctx; + + ctx = (struct md5_ctx *)lua_newuserdata(L, sizeof(struct md5_ctx)); + luaL_setmetatable(L, MD5_CTX); + md5_init(ctx); + return 1; +} + +static int lmd5_digest(lua_State *L) +{ + struct md5_ctx *ctx; + char digest[MD5_DIGEST_SIZE]; + + ctx = luaL_checkudata(L, 1, MD5_CTX); + md5_digest(ctx, sizeof(digest), digest); + lua_pushlstring(L, digest, sizeof(digest)); + return 1; +} + +static int lmd5_update(lua_State *L) +{ + struct md5_ctx *ctx; + const char *data; + size_t len; + + ctx = luaL_checkudata(L, 1, MD5_CTX); + if (lua_isnil(L, 2)) { + return 0; + } + data = luaL_checklstring(L, 2, &len); + nettle_md5_update(ctx, len, data); + return 0; +} + +static int lmd5_tostring(lua_State *L) +{ + struct md5_ctx *ctx, ctx2; + char digest[MD5_DIGEST_SIZE]; + char hex[MD5_DIGEST_SIZE*2+1]; + int i; + + ctx = luaL_checkudata(L, 1, MD5_CTX); + memcpy(&ctx2, ctx, sizeof(struct md5_ctx)); + + md5_digest(&ctx2, sizeof(digest), digest); + for (i = 0; i < sizeof(digest); i++) { + sprintf(hex + 2 * i, "%02hhx", digest[i]); + } + + lua_pushstring(L, hex); + return 1; +} + +static int lsha256(lua_State *L) +{ + struct sha256_ctx *ctx; + + ctx = (struct sha256_ctx *)lua_newuserdata(L, sizeof(struct sha256_ctx)); + luaL_setmetatable(L, SHA256_CTX); + sha256_init(ctx); + return 1; +} + +static int lsha256_digest(lua_State *L) +{ + struct sha256_ctx *ctx; + char digest[SHA256_DIGEST_SIZE]; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + sha256_digest(ctx, sizeof(digest), digest); + lua_pushlstring(L, digest, sizeof(digest)); + return 1; +} + +static int lsha256_update(lua_State *L) +{ + struct sha256_ctx *ctx; + const char *data; + size_t len; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + if (lua_isnil(L, 2)) { + return 0; + } + data = luaL_checklstring(L, 2, &len); + nettle_sha256_update(ctx, len, data); + return 0; +} + +static int lsha256_tostring(lua_State *L) +{ + struct sha256_ctx *ctx, ctx2; + char digest[SHA256_DIGEST_SIZE]; + char hex[SHA256_DIGEST_SIZE*2+1]; + int i; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + memcpy(&ctx2, ctx, sizeof(struct sha256_ctx)); + + sha256_digest(&ctx2, sizeof(digest), digest); + for (i = 0; i < sizeof(digest); i++) { + sprintf(hex + 2 * i, "%02hhx", digest[i]); + } + + lua_pushstring(L, hex); + return 1; +} + +/* functions for 'nettle' library */ +static const luaL_Reg nettlelib[] = { + {"md5", lmd5}, + {"sha256", lsha256}, + {NULL, NULL} +}; + +static const luaL_Reg lmd5_methods[] = { + {"digest", lmd5_digest}, + {"update", lmd5_update}, + {"__tostring", lmd5_tostring}, + {NULL, NULL} +}; + +/* functions for sha256 objects */ +static const luaL_Reg lsha256_methods[] = { + {"digest", lsha256_digest}, + {"update", lsha256_update}, + {"__tostring", lsha256_tostring}, + {NULL, NULL} +}; + +static void createmeta (lua_State *L) +{ + luaL_newmetatable(L, MD5_CTX); /* create metatable for file handles */ + lua_pushvalue(L, -1); /* push metatable */ + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ + luaL_setfuncs(L, lmd5_methods, 0); /* add file methods to new metatable */ + lua_pop(L, 1); /* pop new metatable */ + + luaL_newmetatable(L, SHA256_CTX); /* create metatable for file handles */ + lua_pushvalue(L, -1); /* push metatable */ + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ + luaL_setfuncs(L, lsha256_methods, 0); /* add file methods to new metatable */ + lua_pop(L, 1); /* pop new metatable */ +} + +LUAMOD_API int luaopen_nettle (lua_State *L) +{ + luaL_newlib(L, nettlelib); + createmeta(L); + return 1; +} diff --git a/src/granger/src/lnettlelib.h b/src/granger/src/lnettlelib.h new file mode 100644 index 0000000..0afd753 --- /dev/null +++ b/src/granger/src/lnettlelib.h @@ -0,0 +1,25 @@ +/* + * This file is part of Granger. + * Copyright (c) 2016 Jeff Kent + * + * Granger 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 3 of the License, or + * (at your option) any later version. + * + * Granger 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 Granger. If not, see . + */ + +#ifndef lnettlelib_h +#define lnettlelib_h + +#define LUA_NETTLELIBNAME "nettle" +LUAMOD_API int (luaopen_nettle) (lua_State *L); + +#endif /* lnettlelib_h */ diff --git a/src/granger/src/lua/CMakeLists.txt b/src/granger/src/lua/CMakeLists.txt new file mode 100644 index 0000000..1508ef2 --- /dev/null +++ b/src/granger/src/lua/CMakeLists.txt @@ -0,0 +1,57 @@ +set(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) + +add_library( granger_lua STATIC + ${PARENT_DIR}/lua-5.3.3/src/lapi.c + ${PARENT_DIR}/lua-5.3.3/src/lcode.c + ${PARENT_DIR}/lua-5.3.3/src/lctype.c + ${PARENT_DIR}/lua-5.3.3/src/ldebug.c + ${PARENT_DIR}/lua-5.3.3/src/ldo.c + ${PARENT_DIR}/lua-5.3.3/src/ldump.c + ${PARENT_DIR}/lua-5.3.3/src/lfunc.c + ${PARENT_DIR}/lua-5.3.3/src/lgc.c + ${PARENT_DIR}/lua-5.3.3/src/llex.c + ${PARENT_DIR}/lua-5.3.3/src/lmem.c + ${PARENT_DIR}/lua-5.3.3/src/lobject.c + ${PARENT_DIR}/lua-5.3.3/src/lopcodes.c + ${PARENT_DIR}/lua-5.3.3/src/lparser.c + ${PARENT_DIR}/lua-5.3.3/src/lstate.c + ${PARENT_DIR}/lua-5.3.3/src/lstring.c + ${PARENT_DIR}/lua-5.3.3/src/ltable.c + ${PARENT_DIR}/lua-5.3.3/src/ltm.c + ${PARENT_DIR}/lua-5.3.3/src/lundump.c + ${PARENT_DIR}/lua-5.3.3/src/lvm.c + ${PARENT_DIR}/lua-5.3.3/src/lzio.c + ${PARENT_DIR}/lua-5.3.3/src/lauxlib.c + ${PARENT_DIR}/lua-5.3.3/src/lbaselib.c + ${PARENT_DIR}/lua-5.3.3/src/lbitlib.c + ${PARENT_DIR}/lua-5.3.3/src/lcorolib.c + ${PARENT_DIR}/lua-5.3.3/src/ldblib.c + ${PARENT_DIR}/lua-5.3.3/src/liolib.c + ${PARENT_DIR}/lua-5.3.3/src/lmathlib.c + ${PARENT_DIR}/lua-5.3.3/src/loslib.c + ${PARENT_DIR}/lua-5.3.3/src/lstrlib.c + ${PARENT_DIR}/lua-5.3.3/src/ltablib.c + ${PARENT_DIR}/lua-5.3.3/src/lutf8lib.c + ${PARENT_DIR}/lua-5.3.3/src/loadlib.c + ${PARENT_DIR}/lua-5.3.3/src/linit.c + ) + +if(APPLE) + add_definitions(-DLUA_USE_MACOSX) +endif(APPLE) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_definitions(-DLUA_USE_LINUX) +endif() + +add_definitions ( + -DLUA_COMPAT_5_2 + -DNDEBUG + -mfpmath=sse + -ffast-math + -DGRANGER + ) + +include_directories ( + include + ) diff --git a/src/granger/src/main.c b/src/granger/src/main.c new file mode 100644 index 0000000..3d1b28f --- /dev/null +++ b/src/granger/src/main.c @@ -0,0 +1,97 @@ +/* + * This file is part of Granger. + * Copyright (c) 2016 Jeff Kent + * + * Granger 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 3 of the License, or + * (at your option) any later version. + * + * Granger 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 Granger. If not, see . + */ + +#include +#include +#include +#include + +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +#include "premake/premake.h" + +#if defined(PLATFORM_WINDOWS) +#include "getopt.h" +#endif + +#include "lnettlelib.h" + +extern const luaL_Reg extra_os_functions[]; + +int main (int argc, char *argv[]) +{ + char *script_path; + lua_State *L; + int c; + + while ((c = getopt(argc, argv, "C:")) != -1) { + switch (c) { + case 'C': + if (chdir(optarg)) { + fprintf(stderr, "could not change working directory\n"); + return EXIT_FAILURE; + } + break; + case '?': + default: + return EXIT_FAILURE; + } + } + + if (optind >= argc || argv[optind][0] == '-') { + fprintf(stderr, "lua script not provided\n"); + return EXIT_FAILURE; + } + script_path = argv[optind]; + optind++; + + L = luaL_newstate(); + if (L == NULL) { + fprintf(stderr, "cannot create lua state\n"); + return EXIT_FAILURE; + } + + luaL_openlibs(L); + luaL_requiref(L, "nettle", luaopen_nettle, 1); + lua_pop(L, 1); + + premake_init(L); + premake_locate(L, argv[0]); + lua_setglobal(L, "_EXE_PATH"); + + lua_pushstring(L, script_path); + lua_setglobal(L, "_GRANGER_SCRIPT"); + + lua_newtable(L); + for (int i = 1; optind < argc; i++, optind++) { + lua_pushinteger(L, i); + lua_pushstring(L, argv[optind]); + lua_settable(L, 1); + } + lua_setglobal(L, "argv"); + + if (luaL_dofile(L, script_path)) { + fprintf(stderr, "Error: %s\n", lua_tostring(L, -1)); + lua_close(L); + return EXIT_FAILURE; + } + + lua_close(L); + return EXIT_SUCCESS; +} diff --git a/src/granger/src/nettle/CMakeLists.txt b/src/granger/src/nettle/CMakeLists.txt new file mode 100644 index 0000000..c2ec77c --- /dev/null +++ b/src/granger/src/nettle/CMakeLists.txt @@ -0,0 +1,23 @@ +if(NOT WIN32) + add_definitions(-Wall -Wextra) +endif(NOT WIN32) + +if(WIN32) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif(WIN32) + +add_library(granger_nettle + macros.h + md5-compress.c + md5.c + md5.h + nettle-stdint.h + nettle-types.h + nettle-write.h + sha2.h + sha256-compress.c + sha256.c + version.h + write-be32.c + write-le32.c + ) diff --git a/src/granger/src/nettle/macros.h b/src/granger/src/nettle/macros.h new file mode 100644 index 0000000..af84841 --- /dev/null +++ b/src/granger/src/nettle/macros.h @@ -0,0 +1,245 @@ +/* macros.h + + Copyright (C) 2001, 2010 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_MACROS_H_INCLUDED +#define NETTLE_MACROS_H_INCLUDED + +/* Reads a 64-bit integer, in network, big-endian, byte order */ +#define READ_UINT64(p) \ +( (((uint64_t) (p)[0]) << 56) \ + | (((uint64_t) (p)[1]) << 48) \ + | (((uint64_t) (p)[2]) << 40) \ + | (((uint64_t) (p)[3]) << 32) \ + | (((uint64_t) (p)[4]) << 24) \ + | (((uint64_t) (p)[5]) << 16) \ + | (((uint64_t) (p)[6]) << 8) \ + | ((uint64_t) (p)[7])) + +#define WRITE_UINT64(p, i) \ +do { \ + (p)[0] = ((i) >> 56) & 0xff; \ + (p)[1] = ((i) >> 48) & 0xff; \ + (p)[2] = ((i) >> 40) & 0xff; \ + (p)[3] = ((i) >> 32) & 0xff; \ + (p)[4] = ((i) >> 24) & 0xff; \ + (p)[5] = ((i) >> 16) & 0xff; \ + (p)[6] = ((i) >> 8) & 0xff; \ + (p)[7] = (i) & 0xff; \ +} while(0) + +/* Reads a 32-bit integer, in network, big-endian, byte order */ +#define READ_UINT32(p) \ +( (((uint32_t) (p)[0]) << 24) \ + | (((uint32_t) (p)[1]) << 16) \ + | (((uint32_t) (p)[2]) << 8) \ + | ((uint32_t) (p)[3])) + +#define WRITE_UINT32(p, i) \ +do { \ + (p)[0] = ((i) >> 24) & 0xff; \ + (p)[1] = ((i) >> 16) & 0xff; \ + (p)[2] = ((i) >> 8) & 0xff; \ + (p)[3] = (i) & 0xff; \ +} while(0) + +/* Analogous macros, for 24 and 16 bit numbers */ +#define READ_UINT24(p) \ +( (((uint32_t) (p)[0]) << 16) \ + | (((uint32_t) (p)[1]) << 8) \ + | ((uint32_t) (p)[2])) + +#define WRITE_UINT24(p, i) \ +do { \ + (p)[0] = ((i) >> 16) & 0xff; \ + (p)[1] = ((i) >> 8) & 0xff; \ + (p)[2] = (i) & 0xff; \ +} while(0) + +#define READ_UINT16(p) \ +( (((uint32_t) (p)[0]) << 8) \ + | ((uint32_t) (p)[1])) + +#define WRITE_UINT16(p, i) \ +do { \ + (p)[0] = ((i) >> 8) & 0xff; \ + (p)[1] = (i) & 0xff; \ +} while(0) + +/* And the other, little-endian, byteorder */ +#define LE_READ_UINT64(p) \ +( (((uint64_t) (p)[7]) << 56) \ + | (((uint64_t) (p)[6]) << 48) \ + | (((uint64_t) (p)[5]) << 40) \ + | (((uint64_t) (p)[4]) << 32) \ + | (((uint64_t) (p)[3]) << 24) \ + | (((uint64_t) (p)[2]) << 16) \ + | (((uint64_t) (p)[1]) << 8) \ + | ((uint64_t) (p)[0])) + +#define LE_WRITE_UINT64(p, i) \ +do { \ + (p)[7] = ((i) >> 56) & 0xff; \ + (p)[6] = ((i) >> 48) & 0xff; \ + (p)[5] = ((i) >> 40) & 0xff; \ + (p)[4] = ((i) >> 32) & 0xff; \ + (p)[3] = ((i) >> 24) & 0xff; \ + (p)[2] = ((i) >> 16) & 0xff; \ + (p)[1] = ((i) >> 8) & 0xff; \ + (p)[0] = (i) & 0xff; \ +} while (0) + +#define LE_READ_UINT32(p) \ +( (((uint32_t) (p)[3]) << 24) \ + | (((uint32_t) (p)[2]) << 16) \ + | (((uint32_t) (p)[1]) << 8) \ + | ((uint32_t) (p)[0])) + +#define LE_WRITE_UINT32(p, i) \ +do { \ + (p)[3] = ((i) >> 24) & 0xff; \ + (p)[2] = ((i) >> 16) & 0xff; \ + (p)[1] = ((i) >> 8) & 0xff; \ + (p)[0] = (i) & 0xff; \ +} while(0) + +/* Analogous macros, for 16 bit numbers */ +#define LE_READ_UINT16(p) \ + ( (((uint32_t) (p)[1]) << 8) \ + | ((uint32_t) (p)[0])) + +#define LE_WRITE_UINT16(p, i) \ + do { \ + (p)[1] = ((i) >> 8) & 0xff; \ + (p)[0] = (i) & 0xff; \ + } while(0) + +/* Macro to make it easier to loop over several blocks. */ +#define FOR_BLOCKS(length, dst, src, blocksize) \ + assert( !((length) % (blocksize))); \ + for (; (length); ((length) -= (blocksize), \ + (dst) += (blocksize), \ + (src) += (blocksize)) ) + +/* The masking of the right shift is needed to allow n == 0 (using + just 32 - n and 64 - n results in undefined behaviour). Most uses + of these macros use a constant and non-zero rotation count. */ +#define ROTL32(n,x) (((x)<<(n)) | ((x)>>((-(n)&31)))) + +#define ROTL64(n,x) (((x)<<(n)) | ((x)>>((-(n))&63))) + +/* Requires that size > 0 */ +#define INCREMENT(size, ctr) \ + do { \ + unsigned increment_i = (size) - 1; \ + if (++(ctr)[increment_i] == 0) \ + while (increment_i > 0 \ + && ++(ctr)[--increment_i] == 0 ) \ + ; \ + } while (0) + + +/* Helper macro for Merkle-Damgård hash functions. Assumes the context + structs includes the following fields: + + uint8_t block[...]; // Buffer holding one block + unsigned int index; // Index into block +*/ + +/* Currently used by sha512 (and sha384) only. */ +#define MD_INCR(ctx) ((ctx)->count_high += !++(ctx)->count_low) + +/* Takes the compression function f as argument. NOTE: also clobbers + length and data. */ +#define MD_UPDATE(ctx, length, data, f, incr) \ + do { \ + if ((ctx)->index) \ + { \ + /* Try to fill partial block */ \ + unsigned __md_left = sizeof((ctx)->block) - (ctx)->index; \ + if ((length) < __md_left) \ + { \ + memcpy((ctx)->block + (ctx)->index, (data), (length)); \ + (ctx)->index += (length); \ + goto __md_done; /* Finished */ \ + } \ + else \ + { \ + memcpy((ctx)->block + (ctx)->index, (data), __md_left); \ + \ + f((ctx), (ctx)->block); \ + (incr); \ + \ + (data) += __md_left; \ + (length) -= __md_left; \ + } \ + } \ + while ((length) >= sizeof((ctx)->block)) \ + { \ + f((ctx), (data)); \ + (incr); \ + \ + (data) += sizeof((ctx)->block); \ + (length) -= sizeof((ctx)->block); \ + } \ + memcpy ((ctx)->block, (data), (length)); \ + (ctx)->index = (length); \ + __md_done: \ + ; \ + } while (0) + +/* Pads the block to a block boundary with the bit pattern 1 0*, + leaving size octets for the length field at the end. If needed, + compresses the block and starts a new one. */ +#define MD_PAD(ctx, size, f) \ + do { \ + unsigned __md_i; \ + __md_i = (ctx)->index; \ + \ + /* Set the first char of padding to 0x80. This is safe since there \ + is always at least one byte free */ \ + \ + assert(__md_i < sizeof((ctx)->block)); \ + (ctx)->block[__md_i++] = 0x80; \ + \ + if (__md_i > (sizeof((ctx)->block) - (size))) \ + { /* No room for length in this block. Process it and \ + pad with another one */ \ + memset((ctx)->block + __md_i, 0, sizeof((ctx)->block) - __md_i); \ + \ + f((ctx), (ctx)->block); \ + __md_i = 0; \ + } \ + memset((ctx)->block + __md_i, 0, \ + sizeof((ctx)->block) - (size) - __md_i); \ + \ + } while (0) + +#endif /* NETTLE_MACROS_H_INCLUDED */ diff --git a/src/granger/src/nettle/md5-compress.c b/src/granger/src/nettle/md5-compress.c new file mode 100644 index 0000000..b8dad3f --- /dev/null +++ b/src/granger/src/nettle/md5-compress.c @@ -0,0 +1,174 @@ +/* md5-compress.c + + The compression function for the md5 hash function. + + Copyright (C) 2001, 2005 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +/* Based on public domain code hacked by Colin Plumb, Andrew Kuchling, and + * Niels Möller. */ + + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef MD5_DEBUG +# define MD5_DEBUG 0 +#endif + +#if MD5_DEBUG +# include +# define DEBUG(i) \ + fprintf(stderr, "%2d: %8x %8x %8x %8x\n", i, a, b, c, d) +#else +# define DEBUG(i) +#endif + +#include +#include +#include + +#include "md5.h" + +#include "macros.h" + +/* A block, treated as a sequence of 32-bit words. */ +#define MD5_DATA_LENGTH 16 + +/* MD5 functions */ + +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define F2(x, y, z) F1((z), (x), (y)) +#define F3(x, y, z) ((x) ^ (y) ^ (z)) +#define F4(x, y, z) ((y) ^ ((x) | ~(z))) + +#define ROUND(f, w, x, y, z, data, s) \ +( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* Perform the MD5 transformation on one full block of 16 32-bit + * words. + * + * Compresses 20 (_MD5_DIGEST_LENGTH + MD5_DATA_LENGTH) words into 4 + * (_MD5_DIGEST_LENGTH) words. */ + +void +_nettle_md5_compress(uint32_t *digest, const uint8_t *input) +{ + uint32_t data[MD5_DATA_LENGTH]; + uint32_t a, b, c, d; + unsigned i; + + for (i = 0; i < MD5_DATA_LENGTH; i++, input += 4) + data[i] = LE_READ_UINT32(input); + + a = digest[0]; + b = digest[1]; + c = digest[2]; + d = digest[3]; + + DEBUG(-1); + ROUND(F1, a, b, c, d, data[ 0] + 0xd76aa478, 7); DEBUG(0); + ROUND(F1, d, a, b, c, data[ 1] + 0xe8c7b756, 12); DEBUG(1); + ROUND(F1, c, d, a, b, data[ 2] + 0x242070db, 17); + ROUND(F1, b, c, d, a, data[ 3] + 0xc1bdceee, 22); + ROUND(F1, a, b, c, d, data[ 4] + 0xf57c0faf, 7); + ROUND(F1, d, a, b, c, data[ 5] + 0x4787c62a, 12); + ROUND(F1, c, d, a, b, data[ 6] + 0xa8304613, 17); + ROUND(F1, b, c, d, a, data[ 7] + 0xfd469501, 22); + ROUND(F1, a, b, c, d, data[ 8] + 0x698098d8, 7); + ROUND(F1, d, a, b, c, data[ 9] + 0x8b44f7af, 12); + ROUND(F1, c, d, a, b, data[10] + 0xffff5bb1, 17); + ROUND(F1, b, c, d, a, data[11] + 0x895cd7be, 22); + ROUND(F1, a, b, c, d, data[12] + 0x6b901122, 7); + ROUND(F1, d, a, b, c, data[13] + 0xfd987193, 12); + ROUND(F1, c, d, a, b, data[14] + 0xa679438e, 17); + ROUND(F1, b, c, d, a, data[15] + 0x49b40821, 22); DEBUG(15); + + ROUND(F2, a, b, c, d, data[ 1] + 0xf61e2562, 5); DEBUG(16); + ROUND(F2, d, a, b, c, data[ 6] + 0xc040b340, 9); DEBUG(17); + ROUND(F2, c, d, a, b, data[11] + 0x265e5a51, 14); + ROUND(F2, b, c, d, a, data[ 0] + 0xe9b6c7aa, 20); + ROUND(F2, a, b, c, d, data[ 5] + 0xd62f105d, 5); + ROUND(F2, d, a, b, c, data[10] + 0x02441453, 9); + ROUND(F2, c, d, a, b, data[15] + 0xd8a1e681, 14); + ROUND(F2, b, c, d, a, data[ 4] + 0xe7d3fbc8, 20); + ROUND(F2, a, b, c, d, data[ 9] + 0x21e1cde6, 5); + ROUND(F2, d, a, b, c, data[14] + 0xc33707d6, 9); + ROUND(F2, c, d, a, b, data[ 3] + 0xf4d50d87, 14); + ROUND(F2, b, c, d, a, data[ 8] + 0x455a14ed, 20); + ROUND(F2, a, b, c, d, data[13] + 0xa9e3e905, 5); + ROUND(F2, d, a, b, c, data[ 2] + 0xfcefa3f8, 9); + ROUND(F2, c, d, a, b, data[ 7] + 0x676f02d9, 14); + ROUND(F2, b, c, d, a, data[12] + 0x8d2a4c8a, 20); DEBUG(31); + + ROUND(F3, a, b, c, d, data[ 5] + 0xfffa3942, 4); DEBUG(32); + ROUND(F3, d, a, b, c, data[ 8] + 0x8771f681, 11); DEBUG(33); + ROUND(F3, c, d, a, b, data[11] + 0x6d9d6122, 16); + ROUND(F3, b, c, d, a, data[14] + 0xfde5380c, 23); + ROUND(F3, a, b, c, d, data[ 1] + 0xa4beea44, 4); + ROUND(F3, d, a, b, c, data[ 4] + 0x4bdecfa9, 11); + ROUND(F3, c, d, a, b, data[ 7] + 0xf6bb4b60, 16); + ROUND(F3, b, c, d, a, data[10] + 0xbebfbc70, 23); + ROUND(F3, a, b, c, d, data[13] + 0x289b7ec6, 4); + ROUND(F3, d, a, b, c, data[ 0] + 0xeaa127fa, 11); + ROUND(F3, c, d, a, b, data[ 3] + 0xd4ef3085, 16); + ROUND(F3, b, c, d, a, data[ 6] + 0x04881d05, 23); + ROUND(F3, a, b, c, d, data[ 9] + 0xd9d4d039, 4); + ROUND(F3, d, a, b, c, data[12] + 0xe6db99e5, 11); + ROUND(F3, c, d, a, b, data[15] + 0x1fa27cf8, 16); + ROUND(F3, b, c, d, a, data[ 2] + 0xc4ac5665, 23); DEBUG(47); + + ROUND(F4, a, b, c, d, data[ 0] + 0xf4292244, 6); DEBUG(48); + ROUND(F4, d, a, b, c, data[ 7] + 0x432aff97, 10); DEBUG(49); + ROUND(F4, c, d, a, b, data[14] + 0xab9423a7, 15); + ROUND(F4, b, c, d, a, data[ 5] + 0xfc93a039, 21); + ROUND(F4, a, b, c, d, data[12] + 0x655b59c3, 6); + ROUND(F4, d, a, b, c, data[ 3] + 0x8f0ccc92, 10); + ROUND(F4, c, d, a, b, data[10] + 0xffeff47d, 15); + ROUND(F4, b, c, d, a, data[ 1] + 0x85845dd1, 21); + ROUND(F4, a, b, c, d, data[ 8] + 0x6fa87e4f, 6); + ROUND(F4, d, a, b, c, data[15] + 0xfe2ce6e0, 10); + ROUND(F4, c, d, a, b, data[ 6] + 0xa3014314, 15); + ROUND(F4, b, c, d, a, data[13] + 0x4e0811a1, 21); + ROUND(F4, a, b, c, d, data[ 4] + 0xf7537e82, 6); + ROUND(F4, d, a, b, c, data[11] + 0xbd3af235, 10); + ROUND(F4, c, d, a, b, data[ 2] + 0x2ad7d2bb, 15); + ROUND(F4, b, c, d, a, data[ 9] + 0xeb86d391, 21); DEBUG(63); + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; +#if MD5_DEBUG + fprintf(stderr, "99: %8x %8x %8x %8x\n", + digest[0], digest[1], digest[2], digest[3]); +#endif + +} diff --git a/src/granger/src/nettle/md5.c b/src/granger/src/nettle/md5.c new file mode 100644 index 0000000..97a5245 --- /dev/null +++ b/src/granger/src/nettle/md5.c @@ -0,0 +1,93 @@ +/* md5.c + + The MD5 hash function, described in RFC 1321. + + Copyright (C) 2001 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +/* Based on public domain code hacked by Colin Plumb, Andrew Kuchling, and + * Niels Möller. */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "md5.h" + +#include "macros.h" +#include "nettle-write.h" + +void +md5_init(struct md5_ctx *ctx) +{ + const uint32_t iv[_MD5_DIGEST_LENGTH] = + { + 0x67452301, + 0xefcdab89, + 0x98badcfe, + 0x10325476, + }; + memcpy(ctx->state, iv, sizeof(ctx->state)); + ctx->count = 0; + ctx->index = 0; +} + +#define COMPRESS(ctx, data) (_nettle_md5_compress((ctx)->state, (data))) + +void +md5_update(struct md5_ctx *ctx, + size_t length, + const uint8_t *data) +{ + MD_UPDATE(ctx, length, data, COMPRESS, ctx->count++); +} + +void +md5_digest(struct md5_ctx *ctx, + size_t length, + uint8_t *digest) +{ + uint64_t bit_count; + + assert(length <= MD5_DIGEST_SIZE); + + MD_PAD(ctx, 8, COMPRESS); + + /* There are 512 = 2^9 bits in one block */ + bit_count = (ctx->count << 9) | (ctx->index << 3); + + LE_WRITE_UINT64(ctx->block + (MD5_BLOCK_SIZE - 8), bit_count); + _nettle_md5_compress(ctx->state, ctx->block); + + _nettle_write_le32(length, digest, ctx->state); + md5_init(ctx); +} diff --git a/src/granger/src/nettle/md5.h b/src/granger/src/nettle/md5.h new file mode 100644 index 0000000..79304bc --- /dev/null +++ b/src/granger/src/nettle/md5.h @@ -0,0 +1,86 @@ +/* md5.h + + The MD5 hash function, described in RFC 1321. + + Copyright (C) 2001 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_MD5_H_INCLUDED +#define NETTLE_MD5_H_INCLUDED + +#include "nettle-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Name mangling */ +#define md5_init nettle_md5_init +#define md5_update nettle_md5_update +#define md5_digest nettle_md5_digest + +#define MD5_DIGEST_SIZE 16 +#define MD5_BLOCK_SIZE 64 +/* For backwards compatibility */ +#define MD5_DATA_SIZE MD5_BLOCK_SIZE + +/* Digest is kept internally as 4 32-bit words. */ +#define _MD5_DIGEST_LENGTH 4 + +struct md5_ctx +{ + uint32_t state[_MD5_DIGEST_LENGTH]; + uint64_t count; /* Block count */ + uint8_t block[MD5_BLOCK_SIZE]; /* Block buffer */ + unsigned index; /* Into buffer */ +}; + +void +md5_init(struct md5_ctx *ctx); + +void +md5_update(struct md5_ctx *ctx, + size_t length, + const uint8_t *data); + +void +md5_digest(struct md5_ctx *ctx, + size_t length, + uint8_t *digest); + +/* Internal compression function. STATE points to 4 uint32_t words, + and DATA points to 64 bytes of input data, possibly unaligned. */ +void +_nettle_md5_compress(uint32_t *state, const uint8_t *data); + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_MD5_H_INCLUDED */ diff --git a/src/granger/src/nettle/nettle-stdint.h b/src/granger/src/nettle/nettle-stdint.h new file mode 100644 index 0000000..3298fa6 --- /dev/null +++ b/src/granger/src/nettle/nettle-stdint.h @@ -0,0 +1,6 @@ +#ifndef __NETTLE_STDINT_H +#define __NETTLE_STDINT_H + +#include + +#endif /* __NETTLE_STDINT_H */ diff --git a/src/granger/src/nettle/nettle-types.h b/src/granger/src/nettle/nettle-types.h new file mode 100644 index 0000000..8f77ea9 --- /dev/null +++ b/src/granger/src/nettle/nettle-types.h @@ -0,0 +1,110 @@ +/* nettle-types.h + + Copyright (C) 2005, 2014 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_TYPES_H +#define NETTLE_TYPES_H + +/* For size_t */ +#include + +/* Pretend these types always exists. Nettle doesn't use them. */ +#define _STDINT_HAVE_INT_FAST32_T 1 +#include "nettle-stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* An aligned 16-byte block. */ +union nettle_block16 +{ + uint8_t b[16]; + unsigned long w[16 / sizeof(unsigned long)]; +}; + +/* Randomness. Used by key generation and dsa signature creation. */ +typedef void nettle_random_func(void *ctx, + size_t length, uint8_t *dst); + +/* Progress report function, mainly for key generation. */ +typedef void nettle_progress_func(void *ctx, int c); + +/* Realloc function, used by struct nettle_buffer. */ +typedef void *nettle_realloc_func(void *ctx, void *p, size_t length); + +/* Ciphers */ +typedef void nettle_set_key_func(void *ctx, const uint8_t *key); + +/* For block ciphers, const context. */ +typedef void nettle_cipher_func(const void *ctx, + size_t length, uint8_t *dst, + const uint8_t *src); + + +/* Uses a void * for cipher contexts. Used for crypt operations where + the internal state changes during the encryption. */ +typedef void nettle_crypt_func(void *ctx, + size_t length, uint8_t *dst, + const uint8_t *src); + +/* Hash algorithms */ +typedef void nettle_hash_init_func(void *ctx); +typedef void nettle_hash_update_func(void *ctx, + size_t length, + const uint8_t *src); +typedef void nettle_hash_digest_func(void *ctx, + size_t length, uint8_t *dst); + +/* ASCII armor codecs. NOTE: Experimental and subject to change. */ + +typedef size_t nettle_armor_length_func(size_t length); +typedef void nettle_armor_init_func(void *ctx); + +typedef size_t nettle_armor_encode_update_func(void *ctx, + uint8_t *dst, + size_t src_length, + const uint8_t *src); + +typedef size_t nettle_armor_encode_final_func(void *ctx, uint8_t *dst); + +typedef int nettle_armor_decode_update_func(void *ctx, + size_t *dst_length, + uint8_t *dst, + size_t src_length, + const uint8_t *src); + +typedef int nettle_armor_decode_final_func(void *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_TYPES_H */ diff --git a/src/granger/src/nettle/nettle-write.h b/src/granger/src/nettle/nettle-write.h new file mode 100644 index 0000000..54152bd --- /dev/null +++ b/src/granger/src/nettle/nettle-write.h @@ -0,0 +1,58 @@ +/* nettle-write.h + + Internal functions to write out word-sized data to byte arrays. + + Copyright (C) 2010 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_WRITE_H_INCLUDED +#define NETTLE_WRITE_H_INCLUDED + +/* For size_t */ +#include + +#include "nettle-stdint.h" + +/* Write the word array at SRC to the byte array at DST, using little + endian (le) or big endian (be) byte order, and truncating the + result to LENGTH bytes. */ + +/* FIXME: Use a macro shortcut to memcpy for native endianness. */ +void +_nettle_write_be32(size_t length, uint8_t *dst, + uint32_t *src); +void +_nettle_write_le32(size_t length, uint8_t *dst, + uint32_t *src); + +void +_nettle_write_le64(size_t length, uint8_t *dst, + uint64_t *src); + +#endif /* NETTLE_WRITE_H_INCLUDED */ diff --git a/src/granger/src/nettle/sha2.h b/src/granger/src/nettle/sha2.h new file mode 100644 index 0000000..d0426d7 --- /dev/null +++ b/src/granger/src/nettle/sha2.h @@ -0,0 +1,206 @@ +/* sha2.h + + The sha2 family of hash functions. + + Copyright (C) 2001, 2012 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_SHA2_H_INCLUDED +#define NETTLE_SHA2_H_INCLUDED + +#include "nettle-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Name mangling */ +#define sha224_init nettle_sha224_init +#define sha224_digest nettle_sha224_digest +#define sha256_init nettle_sha256_init +#define sha256_update nettle_sha256_update +#define sha256_digest nettle_sha256_digest +#define sha384_init nettle_sha384_init +#define sha384_digest nettle_sha384_digest +#define sha512_init nettle_sha512_init +#define sha512_update nettle_sha512_update +#define sha512_digest nettle_sha512_digest +#define sha512_224_init nettle_sha512_224_init +#define sha512_224_digest nettle_sha512_224_digest +#define sha512_256_init nettle_sha512_256_init +#define sha512_256_digest nettle_sha512_256_digest + +/* For backwards compatibility */ +#define SHA224_DATA_SIZE SHA256_BLOCK_SIZE +#define SHA256_DATA_SIZE SHA256_BLOCK_SIZE +#define SHA512_DATA_SIZE SHA512_BLOCK_SIZE +#define SHA384_DATA_SIZE SHA512_BLOCK_SIZE + +/* SHA256 */ + +#define SHA256_DIGEST_SIZE 32 +#define SHA256_BLOCK_SIZE 64 + +/* Digest is kept internally as 8 32-bit words. */ +#define _SHA256_DIGEST_LENGTH 8 + +struct sha256_ctx +{ + uint32_t state[_SHA256_DIGEST_LENGTH]; /* State variables */ + uint64_t count; /* 64-bit block count */ + uint8_t block[SHA256_BLOCK_SIZE]; /* SHA256 data buffer */ + unsigned int index; /* index into buffer */ +}; + +void +sha256_init(struct sha256_ctx *ctx); + +void +sha256_update(struct sha256_ctx *ctx, + size_t length, + const uint8_t *data); + +void +sha256_digest(struct sha256_ctx *ctx, + size_t length, + uint8_t *digest); + +/* Internal compression function. STATE points to 8 uint32_t words, + DATA points to 64 bytes of input data, possibly unaligned, and K + points to the table of constants. */ +void +_nettle_sha256_compress(uint32_t *state, const uint8_t *data, const uint32_t *k); + + +/* SHA224, a truncated SHA256 with different initial state. */ + +#define SHA224_DIGEST_SIZE 28 +#define SHA224_BLOCK_SIZE SHA256_BLOCK_SIZE +#define sha224_ctx sha256_ctx + +void +sha224_init(struct sha256_ctx *ctx); + +#define sha224_update nettle_sha256_update + +void +sha224_digest(struct sha256_ctx *ctx, + size_t length, + uint8_t *digest); + + +/* SHA512 */ + +#define SHA512_DIGEST_SIZE 64 +#define SHA512_BLOCK_SIZE 128 + +/* Digest is kept internally as 8 64-bit words. */ +#define _SHA512_DIGEST_LENGTH 8 + +struct sha512_ctx +{ + uint64_t state[_SHA512_DIGEST_LENGTH]; /* State variables */ + uint64_t count_low, count_high; /* 128-bit block count */ + uint8_t block[SHA512_BLOCK_SIZE]; /* SHA512 data buffer */ + unsigned int index; /* index into buffer */ +}; + +void +sha512_init(struct sha512_ctx *ctx); + +void +sha512_update(struct sha512_ctx *ctx, + size_t length, + const uint8_t *data); + +void +sha512_digest(struct sha512_ctx *ctx, + size_t length, + uint8_t *digest); + +/* Internal compression function. STATE points to 8 uint64_t words, + DATA points to 128 bytes of input data, possibly unaligned, and K + points to the table of constants. */ +void +_nettle_sha512_compress(uint64_t *state, const uint8_t *data, const uint64_t *k); + + +/* SHA384, a truncated SHA512 with different initial state. */ + +#define SHA384_DIGEST_SIZE 48 +#define SHA384_BLOCK_SIZE SHA512_BLOCK_SIZE +#define sha384_ctx sha512_ctx + +void +sha384_init(struct sha512_ctx *ctx); + +#define sha384_update nettle_sha512_update + +void +sha384_digest(struct sha512_ctx *ctx, + size_t length, + uint8_t *digest); + + +/* SHA512_224 and SHA512_256, two truncated versions of SHA512 + with different initial states. */ + +#define SHA512_224_DIGEST_SIZE 28 +#define SHA512_224_BLOCK_SIZE SHA512_BLOCK_SIZE +#define sha512_224_ctx sha512_ctx + +void +sha512_224_init(struct sha512_224_ctx *ctx); + +#define sha512_224_update nettle_sha512_update + +void +sha512_224_digest(struct sha512_224_ctx *ctx, + size_t length, + uint8_t *digest); + +#define SHA512_256_DIGEST_SIZE 32 +#define SHA512_256_BLOCK_SIZE SHA512_BLOCK_SIZE +#define sha512_256_ctx sha512_ctx + +void +sha512_256_init(struct sha512_256_ctx *ctx); + +#define sha512_256_update nettle_sha512_update + +void +sha512_256_digest(struct sha512_256_ctx *ctx, + size_t length, + uint8_t *digest); + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_SHA2_H_INCLUDED */ diff --git a/src/granger/src/nettle/sha256-compress.c b/src/granger/src/nettle/sha256-compress.c new file mode 100644 index 0000000..8b82d70 --- /dev/null +++ b/src/granger/src/nettle/sha256-compress.c @@ -0,0 +1,199 @@ +/* sha256-compress.c + + The compression function of the sha256 hash function. + + Copyright (C) 2001, 2010 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef SHA256_DEBUG +# define SHA256_DEBUG 0 +#endif + +#if SHA256_DEBUG +# include +# define DEBUG(i) \ + fprintf(stderr, "%2d: %8x %8x %8x %8x %8x %8x %8x %8x\n", \ + i, A, B, C, D ,E, F, G, H) +#else +# define DEBUG(i) +#endif + +#include +#include +#include + +#include "sha2.h" + +#include "macros.h" + +/* A block, treated as a sequence of 32-bit words. */ +#define SHA256_DATA_LENGTH 16 + +/* The SHA256 functions. The Choice function is the same as the SHA1 + function f1, and the majority function is the same as the SHA1 f3 + function. They can be optimized to save one boolean operation each + - thanks to Rich Schroeppel, rcs@cs.arizona.edu for discovering + this */ + +/* #define Choice(x,y,z) ( ( (x) & (y) ) | ( ~(x) & (z) ) ) */ +#define Choice(x,y,z) ( (z) ^ ( (x) & ( (y) ^ (z) ) ) ) +/* #define Majority(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) ) */ +#define Majority(x,y,z) ( ((x) & (y)) ^ ((z) & ((x) ^ (y))) ) + +#define S0(x) (ROTL32(30,(x)) ^ ROTL32(19,(x)) ^ ROTL32(10,(x))) +#define S1(x) (ROTL32(26,(x)) ^ ROTL32(21,(x)) ^ ROTL32(7,(x))) + +#define s0(x) (ROTL32(25,(x)) ^ ROTL32(14,(x)) ^ ((x) >> 3)) +#define s1(x) (ROTL32(15,(x)) ^ ROTL32(13,(x)) ^ ((x) >> 10)) + +/* The initial expanding function. The hash function is defined over an + 64-word expanded input array W, where the first 16 are copies of the input + data, and the remaining 64 are defined by + + W[ t ] = s1(W[t-2]) + W[t-7] + s0(W[i-15]) + W[i-16] + + This implementation generates these values on the fly in a circular + buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this + optimization. +*/ + +#define EXPAND(W,i) \ +( W[(i) & 15 ] += (s1(W[((i)-2) & 15]) + W[((i)-7) & 15] + s0(W[((i)-15) & 15])) ) + +/* The prototype SHA sub-round. The fundamental sub-round is: + + T1 = h + S1(e) + Choice(e,f,g) + K[t] + W[t] + T2 = S0(a) + Majority(a,b,c) + a' = T1+T2 + b' = a + c' = b + d' = c + e' = d + T1 + f' = e + g' = f + h' = g + + but this is implemented by unrolling the loop 8 times and renaming + the variables + ( h, a, b, c, d, e, f, g ) = ( a, b, c, d, e, f, g, h ) each + iteration. */ + +/* It's crucial that DATA is only used once, as that argument will + * have side effects. */ +#define ROUND(a,b,c,d,e,f,g,h,k,data) do { \ + h += S1(e) + Choice(e,f,g) + k + data; \ + d += h; \ + h += S0(a) + Majority(a,b,c); \ + } while (0) + +/* For fat builds */ +#if HAVE_NATIVE_sha256_compress +void +_nettle_sha256_compress_c(uint32_t *state, const uint8_t *input, const uint32_t *k); +#define _nettle_sha256_compress _nettle_sha256_compress_c +#endif + +void +_nettle_sha256_compress(uint32_t *state, const uint8_t *input, const uint32_t *k) +{ + uint32_t data[SHA256_DATA_LENGTH]; + uint32_t A, B, C, D, E, F, G, H; /* Local vars */ + unsigned i; + uint32_t *d; + + for (i = 0; i < SHA256_DATA_LENGTH; i++, input+= 4) + { + data[i] = READ_UINT32(input); + } + + /* Set up first buffer and local data buffer */ + A = state[0]; + B = state[1]; + C = state[2]; + D = state[3]; + E = state[4]; + F = state[5]; + G = state[6]; + H = state[7]; + + /* Heavy mangling */ + /* First 16 subrounds that act on the original data */ + + DEBUG(-1); + for (i = 0, d = data; i<16; i+=8, k += 8, d+= 8) + { + ROUND(A, B, C, D, E, F, G, H, k[0], d[0]); DEBUG(i); + ROUND(H, A, B, C, D, E, F, G, k[1], d[1]); DEBUG(i+1); + ROUND(G, H, A, B, C, D, E, F, k[2], d[2]); + ROUND(F, G, H, A, B, C, D, E, k[3], d[3]); + ROUND(E, F, G, H, A, B, C, D, k[4], d[4]); + ROUND(D, E, F, G, H, A, B, C, k[5], d[5]); + ROUND(C, D, E, F, G, H, A, B, k[6], d[6]); DEBUG(i+6); + ROUND(B, C, D, E, F, G, H, A, k[7], d[7]); DEBUG(i+7); + } + + for (; i<64; i += 16, k+= 16) + { + ROUND(A, B, C, D, E, F, G, H, k[ 0], EXPAND(data, 0)); DEBUG(i); + ROUND(H, A, B, C, D, E, F, G, k[ 1], EXPAND(data, 1)); DEBUG(i+1); + ROUND(G, H, A, B, C, D, E, F, k[ 2], EXPAND(data, 2)); DEBUG(i+2); + ROUND(F, G, H, A, B, C, D, E, k[ 3], EXPAND(data, 3)); DEBUG(i+3); + ROUND(E, F, G, H, A, B, C, D, k[ 4], EXPAND(data, 4)); DEBUG(i+4); + ROUND(D, E, F, G, H, A, B, C, k[ 5], EXPAND(data, 5)); DEBUG(i+5); + ROUND(C, D, E, F, G, H, A, B, k[ 6], EXPAND(data, 6)); DEBUG(i+6); + ROUND(B, C, D, E, F, G, H, A, k[ 7], EXPAND(data, 7)); DEBUG(i+7); + ROUND(A, B, C, D, E, F, G, H, k[ 8], EXPAND(data, 8)); DEBUG(i+8); + ROUND(H, A, B, C, D, E, F, G, k[ 9], EXPAND(data, 9)); DEBUG(i+9); + ROUND(G, H, A, B, C, D, E, F, k[10], EXPAND(data, 10)); DEBUG(i+10); + ROUND(F, G, H, A, B, C, D, E, k[11], EXPAND(data, 11)); DEBUG(i+11); + ROUND(E, F, G, H, A, B, C, D, k[12], EXPAND(data, 12)); DEBUG(i+12); + ROUND(D, E, F, G, H, A, B, C, k[13], EXPAND(data, 13)); DEBUG(i+13); + ROUND(C, D, E, F, G, H, A, B, k[14], EXPAND(data, 14)); DEBUG(i+14); + ROUND(B, C, D, E, F, G, H, A, k[15], EXPAND(data, 15)); DEBUG(i+15); + } + + /* Update state */ + state[0] += A; + state[1] += B; + state[2] += C; + state[3] += D; + state[4] += E; + state[5] += F; + state[6] += G; + state[7] += H; +#if SHA256_DEBUG + fprintf(stderr, "99: %8x %8x %8x %8x %8x %8x %8x %8x\n", + state[0], state[1], state[2], state[3], + state[4], state[5], state[6], state[7]); +#endif +} diff --git a/src/granger/src/nettle/sha256.c b/src/granger/src/nettle/sha256.c new file mode 100644 index 0000000..0cb3559 --- /dev/null +++ b/src/granger/src/nettle/sha256.c @@ -0,0 +1,162 @@ +/* sha256.c + + The sha256 hash function. + See http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + + Copyright (C) 2001 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +/* Modelled after the sha1.c code by Peter Gutmann. */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "sha2.h" + +#include "macros.h" +#include "nettle-write.h" + +/* Generated by the shadata program. */ +static const uint32_t +K[64] = +{ + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL, +}; + +#define COMPRESS(ctx, data) (_nettle_sha256_compress((ctx)->state, (data), K)) + +/* Initialize the SHA values */ + +void +sha256_init(struct sha256_ctx *ctx) +{ + /* Initial values, also generated by the shadata program. */ + static const uint32_t H0[_SHA256_DIGEST_LENGTH] = + { + 0x6a09e667UL, 0xbb67ae85UL, 0x3c6ef372UL, 0xa54ff53aUL, + 0x510e527fUL, 0x9b05688cUL, 0x1f83d9abUL, 0x5be0cd19UL, + }; + + memcpy(ctx->state, H0, sizeof(H0)); + + /* Initialize bit count */ + ctx->count = 0; + + /* Initialize buffer */ + ctx->index = 0; +} + +void +sha256_update(struct sha256_ctx *ctx, + size_t length, const uint8_t *data) +{ + MD_UPDATE (ctx, length, data, COMPRESS, ctx->count++); +} + +static void +sha256_write_digest(struct sha256_ctx *ctx, + size_t length, + uint8_t *digest) +{ + uint64_t bit_count; + + assert(length <= SHA256_DIGEST_SIZE); + + MD_PAD(ctx, 8, COMPRESS); + + /* There are 512 = 2^9 bits in one block */ + bit_count = (ctx->count << 9) | (ctx->index << 3); + + /* This is slightly inefficient, as the numbers are converted to + big-endian format, and will be converted back by the compression + function. It's probably not worth the effort to fix this. */ + WRITE_UINT64(ctx->block + (SHA256_BLOCK_SIZE - 8), bit_count); + COMPRESS(ctx, ctx->block); + + _nettle_write_be32(length, digest, ctx->state); +} + +void +sha256_digest(struct sha256_ctx *ctx, + size_t length, + uint8_t *digest) +{ + sha256_write_digest(ctx, length, digest); + sha256_init(ctx); +} + +/* sha224 variant. */ + +void +sha224_init(struct sha256_ctx *ctx) +{ + /* Initial values. Low 32 bits of the initial values for sha384. */ + static const uint32_t H0[_SHA256_DIGEST_LENGTH] = + { + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4, + }; + + memcpy(ctx->state, H0, sizeof(H0)); + + /* Initialize bit count */ + ctx->count = 0; + + /* Initialize buffer */ + ctx->index = 0; +} + +void +sha224_digest(struct sha256_ctx *ctx, + size_t length, + uint8_t *digest) +{ + sha256_write_digest(ctx, length, digest); + sha224_init(ctx); +} diff --git a/src/granger/src/nettle/version.h b/src/granger/src/nettle/version.h new file mode 100644 index 0000000..3a1d20c --- /dev/null +++ b/src/granger/src/nettle/version.h @@ -0,0 +1,58 @@ +/* version.h + + Information about library version. + + Copyright (C) 2015 Red Hat, Inc. + Copyright (C) 2015 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_VERSION_H_INCLUDED +#define NETTLE_VERSION_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/* Individual version numbers in decimal */ +#define NETTLE_VERSION_MAJOR 3 +#define NETTLE_VERSION_MINOR 3 + +#define NETTLE_USE_MINI_GMP 1 + +/* We need a preprocessor constant for GMP_NUMB_BITS, simply using + sizeof(mp_limb_t) * CHAR_BIT is not good enough. */ +#if NETTLE_USE_MINI_GMP +# define GMP_NUMB_BITS (sizeof(unsigned long) * 8) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_VERSION_H_INCLUDED */ diff --git a/src/granger/src/nettle/write-be32.c b/src/granger/src/nettle/write-be32.c new file mode 100644 index 0000000..7d68905 --- /dev/null +++ b/src/granger/src/nettle/write-be32.c @@ -0,0 +1,77 @@ +/* write-be32.c + + Copyright (C) 2001 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "nettle-write.h" + +#include "macros.h" + +void +_nettle_write_be32(size_t length, uint8_t *dst, + uint32_t *src) +{ + size_t i; + size_t words; + unsigned leftover; + + words = length / 4; + leftover = length % 4; + + for (i = 0; i < words; i++, dst += 4) + WRITE_UINT32(dst, src[i]); + + if (leftover) + { + uint32_t word; + unsigned j = leftover; + + word = src[i]; + + switch (leftover) + { + default: + abort(); + case 3: + dst[--j] = (word >> 8) & 0xff; + /* Fall through */ + case 2: + dst[--j] = (word >> 16) & 0xff; + /* Fall through */ + case 1: + dst[--j] = (word >> 24) & 0xff; + } + } +} diff --git a/src/granger/src/nettle/write-le32.c b/src/granger/src/nettle/write-le32.c new file mode 100644 index 0000000..d44eb24 --- /dev/null +++ b/src/granger/src/nettle/write-le32.c @@ -0,0 +1,69 @@ +/* write-le32.c + + Copyright (C) 2001, 2011 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "nettle-write.h" + +#include "macros.h" + +void +_nettle_write_le32(size_t length, uint8_t *dst, + uint32_t *src) +{ + size_t i; + size_t words; + unsigned leftover; + + words = length / 4; + leftover = length % 4; + + for (i = 0; i < words; i++, dst += 4) + LE_WRITE_UINT32(dst, src[i]); + + if (leftover) + { + uint32_t word; + + word = src[i]; + + do + { + *dst++ = word & 0xff; + word >>= 8; + } + while (--leftover); + } +} diff --git a/src/granger/src/premake/CMakeLists.txt b/src/granger/src/premake/CMakeLists.txt new file mode 100644 index 0000000..455a9e2 --- /dev/null +++ b/src/granger/src/premake/CMakeLists.txt @@ -0,0 +1,32 @@ +if(NOT WIN32) + add_definitions(-Wall -Wextra) +endif(NOT WIN32) + +if(WIN32) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif(WIN32) + +add_library(premake + os_access.c + os_chdir.c + os_copyfile.c + os_elevate.c + os_getcwd.c + os_is64bit.c + os_isdir.c + os_isfile.c + os_match.c + os_mkdir.c + os_pathsearch.c + os_rmdir.c + os_stat.c + path_getabsolute.c + path_getrelative.c + path_isabsolute.c + path_join.c + path_normalize.c + path_translate.c + premake.c + premake.h + string_endswith.c + ) diff --git a/src/granger/src/premake/os_access.c b/src/granger/src/premake/os_access.c new file mode 100644 index 0000000..90ceb46 --- /dev/null +++ b/src/granger/src/premake/os_access.c @@ -0,0 +1,58 @@ +/* + * This file is part of Granger. + * Copyright (c) 2016 Jeff Kent + * + * Granger 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 3 of the License, or + * (at your option) any later version. + * + * Granger 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 Granger. If not, see . + */ + +#include "premake.h" + +#if defined(PLATFORM_WINDOWS) +#include +#define access _access +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 +#endif + +int os_access(lua_State *L) +{ + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + int nmode = 0; + int result; + + while (*mode) { + switch (*mode) { + case 'x': + nmode |= X_OK; + break; + case 'w': + nmode |= W_OK; + break; + case 'r': + nmode |= R_OK; + break; + default: + lua_pushboolean(L, 0); + return 1; + } + mode++; + } + + result = access(filename, nmode); + + lua_pushboolean(L, result == 0); + return 1; +} diff --git a/src/granger/src/premake/os_chdir.c b/src/granger/src/premake/os_chdir.c new file mode 100644 index 0000000..3acce68 --- /dev/null +++ b/src/granger/src/premake/os_chdir.c @@ -0,0 +1,32 @@ +/** + * \file os_chdir.c + * \brief Change the current working directory. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include "premake.h" + + +int os_chdir(lua_State* L) +{ + int z; + const char* path = luaL_checkstring(L, 1); + +#if PLATFORM_WINDOWS + z = SetCurrentDirectory(path); +#else + z = !chdir(path); +#endif + + if (!z) + { + lua_pushnil(L); + lua_pushfstring(L, "unable to switch to directory '%s'", path); + return 2; + } + else + { + lua_pushboolean(L, 1); + return 1; + } +} diff --git a/src/granger/src/premake/os_copyfile.c b/src/granger/src/premake/os_copyfile.c new file mode 100644 index 0000000..b15d600 --- /dev/null +++ b/src/granger/src/premake/os_copyfile.c @@ -0,0 +1,34 @@ +/** + * \file os_copyfile.c + * \brief Copy a file from one location to another. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include +#include "premake.h" + +int os_copyfile(lua_State* L) +{ + int z; + const char* src = luaL_checkstring(L, 1); + const char* dst = luaL_checkstring(L, 2); + +#if PLATFORM_WINDOWS + z = CopyFile(src, dst, FALSE); +#else + lua_pushfstring(L, "cp %s %s", src, dst); + z = (system(lua_tostring(L, -1)) == 0); +#endif + + if (!z) + { + lua_pushnil(L); + lua_pushfstring(L, "unable to copy file to '%s'", dst); + return 2; + } + else + { + lua_pushboolean(L, 1); + return 1; + } +} diff --git a/src/granger/src/premake/os_elevate.c b/src/granger/src/premake/os_elevate.c new file mode 100644 index 0000000..cab0906 --- /dev/null +++ b/src/granger/src/premake/os_elevate.c @@ -0,0 +1,240 @@ +/* + * This file is part of Granger. + * Copyright (c) 2016 Jeff Kent + * + * Granger 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 3 of the License, or + * (at your option) any later version. + * + * Granger 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 Granger. If not, see . + */ + +#include +#include +#include +#include "premake.h" +#include "../strvec.h" + +#if defined(PLATFORM_WINDOWS) +#include +static int do_elevate(lua_State *L) +{ + BOOL is_elevated = FALSE; + HANDLE hToken = NULL; + + if (OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken)) { + TOKEN_ELEVATION Elevation; + DWORD cbSize = sizeof(TOKEN_ELEVATION); + if (GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize)) { + is_elevated = Elevation.TokenIsElevated; + } + } + if (hToken) { + CloseHandle(hToken); + } + if (is_elevated) { + return 1; + } + + char szPath[MAX_PATH]; + if (!GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) { + return 0; + } + + void *sv = StringVector_New(); + + lua_getglobal(L, "_GRANGER_SCRIPT"); + StringVector_Add(sv, luaL_checkstring(L, 1)); + lua_pop(L, 1); + + lua_getglobal(L, "argv"); + if (luaL_len(L, 1) > 0) { + StringVector_Add(sv, "--"); + } + + for (int i = 1; ; i++) { + lua_pushinteger(L, i); + lua_gettable(L, 1); + if (lua_isnil(L, -1)) { + break; + } + StringVector_Add(sv, luaL_checkstring(L, -1)); + lua_pop(L, 1); + } + lua_pop(L, 1); + + SHELLEXECUTEINFO sei = { 0 }; + sei.cbSize = sizeof(SHELLEXECUTEINFO); + sei.lpVerb = "runas"; + sei.lpFile = szPath; + sei.lpParameters = StringVector_toString(sv); + sei.nShow = SW_NORMAL; + + StringVector_Delete(sv); + + if (ShellExecuteEx(&sei)) { + free((void*)sei.lpParameters); + exit(0); + } + + free((void*)sei.lpParameters); + return 0; +} +#endif + +#if defined(PLATFORM_MACOSX) +#include +#include + +static int do_elevate(lua_State *L) +{ + const char *execpath; + int argc = 0; + char **argv; + + if (geteuid() == 0) { + return 1; + } + + lua_getglobal(L, "_EXE_PATH"); + execpath = luaL_checkstring(L, 1); + + OSStatus err; + AuthorizationRef ref; + AuthorizationFlags flags; + + flags = kAuthorizationFlagDefaults; + err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, flags, &ref); + + if (err != errAuthorizationSuccess) { + return 0; + } + + AuthorizationItem _temp = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights rights = {1, &_temp}; + + flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + + err = AuthorizationCopyRights(ref, &rights, NULL, flags, NULL); + if (err != errAuthorizationSuccess) { + AuthorizationFree(ref, kAuthorizationFlagDefaults); + return 0; + } + + void *sv = StringVector_New(); + + lua_getglobal(L, "_GRANGER_SCRIPT"); + StringVector_Add(sv, luaL_checkstring(L, 1)); + lua_pop(L, 1); + + lua_getglobal(L, "argv"); + if (luaL_len(L, 1) > 0) { + StringVector_Add(sv, "--"); + } + + for (int i = 1; ; i++) { + lua_pushinteger(L, i); + lua_gettable(L, 1); + if (lua_isnil(L, -1)) { + break; + } + StringVector_Add(sv, luaL_checkstring(L, -1)); + lua_pop(L, 1); + } + lua_pop(L, 1); + + flags = kAuthorizationFlagDefaults; + err = AuthorizationExecuteWithPrivileges(ref, execpath, flags, StringVector_GetVector(sv), NULL); + AuthorizationFree(ref, kAuthorizationFlagDefaults); + if (err != errAuthorizationSuccess) { + StringVector_Delete(sv); + return 0; + } + + StringVector_Delete(sv); + exit(0); +} +#endif + +#if defined(PLATFORM_LINUX) || defined(PLATFORM_BSD) +static int do_elevate(lua_State *L) +{ + char *pkexec, *display; + char cwd[PATH_MAX + 1]; + + if (geteuid() == 0) { + return 1; + } + + display = getenv("DISPLAY"); + if (!display) { + return 0; + } + + if (access("/usr/bin/pkexec", X_OK) != -1) { + pkexec = "/usr/bin/pkexec"; + } else if (access("/usr/local/bin/pkexec", X_OK) != -1) { + pkexec = "/usr/local/bin/pkexec"; + } else { + return 0; + } + + if (!getcwd(cwd, sizeof(cwd))) { + return 0; + } + + void *sv = StringVector_New(); + StringVector_Add(sv, "pkexec"); + StringVector_Add(sv, "--user"); + StringVector_Add(sv, "root"); + + lua_getglobal(L, "_EXE_PATH"); + StringVector_Add(sv, luaL_checkstring(L, 1)); + lua_pop(L, 1); + + StringVector_Add(sv, "-C"); + StringVector_Add(sv, cwd); + + lua_getglobal(L, "_GRANGER_SCRIPT"); + StringVector_Add(sv, luaL_checkstring(L, 1)); + lua_pop(L, 1); + + lua_getglobal(L, "argv"); + if (luaL_len(L, 1) > 0) { + StringVector_Add(sv, "--"); + } + + for (int i = 1; ; i++) { + lua_pushinteger(L, i); + lua_gettable(L, 1); + if (lua_isnil(L, -1)) { + break; + } + StringVector_Add(sv, luaL_checkstring(L, -1)); + lua_pop(L, 1); + } + lua_pop(L, 1); + + execv(pkexec, (char * const*)StringVector_GetVector(sv)); + + fprintf(stderr, "execv failed\n"); + lua_pop(L, 1); + return 0; +} +#endif + +int os_elevate(lua_State* L) +{ + lua_pushboolean(L, do_elevate(L)); + return 1; +} diff --git a/src/granger/src/premake/os_getcwd.c b/src/granger/src/premake/os_getcwd.c new file mode 100644 index 0000000..de8bfbd --- /dev/null +++ b/src/granger/src/premake/os_getcwd.c @@ -0,0 +1,36 @@ +/** + * \file os_getcwd.c + * \brief Retrieve the current working directory. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include "premake.h" + +int os_getcwd(lua_State* L) +{ + char buffer[0x4000]; + if (do_getcwd(buffer, 0x4000)) { + lua_pushstring(L, buffer); + return 1; + } + else { + return 0; + } +} + + +int do_getcwd(char* buffer, size_t size) +{ + int result; + +#if PLATFORM_WINDOWS + result = (GetCurrentDirectory(size, buffer) != 0); + if (result) { + do_translate(buffer, '/'); + } +#else + result = (getcwd(buffer, size) != 0); +#endif + + return result; +} diff --git a/src/granger/src/premake/os_is64bit.c b/src/granger/src/premake/os_is64bit.c new file mode 100644 index 0000000..3134751 --- /dev/null +++ b/src/granger/src/premake/os_is64bit.c @@ -0,0 +1,30 @@ +/** + * \file os_is64bit.c + * \brief Native code-side checking for a 64-bit architecture. + * \author Copyright (c) 2011 Jason Perkins and the Premake project + */ + +#include "premake.h" + +int os_is64bit(lua_State* L) +{ + // If this code returns true, then the platform is 64-bit. If it + // returns false, the platform might still be 64-bit, but more + // checking will need to be done on the Lua side of things. +#if PLATFORM_WINDOWS + typedef BOOL (WINAPI* WowFuncSig)(HANDLE, PBOOL); + WowFuncSig func = (WowFuncSig)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); + if (func) + { + BOOL isWow = FALSE; + if (func(GetCurrentProcess(), &isWow)) + { + lua_pushboolean(L, isWow); + return 1; + } + } +#endif + + lua_pushboolean(L, 0); + return 1; +} diff --git a/src/granger/src/premake/os_isdir.c b/src/granger/src/premake/os_isdir.c new file mode 100644 index 0000000..fb5e8bb --- /dev/null +++ b/src/granger/src/premake/os_isdir.c @@ -0,0 +1,34 @@ +/** + * \file os_isdir.c + * \brief Returns true if the specified directory exists. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include +#include +#include "premake.h" + + +int os_isdir(lua_State* L) +{ + struct stat buf; + const char* path = luaL_checkstring(L, 1); + + /* empty path is equivalent to ".", must be true */ + if (strlen(path) == 0) + { + lua_pushboolean(L, 1); + } + else if (stat(path, &buf) == 0) + { + lua_pushboolean(L, buf.st_mode & S_IFDIR); + } + else + { + lua_pushboolean(L, 0); + } + + return 1; +} + + diff --git a/src/granger/src/premake/os_isfile.c b/src/granger/src/premake/os_isfile.c new file mode 100644 index 0000000..61e0def --- /dev/null +++ b/src/granger/src/premake/os_isfile.c @@ -0,0 +1,30 @@ +/** + * \file os_isfile.c + * \brief Returns true if the given file exists on the file system. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include +#include "premake.h" + + +int os_isfile(lua_State* L) +{ + const char* filename = luaL_checkstring(L, 1); + lua_pushboolean(L, do_isfile(filename)); + return 1; +} + + +int do_isfile(const char* filename) +{ + struct stat buf; + if (stat(filename, &buf) == 0) + { + return ((buf.st_mode & S_IFDIR) == 0); + } + else + { + return 0; + } +} diff --git a/src/granger/src/premake/os_match.c b/src/granger/src/premake/os_match.c new file mode 100644 index 0000000..5ccc04d --- /dev/null +++ b/src/granger/src/premake/os_match.c @@ -0,0 +1,181 @@ +/** + * \file os_match.c + * \brief Match files and directories. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include +#include +#include "premake.h" + + +#if PLATFORM_WINDOWS + +#define WIN32_LEAN_AND_MEAN +#include + +typedef struct struct_MatchInfo +{ + HANDLE handle; + int is_first; + WIN32_FIND_DATA entry; +} MatchInfo; + +int os_matchstart(lua_State* L) +{ + const char* mask = luaL_checkstring(L, 1); + MatchInfo* m = (MatchInfo*)malloc(sizeof(MatchInfo)); + m->handle = FindFirstFile(mask, &m->entry); + m->is_first = 1; + lua_pushlightuserdata(L, m); + return 1; +} + +int os_matchdone(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + if (m->handle != INVALID_HANDLE_VALUE) + FindClose(m->handle); + free(m); + return 0; +} + +int os_matchname(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + lua_pushstring(L, m->entry.cFileName); + return 1; +} + +int os_matchisfile(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + lua_pushboolean(L, (m->entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0); + return 1; +} + +int os_matchnext(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + if (m->handle == INVALID_HANDLE_VALUE) { + return 0; + } + + while (m) /* loop forever */ + { + if (!m->is_first) + { + if (!FindNextFile(m->handle, &m->entry)) + return 0; + } + + m->is_first = 0; + lua_pushboolean(L, 1); + return 1; + } + + return 0; +} + +#else + +#include +#include +#include + +typedef struct struct_MatchInfo +{ + DIR* handle; + struct dirent* entry; + char* path; + char* mask; +} MatchInfo; + +int os_matchstart(lua_State* L) +{ + const char* split; + const char* mask = luaL_checkstring(L, 1); + MatchInfo* m = (MatchInfo*)malloc(sizeof(MatchInfo)); + + /* split the mask into path and filename components */ + split = strrchr(mask, '/'); + if (split) + { + m->path = (char*)malloc(split - mask + 1); + strncpy(m->path, mask, split - mask); + m->path[split - mask] = '\0'; + m->mask = (char*)malloc(mask + strlen(mask) - split); + strcpy(m->mask, split + 1); + } + else + { + m->path = (char*)malloc(2); + strcpy(m->path, "."); + m->mask = (char*)malloc(strlen(mask)+1); + strcpy(m->mask, mask); + } + + m->handle = opendir(m->path); + lua_pushlightuserdata(L, m); + return 1; +} + +int os_matchdone(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + if (m->handle != NULL) + closedir(m->handle); + free(m->path); + free(m->mask); + free(m); + return 0; +} + +int os_matchname(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + lua_pushstring(L, m->entry->d_name); + return 1; +} + +int os_matchisfile(lua_State* L) +{ + struct stat info; + const char* fname; + + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + lua_pushfstring(L, "%s/%s", m->path, m->entry->d_name); + fname = lua_tostring(L, -1); + lua_pop(L, 1); + + if (stat(fname, &info) == 0) + { + lua_pushboolean(L, S_ISREG(info.st_mode)); + return 1; + } + + return 0; +} + +int os_matchnext(lua_State* L) +{ + MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1); + if (m->handle == NULL) + return 0; + + m->entry = readdir(m->handle); + while (m->entry != NULL) + { + const char* name = m->entry->d_name; + if (fnmatch(m->mask, name, 0) == 0) + { + lua_pushboolean(L, 1); + return 1; + } + m->entry = readdir(m->handle); + } + + return 0; +} + +#endif diff --git a/src/granger/src/premake/os_mkdir.c b/src/granger/src/premake/os_mkdir.c new file mode 100644 index 0000000..a7a69db --- /dev/null +++ b/src/granger/src/premake/os_mkdir.c @@ -0,0 +1,33 @@ +/** + * \file os_mkdir.c + * \brief Create a subdirectory. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + */ + +#include +#include "premake.h" + + +int os_mkdir(lua_State* L) +{ + int z; + const char* path = luaL_checkstring(L, 1); + +#if PLATFORM_WINDOWS + z = CreateDirectory(path, NULL); +#else + z = (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0); +#endif + + if (!z) + { + lua_pushnil(L); + lua_pushfstring(L, "unable to create directory '%s'", path); + return 2; + } + else + { + lua_pushboolean(L, 1); + return 1; + } +} diff --git a/src/granger/src/premake/os_pathsearch.c b/src/granger/src/premake/os_pathsearch.c new file mode 100644 index 0000000..44f775d --- /dev/null +++ b/src/granger/src/premake/os_pathsearch.c @@ -0,0 +1,84 @@ +/** + * \file os_pathsearch.c + * \brief Locates a file, given a set of search paths. + * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project + * + * \note This function is required by the bootstrapping code; it must be + * implemented here in the host and not scripted. + */ + +#include +#include "premake.h" + + +int os_pathsearch(lua_State* L) +{ + int i; + for (i = 2; i <= lua_gettop(L); ++i) + { + const char* path; + + if (lua_isnil(L, i)) + continue; + + path = luaL_checkstring(L, i); + do + { + const char* split; + + /* look for the closest path separator ; or : */ + /* can't use : on windows because it breaks on C:\path */ + const char* semi = strchr(path, ';'); +#if !defined(PLATFORM_WINDOWS) + const char* full = strchr(path, ':'); +#else + const char* full = NULL; +#endif + + if (!semi) + { + split = full; + } + else if (!full) + { + split = semi; + } + else + { + split = (semi < full) ? semi : full; + } + + /* push this piece of the full search string onto the stack */ + if (split) + { + lua_pushlstring(L, path, split - path); + } + else + { + lua_pushstring(L, path); + } + + /* keep an extra copy around, so I can return it if I have a match */ + lua_pushvalue(L, -1); + + /* append the filename to make the full test path */ + lua_pushstring(L, "/"); + lua_pushvalue(L, 1); + lua_concat(L, 3); + + /* test it - if it exists return the path */ + if (do_isfile(lua_tostring(L, -1))) + { + lua_pop(L, 1); + return 1; + } + + /* no match, set up the next try */ + lua_pop(L, 2); + path = (split) ? split + 1 : NULL; + } + while (path); + } + + return 0; +} diff --git a/src/granger/src/premake/os_rmdir.c b/src/granger/src/premake/os_rmdir.c new file mode 100644 index 0000000..f2ffeee --- /dev/null +++ b/src/granger/src/premake/os_rmdir.c @@ -0,0 +1,33 @@ +/** + * \file os_rmdir.c + * \brief Remove a subdirectory. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include +#include "premake.h" + + +int os_rmdir(lua_State* L) +{ + int z; + const char* path = luaL_checkstring(L, 1); + +#if PLATFORM_WINDOWS + z = RemoveDirectory(path); +#else + z = (0 == rmdir(path)); +#endif + + if (!z) + { + lua_pushnil(L); + lua_pushfstring(L, "unable to remove directory '%s'", path); + return 2; + } + else + { + lua_pushboolean(L, 1); + return 1; + } +} diff --git a/src/granger/src/premake/os_stat.c b/src/granger/src/premake/os_stat.c new file mode 100644 index 0000000..b3554dc --- /dev/null +++ b/src/granger/src/premake/os_stat.c @@ -0,0 +1,46 @@ +/** + * \file os_stat.c + * \brief Retrieve information about a file. + * \author Copyright (c) 2011 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include +#include + +int os_stat(lua_State* L) +{ + struct stat s; + + const char* filename = luaL_checkstring(L, 1); + if (stat(filename, &s) != 0) + { + lua_pushnil(L); + switch (errno) + { + case EACCES: + lua_pushfstring(L, "'%s' could not be accessed", filename); + break; + case ENOENT: + lua_pushfstring(L, "'%s' was not found", filename); + break; + default: + lua_pushfstring(L, "An unknown error %d occured while accessing '%s'", errno, filename); + break; + } + return 2; + } + + + lua_newtable(L); + + lua_pushstring(L, "mtime"); + lua_pushinteger(L, (lua_Integer)s.st_mtime); + lua_settable(L, -3); + + lua_pushstring(L, "size"); + lua_pushnumber(L, s.st_size); + lua_settable(L, -3); + + return 1; +} diff --git a/src/granger/src/premake/path_getabsolute.c b/src/granger/src/premake/path_getabsolute.c new file mode 100644 index 0000000..36b1c3a --- /dev/null +++ b/src/granger/src/premake/path_getabsolute.c @@ -0,0 +1,102 @@ +/** + * \file path_getabsolute.c + * \brief Returns an absolute version of a relative path. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +void do_getabsolute(char* result, const char* value, const char* relative_to) +{ + int i; + char* ch; + char* prev; + char buffer[0x4000] = { '\0' }; + + /* if the path is not already absolute, base it on working dir */ + if (!do_isabsolute(value)) { + if (relative_to) { + strcpy(buffer, relative_to); + } + else { + do_getcwd(buffer, 0x4000); + } + strcat(buffer, "/"); + } + + /* normalize the path */ + strcat(buffer, value); + do_translate(buffer, '/'); + + /* process it part by part */ + result[0] = '\0'; + if (buffer[0] == '/') { + strcat(result, "/"); + } + + prev = NULL; + ch = strtok(buffer, "/"); + while (ch) { + /* remove ".." where I can */ + if (strcmp(ch, "..") == 0 && (prev == NULL || (prev[0] != '$' && strcmp(prev, "..") != 0))) { + i = strlen(result) - 2; + while (i >= 0 && result[i] != '/') { + --i; + } + if (i >= 0) { + result[i + 1] = '\0'; + } + ch = NULL; + } + + /* allow everything except "." */ + else if (strcmp(ch, ".") != 0) { + strcat(result, ch); + strcat(result, "/"); + } + + prev = ch; + ch = strtok(NULL, "/"); + } + + /* remove trailing slash */ + i = strlen(result) - 1; + if (result[i] == '/') { + result[i] = '\0'; + } +} + + +int path_getabsolute(lua_State* L) +{ + const char* relative_to; + char buffer[0x4000]; + + relative_to = NULL; + if (lua_gettop(L) > 1 && !lua_isnil(L,2)) { + relative_to = luaL_checkstring(L, 2); + } + + if (lua_istable(L, 1)) { + int i = 0; + lua_newtable(L); + lua_pushnil(L); + while (lua_next(L, 1)) { + const char* value = luaL_checkstring(L, -1); + do_getabsolute(buffer, value, relative_to); + lua_pop(L, 1); + + lua_pushstring(L, buffer); + lua_rawseti(L, -3, ++i); + } + return 1; + } + else { + const char* value = luaL_checkstring(L, 1); + do_getabsolute(buffer, value, relative_to); + lua_pushstring(L, buffer); + return 1; + } +} diff --git a/src/granger/src/premake/path_getrelative.c b/src/granger/src/premake/path_getrelative.c new file mode 100644 index 0000000..dc6629b --- /dev/null +++ b/src/granger/src/premake/path_getrelative.c @@ -0,0 +1,80 @@ +/** + * \file path_getrelative.c + * \brief Returns a path relative to another. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int path_getrelative(lua_State* L) +{ + int i, last, count; + char src[0x4000]; + char dst[0x4000]; + + const char* p1 = luaL_checkstring(L, 1); + const char* p2 = luaL_checkstring(L, 2); + + /* normalize the paths */ + do_getabsolute(src, p1, NULL); + do_getabsolute(dst, p2, NULL); + + /* same directory? */ + if (strcmp(src, dst) == 0) { + lua_pushstring(L, "."); + return 1; + } + + /* dollar macro? Can't tell what the real path might be, so treat + * as absolute. This enables paths like $(SDK_ROOT)/include to + * work as expected. */ + if (dst[0] == '$') { + lua_pushstring(L, dst); + return 1; + } + + /* find the common leading directories */ + strcat(src, "/"); + strcat(dst, "/"); + + last = -1; + i = 0; + while (src[i] && dst[i] && src[i] == dst[i]) { + if (src[i] == '/') { + last = i; + } + ++i; + } + + /* if I end up with just the root of the filesystem, either a single + * slash (/) or a drive letter (c:) then return the absolute path. */ + if (last <= 0 || (last == 2 && src[1] == ':')) { + dst[strlen(dst) - 1] = '\0'; + lua_pushstring(L, dst); + return 1; + } + + /* count remaining levels in src */ + count = 0; + for (i = last + 1; src[i] != '\0'; ++i) { + if (src[i] == '/') { + ++count; + } + } + + /* start my result by backing out that many levels */ + src[0] = '\0'; + for (i = 0; i < count; ++i) { + strcat(src, "../"); + } + + /* append what's left */ + strcat(src, dst + last + 1); + + /* remove trailing slash and done */ + src[strlen(src) - 1] = '\0'; + lua_pushstring(L, src); + return 1; +} diff --git a/src/granger/src/premake/path_isabsolute.c b/src/granger/src/premake/path_isabsolute.c new file mode 100644 index 0000000..7412935 --- /dev/null +++ b/src/granger/src/premake/path_isabsolute.c @@ -0,0 +1,27 @@ +/** + * \file path_isabsolute.c + * \brief Determines if a path is absolute or relative. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" + + +int path_isabsolute(lua_State* L) +{ + const char* path = luaL_checkstring(L, -1); + lua_pushboolean(L, do_isabsolute(path)); + return 1; +} + + +int do_isabsolute(const char* path) +{ + return ( + path[0] == '/' || + path[0] == '\\' || + path[0] == '$' || + (path[0] == '"' && path[1] == '$') || + (path[0] != '\0' && path[1] == ':') + ); +} diff --git a/src/granger/src/premake/path_join.c b/src/granger/src/premake/path_join.c new file mode 100644 index 0000000..7d80104 --- /dev/null +++ b/src/granger/src/premake/path_join.c @@ -0,0 +1,58 @@ +/** + * \file path_join.c + * \brief Join two or more pieces of a file system path. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int path_join(lua_State* L) +{ + int i, len; + const char* part; + char buffer[0x4000]; + char* ptr = buffer; + + /* for each argument... */ + int argc = lua_gettop(L); + for (i = 1; i <= argc; ++i) { + /* if next argument is nil, skip it */ + if (lua_isnil(L, i)) { + continue; + } + + /* grab the next argument */ + part = luaL_checkstring(L, i); + len = strlen(part); + + /* remove trailing slashes */ + while (len > 1 && part[len - 1] == '/') { + --len; + } + + /* ignore empty segments and "." */ + if (len == 0 || (len == 1 && part[0] == '.')) { + continue; + } + + /* if I encounter an absolute path, restart my result */ + if (do_isabsolute(part)) { + ptr = buffer; + } + + /* if the path is already started, split parts */ + if (ptr != buffer && *(ptr - 1) != '/') { + *(ptr++) = '/'; + } + + /* append new part */ + strcpy(ptr, part); + ptr += len; + } + + *ptr = '\0'; + lua_pushstring(L, buffer); + return 1; +} diff --git a/src/granger/src/premake/path_normalize.c b/src/granger/src/premake/path_normalize.c new file mode 100644 index 0000000..64c5cf2 --- /dev/null +++ b/src/granger/src/premake/path_normalize.c @@ -0,0 +1,77 @@ +/** + * \file path_normalize.c + * \brief Removes any weirdness from a file system path string. + * \author Copyright (c) 2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int path_normalize(lua_State* L) +{ + char buffer[0x4000]; + char* src; + char* dst; + char last; + + const char* path = luaL_checkstring(L, 1); + strcpy(buffer, path); + + src = buffer; + dst = buffer; + last = '\0'; + + while (*src != '\0') { + char ch = (*src); + + /* make sure we're using '/' for all separators */ + if (ch == '\\') { + ch = '/'; + } + + /* add to the result, filtering out duplicate slashes */ + if (ch != '/' || last != '/') { + *(dst++) = ch; + } + + /* ...except at the start of a string, for UNC paths */ + if (src != buffer) { + last = (*src); + } + + ++src; + } + + /* remove any trailing slashes */ + for (--src; src > buffer && *src == '/'; --src) { + *src = '\0'; + } + + /* remove any leading "./" sequences */ + src = buffer; + while (strncmp(src, "./", 2) == 0) { + src += 2; + } + + *dst = '\0'; + lua_pushstring(L, src); + return 1; +} + + +/* Call the scripted path.normalize(), to allow for overrides */ +void do_normalize(lua_State* L, char* buffer, const char* path) +{ + int top = lua_gettop(L); + + lua_getglobal(L, "path"); + lua_getfield(L, -1, "normalize"); + lua_pushstring(L, path); + lua_call(L, 1, 1); + + path = luaL_checkstring(L, -1); + strcpy(buffer, path); + + lua_settop(L, top); +} diff --git a/src/granger/src/premake/path_translate.c b/src/granger/src/premake/path_translate.c new file mode 100644 index 0000000..8996b37 --- /dev/null +++ b/src/granger/src/premake/path_translate.c @@ -0,0 +1,61 @@ +/** + * \file path_translate.c + * \brief Translates between path separators. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +void do_translate(char* value, const char sep) +{ + char* ch; + for (ch = value; *ch != '\0'; ++ch) { + if (*ch == '/' || *ch == '\\') { + *ch = sep; + } + } +} + + +static void translate(char* result, const char* value, const char sep) +{ + strcpy(result, value); + do_translate(result, sep); +} + + +int path_translate(lua_State* L) +{ + const char* sep; + char buffer[0x4000]; + + if (lua_gettop(L) == 1) { + sep = "\\"; + } + else { + sep = luaL_checkstring(L, 2); + } + + if (lua_istable(L, 1)) { + int i = 0; + lua_newtable(L); + lua_pushnil(L); + while (lua_next(L, 1)) { + const char* value = luaL_checkstring(L, 4); + translate(buffer, value, sep[0]); + lua_pop(L, 1); + + lua_pushstring(L, buffer); + lua_rawseti(L, -3, ++i); + } + return 1; + } + else { + const char* value = luaL_checkstring(L, 1); + translate(buffer, value, sep[0]); + lua_pushstring(L, buffer); + return 1; + } +} diff --git a/src/granger/src/premake/premake.c b/src/granger/src/premake/premake.c new file mode 100644 index 0000000..19a6168 --- /dev/null +++ b/src/granger/src/premake/premake.c @@ -0,0 +1,171 @@ +/** + * \file premake.c + * \brief Program entry point. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include +#include +#include "premake.h" + +#if PLATFORM_MACOSX +#include +#endif + +int premake_locate(lua_State* L, const char* argv0); + +/* Built-in functions */ +static const luaL_Reg path_functions[] = { + { "getabsolute", path_getabsolute }, + { "getrelative", path_getrelative }, + { "isabsolute", path_isabsolute }, + { "join", path_join }, + { "normalize", path_normalize }, + { "translate", path_translate }, + { NULL, NULL } +}; + +static const luaL_Reg os_functions[] = { + { "access", os_access }, + { "chdir", os_chdir }, + { "copyfile", os_copyfile }, + { "elevate", os_elevate }, + { "getcwd", os_getcwd }, + { "_is64bit", os_is64bit }, + { "isdir", os_isdir }, + { "isfile", os_isfile }, + { "matchdone", os_matchdone }, + { "matchisfile", os_matchisfile }, + { "matchname", os_matchname }, + { "matchnext", os_matchnext }, + { "matchstart", os_matchstart }, + { "mkdir", os_mkdir }, + { "pathsearch", os_pathsearch }, + { "rmdir", os_rmdir }, + { "stat", os_stat }, + { NULL, NULL } +}; + +static const luaL_Reg string_functions[] = { + { "endswith", string_endswith }, + { NULL, NULL } +}; + + +/** + * Initialize the Premake Lua environment. + */ +int premake_init(lua_State* L) +{ + if (lua_getglobal(L, "path") != LUA_TTABLE) { + lua_pop(L, 1); + lua_newtable(L); + } + luaL_setfuncs(L, path_functions, 0); + lua_setglobal(L, "path"); + + if (lua_getglobal(L, "os") != LUA_TTABLE) { + lua_pop(L, 1); + lua_newtable(L); + } + luaL_setfuncs(L, os_functions, 0); + lua_setglobal(L, "os"); + + if (lua_getglobal(L, "string") != LUA_TTABLE) { + lua_pop(L, 1); + lua_newtable(L); + } + luaL_setfuncs(L, string_functions, 0); + lua_setglobal(L, "string"); + + lua_pushstring(L, PLATFORM_STRING); + lua_setglobal(L, "_OS"); + + return OKAY; +} + +/** + * Locate the Premake executable, and push its full path to the Lua stack. + * Based on: + * http://sourceforge.net/tracker/index.php?func=detail&aid=3351583&group_id=71616&atid=531880 + * http://stackoverflow.com/questions/933850/how-to-find-the-location-of-the-executable-in-c + * http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe + */ +int premake_locate(lua_State* L, const char* argv0) +{ +#if !defined(PATH_MAX) +#define PATH_MAX (4096) +#endif + + char buffer[PATH_MAX]; + const char* path = NULL; + +#if PLATFORM_WINDOWS + DWORD len = GetModuleFileName(NULL, buffer, PATH_MAX); + if (len > 0) + path = buffer; +#endif + +#if PLATFORM_MACOSX + CFURLRef bundleURL = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); + CFStringRef pathRef = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle); + if (CFStringGetCString(pathRef, buffer, PATH_MAX - 1, kCFStringEncodingUTF8)) + path = buffer; +#endif + +#if PLATFORM_LINUX + int len = readlink("/proc/self/exe", buffer, PATH_MAX); + if (len > 0) + path = buffer; +#endif + +#if PLATFORM_BSD + int len = readlink("/proc/curproc/file", buffer, PATH_MAX); + if (len < 0) + len = readlink("/proc/curproc/exe", buffer, PATH_MAX); + if (len > 0) + path = buffer; +#endif + +#if PLATFORM_SOLARIS + int len = readlink("/proc/self/path/a.out", buffer, PATH_MAX); + if (len > 0) + path = buffer; +#endif + + /* As a fallback, search the PATH with argv[0] */ + if (!path) + { + lua_pushcfunction(L, os_pathsearch); + lua_pushstring(L, argv0); + lua_pushstring(L, getenv("PATH")); + if (lua_pcall(L, 2, 1, 0) == OKAY && !lua_isnil(L, -1)) + { + lua_pushstring(L, "/"); + lua_pushstring(L, argv0); + lua_concat(L, 3); + path = lua_tostring(L, -1); + } + } + + /* If all else fails, use argv[0] as-is and hope for the best */ + if (!path) + { + /* make it absolute, if needed */ + os_getcwd(L); + lua_pushstring(L, "/"); + lua_pushstring(L, argv0); + + if (!path_isabsolute(L)) { + lua_concat(L, 3); + } + else { + lua_pop(L, 1); + } + + path = lua_tostring(L, -1); + } + + lua_pushstring(L, path); + return 1; +} diff --git a/src/granger/src/premake/premake.h b/src/granger/src/premake/premake.h new file mode 100644 index 0000000..c54f162 --- /dev/null +++ b/src/granger/src/premake/premake.h @@ -0,0 +1,90 @@ +/** + * \file premake.h + * \brief Program-wide constants and definitions. + * \author Copyright (c) 2002-2011 Jason Perkins and the Premake project + */ + +#define lua_c +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + + +/* Identify the current platform I'm not sure how to reliably detect + * Windows but since it is the most common I use it as the default */ +#if defined(__linux__) +#define PLATFORM_LINUX (1) +#define PLATFORM_STRING "linux" +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#define PLATFORM_BSD (1) +#define PLATFORM_STRING "bsd" +#elif defined(__APPLE__) && defined(__MACH__) +#define PLATFORM_MACOSX (1) +#define PLATFORM_STRING "macosx" +#elif defined(__sun__) && defined(__svr4__) +#define PLATFORM_SOLARIS (1) +#define PLATFORM_STRING "solaris" +#elif defined(__HAIKU__) +#define PLATFORM_HAIKU (1) +#define PLATFORM_STRING "haiku" +#else +#define PLATFORM_WINDOWS (1) +#define PLATFORM_STRING "windows" +#endif + + +/* Pull in platform-specific headers required by built-in functions */ +#if PLATFORM_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#endif + + +/* A success return code */ +#define OKAY (0) + + +/* Bootstrapping helper functions */ +void do_getabsolute(char* result, const char* value, const char* relative_to); +int do_getcwd(char* buffer, size_t size); +int do_isabsolute(const char* path); +int do_isfile(const char* filename); +void do_normalize(lua_State* L, char* buffer, const char* path); +void do_translate(char* value, const char sep); + + +/* Built-in functions */ +int path_getabsolute(lua_State* L); +int path_getrelative(lua_State* L); +int path_isabsolute(lua_State* L); +int path_join(lua_State* L); +int path_normalize(lua_State* L); +int path_translate(lua_State* L); +int os_access(lua_State* L); +int os_chdir(lua_State* L); +int os_copyfile(lua_State* L); +int os_elevate(lua_State *L); +int os_getcwd(lua_State* L); +int os_getversion(lua_State* L); +int os_is64bit(lua_State* L); +int os_isdir(lua_State* L); +int os_isfile(lua_State* L); +int os_matchdone(lua_State* L); +int os_matchisfile(lua_State* L); +int os_matchname(lua_State* L); +int os_matchnext(lua_State* L); +int os_matchstart(lua_State* L); +int os_mkdir(lua_State* L); +int os_pathsearch(lua_State* L); +int os_rmdir(lua_State* L); +int os_stat(lua_State* L); +int string_endswith(lua_State* L); + + +/* Engine interface */ +int premake_init(lua_State* L); +int premake_locate(lua_State* L, const char* argv0); + diff --git a/src/granger/src/premake/string_endswith.c b/src/granger/src/premake/string_endswith.c new file mode 100644 index 0000000..f80cfc1 --- /dev/null +++ b/src/granger/src/premake/string_endswith.c @@ -0,0 +1,28 @@ +/** + * \file string_endswith.c + * \brief Determines if a string ends with the given sequence. + * \author Copyright (c) 2002-2009 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int string_endswith(lua_State* L) +{ + const char* haystack = luaL_optstring(L, 1, NULL); + const char* needle = luaL_optstring(L, 2, NULL); + + if (haystack && needle) + { + int hlen = strlen(haystack); + int nlen = strlen(needle); + if (hlen >= nlen) + { + lua_pushboolean(L, strcmp(haystack + hlen - nlen, needle) == 0); + return 1; + } + } + + return 0; +} diff --git a/src/granger/src/strvec.c b/src/granger/src/strvec.c new file mode 100644 index 0000000..14681e8 --- /dev/null +++ b/src/granger/src/strvec.c @@ -0,0 +1,156 @@ +/**************************************************************************** + * + * Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2009-2013 Sourcefire, Inc. + * Copyright (C) 2015-2019 GrangerHub + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 3 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * 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, see . + * + ****************************************************************************/ + +#include +#include + +#include "strvec.h" + +typedef struct { + char** v; + unsigned n; +} StringVector; + +void* StringVector_New (void) +{ + StringVector* sv = malloc(sizeof(*sv)); + if ( !sv ) + return NULL; + + sv->v = malloc(sizeof(*sv->v)); + if ( !sv->v ) + { + free(sv); + return NULL; + } + + sv->n = 0; + return sv; +} + +void StringVector_Delete (void* pv) +{ + unsigned i; + StringVector* sv = (StringVector*)pv; + + if ( !sv ) + return; + + for ( i = 0; i < sv->n; i++ ) + free(sv->v[i]); + + free(sv->v); + free(sv); +} + +int StringVector_Add (void* pv, const char* s) +{ + StringVector* sv = (StringVector*)pv; + char** v; + char* temp; + + if ( !sv || !s ) + return 0; + + temp = strdup(s); + if ( !temp ) + return 0; + + v = realloc(sv->v, (sv->n+2) * sizeof(char*)); + if ( !v ) + return 0; + + sv->v = v; + sv->v[sv->n++] = temp; + sv->v[sv->n] = NULL; + + return 1; +} + +char* StringVector_Get (void* pv, unsigned index) +{ + StringVector* sv = (StringVector*)pv; + + if ( !sv || index >= sv->n ) + return NULL; + + return sv->v[index]; +} + +int StringVector_AddVector (void* pd, void* ps) +{ + unsigned i = 0; + const char* s = StringVector_Get(ps, i++); + + while ( s ) + { + if ( !StringVector_Add(pd, s) ) + return 0; + + s = StringVector_Get(ps, i++); + } + return 1; +} + +const char** StringVector_GetVector (void* pv) +{ + StringVector* sv = (StringVector*)pv; + + if ( !sv ) + return NULL; + + return (const char**)sv->v; +} + +const char* StringVector_toString(void* pv) +{ + unsigned i = 0; + const char* s = StringVector_Get(pv, i++); + + char* ret = NULL; + size_t siz = 0; + + while ( s ) + { + size_t n = siz + strlen(s) + 2; + size_t o = n - 1; + + char* _tmp = realloc(ret, n); + if ( !_tmp ) + { + free(ret); + return NULL; + } + ret = _tmp; + + memcpy(ret + siz, s, strlen(s)); + ret[o] = ' '; + siz = n; + + s = StringVector_Get(pv, i++); + } + + if ( ret && siz ) + ret[siz-1] = '\0'; + + return ret; +} diff --git a/src/granger/src/strvec.h b/src/granger/src/strvec.h new file mode 100644 index 0000000..69bab51 --- /dev/null +++ b/src/granger/src/strvec.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * + * Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2009-2013 Sourcefire, Inc. + * Copyright (C) 2015-2019 GrangerHub + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 3 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * 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, see . + * + ****************************************************************************/ + +#ifndef _STRVEC_H_ +#define _STRVEC_H_ + +void* StringVector_New(void); +void StringVector_Delete(void*); + +int StringVector_Add(void*, const char*); +char* StringVector_Get(void*, unsigned index); + +int StringVector_AddVector(void* dst, void* src); +const char** StringVector_GetVector(void*); +const char* StringVector_toString(void*); + +#endif // _STRVEC_H_ diff --git a/src/granger/test/main.lua b/src/granger/test/main.lua new file mode 100644 index 0000000..a5f9aee --- /dev/null +++ b/src/granger/test/main.lua @@ -0,0 +1,11 @@ +-- +-- main.lua +-- test runner +-- Copyright (c) 2016 Jeff Kent +-- + +package.path = package.path .. ";../lua/?.lua;../lua/?/init.lua" + +require "lib" +require "test-os-access" +require "test-nettle" diff --git a/src/granger/test/test-nettle.lua b/src/granger/test/test-nettle.lua new file mode 100644 index 0000000..e453a57 --- /dev/null +++ b/src/granger/test/test-nettle.lua @@ -0,0 +1,33 @@ +-- +-- test-nettle.lua +-- test cases for nettle library +-- Copyright (c) 2016 Jeff Kent +-- + +print "nettle tests begin" + +empty_hash = tostring(nettle.sha256()) + +ctx = nettle.sha256() +ctx:update(nil) +assert(empty_hash == tostring(ctx)) + +ctx = nettle.sha256() +ctx:update("Hello World!") +ctx:update("Hello World!") +hash = tostring(ctx) +assert(hash == "95a5a79bf6218dd0938950acb61bca24d5809172fe6cfd7f1af4b059449e52f8") + +ctx = nettle.sha256() +ctx:update("Hello World!Hello World!") +hash = tostring(ctx) +assert(hash == "95a5a79bf6218dd0938950acb61bca24d5809172fe6cfd7f1af4b059449e52f8") + +require "util" +hash = md5_file("../COPYING") +assert(hash == "b234ee4d69f5fce4486a80fdaf4a4263") + +hash = sha256_file("../COPYING") +assert(hash == "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643") + +print "nettle tests completed" diff --git a/src/granger/test/test-os-access.lua b/src/granger/test/test-os-access.lua new file mode 100644 index 0000000..60740c4 --- /dev/null +++ b/src/granger/test/test-os-access.lua @@ -0,0 +1,17 @@ +-- +-- test-os-access.lua +-- test case for os.access() +-- Copyright (c) 2016 Jeff Kent +-- + +print "os.access() test begin" + +if not os.is("windows") then + p = os.tmpname() + os.execute("touch " .. p .. "; chmod 400 " .. p) + assert(os.access(p, "r") == true) + assert(os.access(p, "w") == false) + os.execute("rm -f " .. p) +end + +print "os.access() test end" diff --git a/src/null/null_client.cpp b/src/null/null_client.cpp new file mode 100644 index 0000000..697780e --- /dev/null +++ b/src/null/null_client.cpp @@ -0,0 +1,95 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "qcommon/cvar.h" +#include "qcommon/msg.h" +#include "qcommon/net.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +cvar_t *cl_shownet; + +void CL_Shutdown(const char *finalmsg, bool disconnect, bool quit) +{ +} + +void CL_Init( void ) { + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); +} + +void CL_MouseEvent( int dx, int dy, int time ) { +} + +void Key_WriteBindings( fileHandle_t f ) { +} + +void CL_Frame ( int msec ) { +} + +void CL_PacketEvent( struct netadr_t from, struct msg_t *msg ) { +} + +void CL_CharEvent( int key ) { +} + +void CL_Disconnect( bool showMainMenu ) { +} + +void CL_MapLoading( void ) { +} + +bool CL_GameCommand( void ) { + return false; +} + +void CL_KeyEvent (int key, bool down, unsigned time) { +} + +bool UI_GameCommand( void ) { + return false; +} + +void CL_ForwardCommandToServer( const char *string ) { +} + +void CL_ConsolePrint( const char *txt ) { +} + +void CL_JoystickEvent( int axis, int value, int time ) { +} + +void CL_InitKeyCommands( void ) { +} + +void CL_FlushMemory(void) +{ +} + +void CL_ShutdownAll(bool shutdownRef) +{ +} + +void CL_StartHunkUsers( bool rendererOnly ) +{ +} diff --git a/src/null/null_glimp.cpp b/src/null/null_glimp.cpp new file mode 100644 index 0000000..3f9f602 --- /dev/null +++ b/src/null/null_glimp.cpp @@ -0,0 +1,63 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "renderercommon/tr_common.h" + +qboolean ( * qwglSwapIntervalEXT)( int interval ); +void ( * qglMultiTexCoord2fARB )( GLenum texture, float s, float t ); +void ( * qglActiveTextureARB )( GLenum texture ); +void ( * qglClientActiveTextureARB )( GLenum texture ); + + +void ( * qglLockArraysEXT)( int, int); +void ( * qglUnlockArraysEXT) ( void ); + + +void GLimp_EndFrame( void ) { +} + +void GLimp_Init( void ) { +} + +void GLimp_Shutdown( void ) { +} + +void GLimp_EnableLogging( qboolean enable ) { +} + +void GLimp_LogComment( const char *comment ) { +} + +qboolean QGL_Init( const char *dllname ) { + return qtrue; +} + +void QGL_Shutdown( void ) { +} + +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ) { +} + +void GLimp_Minimize( void ) { +} diff --git a/src/null/null_input.cpp b/src/null/null_input.cpp new file mode 100644 index 0000000..05bfff8 --- /dev/null +++ b/src/null/null_input.cpp @@ -0,0 +1,37 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "sys/sys_local.h" + +void IN_Init( void ) { +} + +void IN_Frame (void) { +} + +void IN_Shutdown( void ) { +} + +void IN_Restart( void ) { +} diff --git a/src/null/null_main.cpp b/src/null/null_main.cpp new file mode 100644 index 0000000..f30850a --- /dev/null +++ b/src/null/null_main.cpp @@ -0,0 +1,83 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// null_main.c -- null system driver to aid porting efforts + +#include +#include + +#include "qcommon/qcommon.h" + +int sys_curtime; + + +//=================================================================== + + +void Sys_Error (char *error, ...) { + va_list argptr; + + printf ("Sys_Error: "); + va_start (argptr,error); + vprintf (error,argptr); + va_end (argptr); + printf ("\n"); + + exit (1); +} + +void Sys_Quit (void) { + exit (0); +} + +char *Sys_GetClipboardData( void ) { + return NULL; +} + +int Sys_Milliseconds (void) { + return 0; +} + +FILE *Sys_FOpen(const char *ospath, const char *mode) { + return fopen( ospath, mode ); +} + +void Sys_Mkdir (char *path) { +} + +bool Sys_OpenWithDefault( const char *path ) +{ + return false; +} + +void Sys_Init (void) { +} + + +void main (int argc, char **argv) { + Com_Init (argc, argv); + + while (1) { + Com_Frame( ); + } +} diff --git a/src/null/null_net.cpp b/src/null/null_net.cpp new file mode 100644 index 0000000..97b2d9c --- /dev/null +++ b/src/null/null_net.cpp @@ -0,0 +1,55 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "qcommon/qcommon.h" + +/* +============= +NET_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +qboolean NET_StringToAdr (char *s, netadr_t *a) +{ + if (!strcmp (s, "localhost")) { + memset (a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return true; + } + + return false; +} + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, void *data, netadr_t to ) { +} diff --git a/src/null/null_snddma.cpp b/src/null/null_snddma.cpp new file mode 100644 index 0000000..d77899b --- /dev/null +++ b/src/null/null_snddma.cpp @@ -0,0 +1,62 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// snddma_null.c +// all other sound mixing is portable + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +bool SNDDMA_Init(void) +{ + return false; +} + +int SNDDMA_GetDMAPos(void) +{ + return 0; +} + +void SNDDMA_Shutdown(void) +{ +} + +void SNDDMA_BeginPainting (void) +{ +} + +void SNDDMA_Submit(void) +{ +} + +sfxHandle_t S_RegisterSound( const char *name, bool compressed ) +{ + return 0; +} + +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { +} + +void S_ClearSoundBuffer( void ) { +} diff --git a/src/qcommon/CMakeLists.txt b/src/qcommon/CMakeLists.txt new file mode 100644 index 0000000..493dfa4 --- /dev/null +++ b/src/qcommon/CMakeLists.txt @@ -0,0 +1,54 @@ +add_library ( + common STATIC + cm_load.c + cm_local.h + cm_patch.c + cm_patch.h + cm_polylib.c + cm_polylib.h + cm_public.h + cm_test.c + cm_trace.c + cmd.cpp + common.c + cvar.cpp + dialog.h + files.c + huffman.c + ioapi.c + ioapi.h + json.h + md4.c + md5.c + msg.c + net_chan.c + net_ip.c + parse.c + puff.c + puff.h + q3_lauxlib.cpp + q3_lauxlib.h + q_math.c + q_platform.h + q_shared.c + q_shared.h + qcommon.h + qfiles.h + surfaceflags.h + unzip.c + unzip.h + vm.c + vm_interpreted.c + vm_local.h + vm_none.c + vm_powerpc.c + vm_powerpc_asm.c + vm_powerpc_asm.h + vm_sparc.c + vm_sparc.h + vm_x86.c +) + +include_directories( ${RESTCLIENT_INCLUDES_DIR} ) + +set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14" ) diff --git a/src/qcommon/alternatePlayerstate.h b/src/qcommon/alternatePlayerstate.h new file mode 100644 index 0000000..a35778b --- /dev/null +++ b/src/qcommon/alternatePlayerstate.h @@ -0,0 +1,75 @@ +#ifndef QCOMMON_ALTERNATEPLAYERSTATE_H +#define QCOMMON_ALTERNATEPLAYERSTATE_H 1 + +#include "q_shared.h" + +struct alternatePlayerState_t { + int commandTime; // cmd->serverTime of last executed command + int pm_type; + int bobCycle; // for view bobbing and footstep generation + int pm_flags; // ducked, jump_held, etc + int pm_time; + + vec3_t origin; + vec3_t velocity; + int weaponTime; + int gravity; + int speed; + int delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + + int groundEntityNum; // ENTITYNUM_NONE = in air + + int legsTimer; // don't change low priority animations until this runs out + int legsAnim; // mask off ANIM_TOGGLEBIT + + int torsoTimer; // don't change low priority animations until this runs out + int torsoAnim; // mask off ANIM_TOGGLEBIT + + int movementDir; // a number 0 to 7 that represents the relative angle + // of movement to the view angle (axial and diagonals) + // when at rest, the value will remain unchanged + // used to twist the legs during strafing + + vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL + + int eFlags; // copied to entityState_t->eFlags + + int eventSequence; // pmove generated events + int events[MAX_PS_EVENTS]; + int eventParms[MAX_PS_EVENTS]; + + int externalEvent; // events set on player from another source + int externalEventParm; + int externalEventTime; + + int clientNum; // ranges from 0 to MAX_CLIENTS-1 + int weapon; // copied to entityState_t->weapon + int weaponstate; + + vec3_t viewangles; // for fixed views + int viewheight; + + // damage feedback + int damageEvent; // when it changes, latch the other parms + int damageYaw; + int damagePitch; + int damageCount; + + int stats[MAX_STATS]; + int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death + int misc[MAX_MISC]; // misc data + int ammo[MAX_WEAPONS]; + + int generic1; + int loopSound; + int otherEntityNum; + + // not communicated over the net at all + int ping; // server to game info for scoreboard + int pmove_framecount; + int jumppad_frame; + int entityEventSequence; +}; + +#endif diff --git a/src/qcommon/cdefs.h b/src/qcommon/cdefs.h new file mode 100644 index 0000000..327c244 --- /dev/null +++ b/src/qcommon/cdefs.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2010-2012, Victor J. Roemer. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __CDEFS_H__ +#define __CDEFS_H__ + +#define UNUSED __attribute__((unused)) + +#define NORETURN __attribute__((noreturn)) + +/* Support for flexible arrays, stolen from dnet */ +#undef __flexarr +#if defined(__GNUC__) && ((__GNUC__ > 2) || \ + (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)) + +/* GCC 2.97 supports C99 flexible array members. */ +# define __flexarr [] +#else +# ifdef __GNUC__ +# define __flexarr [0] +# else +# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define __flexarr [] +# elif defined(_WIN32) +/* MS VC++ */ +# define __flexarr [] +# else +/* Some other non-C99 compiler. Approximate with [1]. */ +# define __flexarr [1] +# endif +# endif +#endif + +#ifndef SO_PUBLIC +#if defined _WIN32 || defined __CYGWIN__ +# ifdef __GNUC__ +# define SO_PUBLIC __attribute__((dllimport)) +# else +# define SO_PUBLIC __declspec(dllimport) +# endif +# define DLL_LOCAL +#else +# ifdef HAVE_VISIBILITY +# define SO_PUBLIC __attribute__ ((visibility("default"))) +# define SO_PRIVATE __attribute__ ((visibility("hidden"))) +# else +# define SO_PUBLIC +# define SO_PRIVATE +# endif +#endif +#endif + +#endif diff --git a/src/qcommon/cm_load.cpp b/src/qcommon/cm_load.cpp new file mode 100644 index 0000000..0ce0606 --- /dev/null +++ b/src/qcommon/cm_load.cpp @@ -0,0 +1,1022 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// cmodel.c -- model loading + +#include "cm_local.h" +#include "files.h" +#include "md4.h" + +#ifdef BSPC + +#include "../bspc/l_qfiles.h" + +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL(x) x=LittleLong(x) + + +clipMap_t cm; +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + + + +void CM_InitBoxHull (void); + + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( lump_t *l ) { + dshader_t *in, *out; + int i, count; + + in = (dshader_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = l->filelen / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + cm.shaders = (dshader_t*)Hunk_Alloc( count * sizeof( *cm.shaders ), h_high ); + cm.numShaders = count; + + ::memcpy( cm.shaders, in, count * sizeof( *cm.shaders ) ); + + out = cm.shaders; + for ( i=0 ; icontentFlags = LittleLong( out->contentFlags ); + out->surfaceFlags = LittleLong( out->surfaceFlags ); + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = (dmodel_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no models"); + cm.cmodels = (cmodel_t*)Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high ); + cm.numSubModels = count; + + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" ); + } + + for ( i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + } + + if ( i == 0 ) { + continue; // world model doesn't need other info + } + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = LittleLong( in->numBrushes ); + indexes = (int*)Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high ); + out->leaf.firstLeafBrush = indexes - cm.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = LittleLong( in->firstBrush ) + j; + } + + out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces ); + indexes = (int*)Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high ); + out->leaf.firstLeafSurface = indexes - cm.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = LittleLong( in->firstSurface ) + j; + } + } +} + + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( lump_t *l ) { + dnode_t *in; + int child; + cNode_t *out; + int count; + + in = (dnode_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map has no nodes"); + + cm.nodes = (cNode_t*)Hunk_Alloc( count * sizeof( *cm.nodes ), h_high ); + cm.numNodes = count; + + out = cm.nodes; + + for (int i=0 ; iplane = cm.planes + LittleLong( in->planeNum ); + for (int j=0 ; j<2 ; j++) + { + child = LittleLong (in->children[j]); + out->children[j] = child; + } + } + +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -b->sides[0].plane->dist; + b->bounds[1][0] = b->sides[1].plane->dist; + + b->bounds[0][1] = -b->sides[2].plane->dist; + b->bounds[1][1] = b->sides[3].plane->dist; + + b->bounds[0][2] = -b->sides[4].plane->dist; + b->bounds[1][2] = b->sides[5].plane->dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( lump_t *l ) { + dbrush_t *in; + cbrush_t *out; + int count; + + in = (dbrush_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushes = (cbrush_t*)Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high ); + cm.numBrushes = count; + + out = cm.brushes; + + for ( int i = 0 ; isides = cm.brushsides + LittleLong(in->firstSide); + out->numsides = LittleLong(in->numSides); + + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cm.shaders[out->shaderNum].contentFlags; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (lump_t *l) +{ + cLeaf_t *out; + dleaf_t *in; + int count; + + in = (dleaf_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no leafs"); + + cm.leafs = (cLeaf_t*)Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high ); + cm.numLeafs = count; + + out = cm.leafs; + for ( int i = 0 ; icluster = LittleLong (in->cluster); + out->area = LittleLong (in->area); + out->firstLeafBrush = LittleLong (in->firstLeafBrush); + out->numLeafBrushes = LittleLong (in->numLeafBrushes); + out->firstLeafSurface = LittleLong (in->firstLeafSurface); + out->numLeafSurfaces = LittleLong (in->numLeafSurfaces); + + if (out->cluster >= cm.numClusters) + cm.numClusters = out->cluster + 1; + if (out->area >= cm.numAreas) + cm.numAreas = out->area + 1; + } + + cm.areas = (cArea_t*)Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high ); + cm.areaPortals = (int*)Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (lump_t *l) +{ + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = (dplane_t*)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + cm.planes = (cplane_t*)Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high ); + cm.numPlanes = count; + + out = cm.planes; + + for ( int i = 0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (lump_t *l) +{ + int *out; + int *in; + int count; + + in = (int*)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafbrushes = (int*)Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high ); + cm.numLeafBrushes = count; + + out = cm.leafbrushes; + + for ( int i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafsurfaces = (int*)Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high ); + cm.numLeafSurfaces = count; + + out = cm.leafsurfaces; + + for ( int i = 0 ; ifileofs); + if ( l->filelen % sizeof(*in) ) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushsides = (cbrushside_t*)Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high ); + cm.numBrushSides = count; + + out = cm.brushsides; + + for ( i=0 ; iplaneNum ); + out->planeNum = num; + out->plane = &cm.planes[num]; + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } + out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags; + } +} + +#define CM_EDGE_VERTEX_EPSILON 0.1f + +/* +================= +CMod_BrushEdgesAreTheSame +================= +*/ +static bool CMod_BrushEdgesAreTheSame( const vec3_t p0, const vec3_t p1, + const vec3_t q0, const vec3_t q1 ) +{ + if( VectorCompareEpsilon( p0, q0, CM_EDGE_VERTEX_EPSILON ) && + VectorCompareEpsilon( p1, q1, CM_EDGE_VERTEX_EPSILON ) ) + return true; + + if( VectorCompareEpsilon( p1, q0, CM_EDGE_VERTEX_EPSILON ) && + VectorCompareEpsilon( p0, q1, CM_EDGE_VERTEX_EPSILON ) ) + return true; + + return false; +} + +/* +================= +CMod_AddEdgeToBrush +================= +*/ +static bool CMod_AddEdgeToBrush( const vec3_t p0, const vec3_t p1, + cbrushedge_t *edges, int *numEdges ) +{ + int i; + + if( !edges || !numEdges ) + return false; + + for( i = 0; i < *numEdges; i++ ) + { + if( CMod_BrushEdgesAreTheSame( p0, p1, + edges[ i ].p0, edges[ i ].p1 ) ) + return false; + } + + VectorCopy( p0, edges[ *numEdges ].p0 ); + VectorCopy( p1, edges[ *numEdges ].p1 ); + (*numEdges)++; + + return true; +} + +/* +================= +CMod_CreateBrushSideWindings +================= +*/ +static void CMod_CreateBrushSideWindings( void ) +{ + int i, j, k; + winding_t *w; + cbrushside_t *side, *chopSide; + cplane_t *plane; + cbrush_t *brush; + cbrushedge_t *tempEdges; + int numEdges; + int edgesAlloc; + int totalEdgesAlloc = 0; + int totalEdges = 0; + + for( i = 0; i < cm.numBrushes; i++ ) + { + brush = &cm.brushes[ i ]; + numEdges = 0; + + // walk the list of brush sides + for( j = 0; j < brush->numsides; j++ ) + { + // get side and plane + side = &brush->sides[ j ]; + plane = side->plane; + + w = BaseWindingForPlane( plane->normal, plane->dist ); + + // walk the list of brush sides + for( k = 0; k < brush->numsides && w != NULL; k++ ) + { + chopSide = &brush->sides[ k ]; + + if( chopSide == side ) + continue; + + if( chopSide->planeNum == ( side->planeNum ^ 1 ) ) + continue; // back side clipaway + + plane = &cm.planes[ chopSide->planeNum ^ 1 ]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); + } + + if( w ) + numEdges += w->numpoints; + + // set side winding + side->winding = w; + } + + // Allocate a temporary buffer of the maximal size + tempEdges = (cbrushedge_t *)Z_Malloc( sizeof( cbrushedge_t ) * numEdges ); + brush->numEdges = 0; + + // compose the points into edges + for( j = 0; j < brush->numsides; j++ ) + { + side = &brush->sides[ j ]; + + if( side->winding ) + { + for( k = 0; k < side->winding->numpoints - 1; k++ ) + { + if( brush->numEdges == numEdges ) + Com_Error( ERR_FATAL, + "Insufficient memory allocated for collision map edges" ); + + CMod_AddEdgeToBrush( side->winding->p[ k ], + side->winding->p[ k + 1 ], tempEdges, &brush->numEdges ); + } + + FreeWinding( side->winding ); + side->winding = NULL; + } + } + + // Allocate a buffer of the actual size + edgesAlloc = sizeof( cbrushedge_t ) * brush->numEdges; + totalEdgesAlloc += edgesAlloc; + brush->edges = (cbrushedge_t *)Hunk_Alloc( edgesAlloc, h_low ); + + // Copy temporary buffer to permanent buffer + ::memcpy( brush->edges, tempEdges, edgesAlloc ); + + // Free temporary buffer + Z_Free( tempEdges ); + + totalEdges += brush->numEdges; + } + + Com_DPrintf( "Allocated %d bytes for %d collision map edges...\n", + totalEdgesAlloc, totalEdges ); +} + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( lump_t *l ) { + cm.entityString = (char*)Hunk_Alloc( l->filelen, h_high ); + cm.numEntityChars = l->filelen; + ::memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define VIS_HEADER 8 +void CMod_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = l->filelen; + if ( !len ) { + cm.clusterBytes = ( cm.numClusters + 31 ) & ~31; + cm.visibility = (byte*)Hunk_Alloc( cm.clusterBytes, h_high ); + ::memset( cm.visibility, 255, cm.clusterBytes ); + return; + } + buf = cmod_base + l->fileofs; + + cm.vised = true; + cm.visibility = (byte*)Hunk_Alloc( len, h_high ); + cm.numClusters = LittleLong( ((int *)buf)[0] ); + cm.clusterBytes = LittleLong( ((int *)buf)[1] ); + ::memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER ); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 +void CMod_LoadPatches( lump_t *surfs, lump_t *verts ) { + drawVert_t *dv, *dv_p; + dsurface_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + in = (dsurface_t *)(cmod_base + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + cm.numSurfaces = count = surfs->filelen / sizeof(*in); + cm.surfaces = (cPatch_t**)Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high ); + + dv = (drawVert_t *)(cmod_base + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + + // scan through all the surfaces, but only load patches, + // not planar faces + for ( i = 0 ; i < count ; i++, in++ ) { + if ( LittleLong( in->surfaceType ) != MST_PATCH ) { + continue; // ignore other surfaces + } + // FIXME: check for non-colliding patches + + cm.surfaces[ i ] = patch = (cPatch_t*)Hunk_Alloc( sizeof( *patch ), h_high ); + + // load the full drawverts onto the stack + width = LittleLong( in->patchWidth ); + height = LittleLong( in->patchHeight ); + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + LittleLong( in->firstVert ); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = LittleFloat( dv_p->xyz[0] ); + points[j][1] = LittleFloat( dv_p->xyz[1] ); + points[j][2] = LittleFloat( dv_p->xyz[2] ); + } + + shaderNum = LittleLong( in->shaderNum ); + patch->contents = cm.shaders[shaderNum].contentFlags; + patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points ); + } +} + +//================================================================== + +unsigned CM_LumpChecksum(lump_t *lump) { + return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen)); +} + +unsigned CM_Checksum(dheader_t *header) { + unsigned checksums[16]; + checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]); + checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]); + checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]); + checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]); + checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]); + checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]); + checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]); + checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]); + checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]); + checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]); + checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]); + + return LittleLong(Com_BlockChecksum(checksums, 11 * 4)); +} + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void CM_LoadMap( const char *name, bool clientload, int *checksum ) { + union { + int *i; + void *v; + } buf; + dheader_t header; + int length; + static unsigned last_checksum; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); + cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); + cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cm.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + // free old stuff + ::memset( &cm, 0, sizeof( cm ) ); + CM_ClearLevelPatches(); + + if ( !name[0] ) { + cm.numLeafs = 1; + cm.numClusters = 1; + cm.numAreas = 1; + cm.cmodels = (cmodel_t*)Hunk_Alloc( sizeof( *cm.cmodels ), h_high ); + *checksum = 0; + return; + } + + // + // load the file + // +#ifndef BSPC + length = FS_ReadFile( name, &buf.v ); +#else + length = LoadQuakeFile((quakefile_t *) name, &buf.v); +#endif + + if ( !buf.i ) { + Com_Error (ERR_DROP, "Couldn't load %s", name); + } + + last_checksum = LittleLong (Com_BlockChecksum (buf.i, length)); + *checksum = last_checksum; + + header = *(dheader_t *)buf.i; + for (size_t i = 0 ; i= cm.numSubModels ) { + Com_Error (ERR_DROP, "CM_InlineModel: bad number"); + } + return index; +} + +int CM_NumClusters( void ) { + return cm.numClusters; +} + +int CM_NumInlineModels( void ) { + return cm.numSubModels; +} + +char *CM_EntityString( void ) { + return cm.entityString; +} + +int CM_LeafCluster( int leafnum ) { + if (leafnum < 0 || leafnum >= cm.numLeafs) { + Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); + } + return cm.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cm.numLeafs ) { + Com_Error (ERR_DROP, "CM_LeafArea: bad number"); + } + return cm.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cm.planes[cm.numPlanes]; + + box_brush = &cm.brushes[cm.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cm.brushsides + cm.numBrushSides; + box_brush->contents = CONTENTS_BODY; + box_brush->edges = (cbrushedge_t *)Hunk_Alloc( + sizeof( cbrushedge_t ) * 12, h_low ); + box_brush->numEdges = 12; + + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cm.numBrushes; + box_model.leaf.firstLeafBrush = cm.numLeafBrushes; + cm.leafbrushes[cm.numLeafBrushes] = cm.numBrushes; + + for (i=0 ; i<6 ; i++) + { + side = i&1; + + // brush sides + s = &cm.brushsides[cm.numBrushSides+i]; + s->plane = cm.planes + (cm.numPlanes+i*2+side); + s->surfaceFlags = 0; + + // planes + p = &box_planes[i*2]; + p->type = i>>1; + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = 1; + + p = &box_planes[i*2+1]; + p->type = 3 + (i>>1); + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = -1; + + SetPlaneSignbits( p ); + } +} + +/* +=================== +CM_TempBoxModel + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +Capsules are handled differently though. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) { + + VectorCopy( mins, box_model.mins ); + VectorCopy( maxs, box_model.maxs ); + + if ( capsule ) { + return CAPSULE_MODEL_HANDLE; + } + + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + // First side + VectorSet( box_brush->edges[ 0 ].p0, mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 0 ].p1, mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 1 ].p0, mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 1 ].p1, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 2 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 2 ].p1, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 3 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 3 ].p1, mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + + // Opposite side + VectorSet( box_brush->edges[ 4 ].p0, maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 4 ].p1, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 5 ].p0, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 5 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 6 ].p0, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 6 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 7 ].p0, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 7 ].p1, maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + + // Connecting edges + VectorSet( box_brush->edges[ 8 ].p0, mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 8 ].p1, maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 9 ].p0, mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 9 ].p1, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 10 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 10 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 11 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 11 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + return BOX_MODEL_HANDLE; +} + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} diff --git a/src/qcommon/cm_local.h b/src/qcommon/cm_local.h new file mode 100644 index 0000000..54e62eb --- /dev/null +++ b/src/qcommon/cm_local.h @@ -0,0 +1,225 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef CM_LOCAL_H +#define CM_LOCAL_H 1 + +#include "cvar.h" +#include "q_shared.h" +#include "qcommon.h" +#include "cm_polylib.h" + +#define MAX_SUBMODELS 256 +#define BOX_MODEL_HANDLE 255 +#define CAPSULE_MODEL_HANDLE 254 + +typedef struct { + cplane_t *plane; + int children[2]; // negative numbers are leafs +} cNode_t; + +typedef struct { + int cluster; + int area; + + int firstLeafBrush; + int numLeafBrushes; + + int firstLeafSurface; + int numLeafSurfaces; +} cLeaf_t; + +typedef struct cmodel_s { + vec3_t mins, maxs; + cLeaf_t leaf; // submodels don't reference the main tree +} cmodel_t; + +typedef struct cbrushedge_s +{ + vec3_t p0; + vec3_t p1; +} cbrushedge_t; + +typedef struct { + cplane_t *plane; + int planeNum; + int surfaceFlags; + int shaderNum; + winding_t *winding; +} cbrushside_t; + +typedef struct { + int shaderNum; // the shader that determined the contents + int contents; + vec3_t bounds[2]; + int numsides; + cbrushside_t *sides; + int checkcount; // to avoid repeated testings + bool collided; // marker for optimisation + cbrushedge_t *edges; + int numEdges; +} cbrush_t; + + +typedef struct { + int checkcount; // to avoid repeated testings + int surfaceFlags; + int contents; + struct patchCollide_s *pc; +} cPatch_t; + + +typedef struct { + int floodnum; + int floodvalid; +} cArea_t; + +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + dshader_t *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + byte *visibility; + bool vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace +} clipMap_t; + + +// keep 1/8 unit away to keep the position valid before network snapping +// and to avoid various numeric issues +#define SURFACE_CLIP_EPSILON (0.125) + +extern clipMap_t cm; +extern int c_pointcontents; +extern int c_traces, c_brush_traces, c_patch_traces; +extern cvar_t *cm_noAreas; +extern cvar_t *cm_noCurves; +extern cvar_t *cm_playerCurveClip; + +// cm_test.c + +typedef struct +{ + float startRadius; + float endRadius; +} biSphere_t; + +// Used for oriented capsule collision detection +typedef struct +{ + float radius; + float halfheight; + vec3_t offset; +} sphere_t; + +typedef struct { + traceType_t type; + vec3_t start; + vec3_t end; + vec3_t size[2]; // size of the box being swept through the model + vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x] + float maxOffset; // longest corner length from origin + vec3_t extents; // greatest of abs(size[0]) and abs(size[1]) + vec3_t bounds[2]; // enclosing box of start and end surrounding by size + vec3_t modelOrigin;// origin of the model tracing through + int contents; // ored contents of the model tracing through + bool isPoint; // optimized case + trace_t trace; // returned from trace call + sphere_t sphere; // sphere for oriendted capsule collision + biSphere_t biSphere; + bool testLateralCollision; // whether or not to test for lateral collision +} traceWork_t; + +typedef struct leafList_s { + int count; + int maxcount; + bool overflowed; + int *list; + vec3_t bounds[2]; + int lastLeaf; // for overflows where each leaf can't be stored individually + void (*storeLeafs)( struct leafList_s *ll, int nodenum ); +} leafList_t; + + +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ); + +void CM_StoreLeafs( leafList_t *ll, int nodenum ); +void CM_StoreBrushes( leafList_t *ll, int nodenum ); + +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ); + +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle ); +bool CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 ); +bool CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ); + +// cm_patch.c + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +bool CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_ClearLevelPatches( void ); + +// cm_test.c +void CM_FloodAreaConnections (void); + +#endif diff --git a/src/qcommon/cm_patch.cpp b/src/qcommon/cm_patch.cpp new file mode 100644 index 0000000..274d7c6 --- /dev/null +++ b/src/qcommon/cm_patch.cpp @@ -0,0 +1,1801 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "cm_local.h" +#include "cm_patch.h" + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +bool CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + bool borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + bool wrapWidth; + bool wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 +*/ + +int c_totalPatchBlocks; +int c_totalPatchSurfaces; +int c_totalPatchEdges; + +static const patchCollide_t *debugPatchCollide; +static const facet_t *debugFacet; +static bool debugBlock; +static vec3_t debugBlockPoints[4]; + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { + debugPatchCollide = NULL; + debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static int CM_SignbitsForNormal( vec3_t normal ) { + int bits, j; + + bits = 0; + for (j=0 ; j<3 ; j++) { + if ( normal[j] < 0 ) { + bits |= 1<= SUBDIVIDE_DISTANCE); +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + out1[i] = 0.5 * (a[i] + b[i]); + out3[i] = 0.5 * (b[i] + c[i]); + out2[i] = 0.5 * (out1[i] + out3[i]); + } +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { + int i, j, l; + vec3_t temp; + bool tempWrap; + + if ( grid->width > grid->height ) { + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = i + 1 ; j < grid->width ; j++ ) { + if ( j < grid->height ) { + // swap the value + VectorCopy( grid->points[i][j], temp ); + VectorCopy( grid->points[j][i], grid->points[i][j] ); + VectorCopy( temp, grid->points[j][i] ); + } else { + // just copy + VectorCopy( grid->points[j][i], grid->points[i][j] ); + } + } + } + } else { + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = i + 1 ; j < grid->height ; j++ ) { + if ( j < grid->width ) { + // swap the value + VectorCopy( grid->points[j][i], temp ); + VectorCopy( grid->points[i][j], grid->points[j][i] ); + VectorCopy( temp, grid->points[i][j] ); + } else { + // just copy + VectorCopy( grid->points[i][j], grid->points[j][i] ); + } + } + } + } + + l = grid->width; + grid->width = grid->height; + grid->height = l; + + tempWrap = grid->wrapWidth; + grid->wrapWidth = grid->wrapHeight; + grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth true +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { + int i, j; + float d; + + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + d = grid->points[0][i][j] - grid->points[grid->width-1][i][j]; + if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { + break; + } + } + if ( j != 3 ) { + break; + } + } + if ( i == grid->height ) { + grid->wrapWidth = true; + } else { + grid->wrapWidth = false; + } +} + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 2 ; ) { + // grid->points[i][x] is an interpolating control point + // grid->points[i+1][x] is an aproximating control point + // grid->points[i+2][x] is an interpolating control point + + // + // first see if we can collapse the aproximating collumn away + // + for ( j = 0 ; j < grid->height ; j++ ) { + if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) { + break; + } + } + if ( j == grid->height ) { + // all of the points were close enough to the linear midpoints + // that we can collapse the entire column away + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + + grid->width--; + + // go to the next curve segment + i++; + continue; + } + + // + // we need to subdivide the curve + // + for ( j = 0 ; j < grid->height ; j++ ) { + vec3_t prev, mid, next; + + // save the control points now + VectorCopy( grid->points[i][j], prev ); + VectorCopy( grid->points[i+1][j], mid ); + VectorCopy( grid->points[i+2][j], next ); + + // make room for two additional columns in the grid + // columns i+1 will be replaced, column i+2 will become i+4 + // i+1, i+2, and i+3 will be generated + for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { + VectorCopy( grid->points[k][j], grid->points[k+2][j] ); + } + + // generate the subdivided points + CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] ); + } + + grid->width += 2; + + // the new aproximating point at i+1 may need to be removed + // or subdivided farther, so don't advance i + } +} + +/* +====================== +CM_ComparePoints +====================== +*/ +#define POINT_EPSILON 0.1 +static bool CM_ComparePoints( float *a, float *b ) { + float d; + + d = a[0] - b[0]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return false; + } + d = a[1] - b[1]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return false; + } + d = a[2] - b[2]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return false; + } + return true; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) { + break; + } + } + + if ( j != grid->height ) { + continue; // not degenerate + } + + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + grid->width--; + + // check against the next column + i--; + } +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static int numPlanes; +static patchPlane_t planes[MAX_PATCH_PLANES]; + +static int numFacets; +static facet_t facets[MAX_FACETS]; + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.02 + +/* +================== +CM_PlaneEqual +================== +*/ +int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) { + float invplane[4]; + + if ( + fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON + && fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON + && fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON + && fabs(p->plane[3] - plane[3]) < DIST_EPSILON ) + { + *flipped = false; + return true; + } + + VectorNegate(plane, invplane); + invplane[3] = -plane[3]; + + if ( + fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON + && fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON + && fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON + && fabs(p->plane[3] - invplane[3]) < DIST_EPSILON ) + { + *flipped = true; + return true; + } + + return false; +} + +/* +================== +CM_SnapVector +================== +*/ +void CM_SnapVector(vec3_t normal) { + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} + +/* +================== +CM_FindPlane2 +================== +*/ +int CM_FindPlane2(float plane[4], int *flipped) { + int i; + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if (CM_PlaneEqual(&planes[i], plane, flipped)) return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + *flipped = false; + + return numPlanes-1; +} + +/* +================== +CM_FindPlane +================== +*/ +static int CM_FindPlane( float *p1, float *p2, float *p3 ) { + float plane[4]; + int i; + float d; + + if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { + return -1; + } + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( DotProduct( plane, planes[i].plane ) < 0 ) { + continue; // allow backwards planes? + } + + d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + // found it + return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + return numPlanes-1; +} + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static int CM_PointOnPlaneSide( float *p, int planeNum ) { + float *plane; + float d; + + if ( planeNum == -1 ) { + return SIDE_ON; + } + plane = planes[ planeNum ].plane; + + d = DotProduct( p, plane ) - plane[3]; + + if ( d > PLANE_TRI_EPSILON ) { + return SIDE_FRONT; + } + + if ( d < -PLANE_TRI_EPSILON ) { + return SIDE_BACK; + } + + return SIDE_ON; +} + +/* +================== +CM_GridPlane +================== +*/ +static int CM_GridPlane( int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int tri ) { + int p; + + p = gridPlanes[i][j][tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i][j][!tri]; + if ( p != -1 ) { + return p; + } + + // should never happen + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); + return -1; +} + +/* +================== +CM_EdgePlaneNum +================== +*/ +static int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + if ( p == -1 ) { + return -1; + } + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j+1]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + if ( p == -1 ) { + return -1; + } + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + if ( p == -1 ) { + return -1; + } + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i+1][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + if ( p == -1 ) { + return -1; + } + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + if ( p == -1 ) { + return -1; + } + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + if ( p == -1 ) { + return -1; + } + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + +/* +=================== +CM_SetBorderInward +=================== +*/ +static void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case -1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + points[3] = grid->points[i][j+1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i+1][j+1]; + points[1] = grid->points[i][j+1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = true; + } else if ( back && !front ) { + facet->borderInward[k] = false; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); + facet->borderInward[k] = false; + if ( !debugBlock ) { + debugBlock = true; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); + } + } + } +} + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +static bool CM_ValidateFacet( facet_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return false; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + FreeWinding( w ); + return false; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return false; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { + return false; // we must be missing a plane + } + if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { + return false; + } + if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { + return false; + } + } + return true; // winding is fine +} + +/* +================== +CM_AddFacetBevels +================== +*/ +void CM_AddFacetBevels( facet_t *facet ) { + + int i, j, k, l; + int axis, dir, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if (facet->borderPlanes[j] == facet->surfacePlane) continue; + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds(w, mins, maxs); + + // add the axial planes + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + VectorClear(plane); + plane[axis] = dir; + if (dir == 1) { + plane[3] = maxs[axis]; + } + else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) + break; + } + + if ( i == facet->numBorders ) { + if ( facet->numBorders >= 4 + 6 + 16 ) { + Com_Printf( "ERROR: too many bevels\n" ); + continue; + } + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + facet->borderNoAdjust[facet->numBorders] = false; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + //if it's a degenerate edge + if (VectorNormalize (vec) < 0.5) + continue; + CM_SnapVector(vec); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if ( k < 3 ) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, plane); + if (VectorNormalize (plane) < 0.5) + continue; + plane[3] = DotProduct (w->p[j], plane); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct (w->p[l], plane) - plane[3]; + if (d > 0.1) + break; // point in front + } + if ( l < w->numpoints ) + continue; + + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { + break; + } + } + + if ( i == facet->numBorders ) { + if ( facet->numBorders >= 4 + 6 + 16 ) { + Com_Printf( "ERROR: too many bevels\n" ); + continue; + } + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if (facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); + } + + facet->borderNoAdjust[facet->numBorders] = false; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding(w); + Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); + if (!facet->borderInward[facet->numBorders]) + { + VectorNegate(newplane, newplane); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if (!w2) { + Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); + continue; + } + else { + FreeWinding(w2); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + if ( facet->numBorders >= 4 + 6 + 16 ) { + Com_Printf( "ERROR: too many bevels\n" ); + return; + } + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = false; + facet->borderInward[facet->numBorders] = true; + facet->numBorders++; +#endif //BSPC + +} + +typedef enum { + EN_TOP, + EN_RIGHT, + EN_BOTTOM, + EN_LEFT +} edgeName_t; + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) { + int i, j; + float *p1, *p2, *p3; + int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2]; + facet_t *facet; + int borders[4]; + int noAdjust[4]; + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p3 = grid->points[i+1][j+1]; + gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j+1]; + p3 = grid->points[i][j]; + gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i][j-1][1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i][grid->height-2][1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i][j+1][0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i][0][0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[i-1][j][0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[grid->width-2][j][0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[i+1][j][1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0][j][1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + ::memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) { + if ( gridPlanes[i][j][0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = (bool)noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = (bool)noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = borders[EN_BOTTOM]; + facet->borderNoAdjust[2] = (bool)noAdjust[EN_BOTTOM]; + facet->borderPlanes[3] = borders[EN_LEFT]; + facet->borderNoAdjust[3] = (bool)noAdjust[EN_LEFT]; + CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = (bool)noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = (bool)noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = gridPlanes[i][j][1]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + ::memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i][j][1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + facet->borderNoAdjust[0] = (bool)noAdjust[EN_BOTTOM]; + facet->borderPlanes[1] = borders[EN_LEFT]; + facet->borderNoAdjust[1] = (bool)noAdjust[EN_LEFT]; + facet->borderPlanes[2] = gridPlanes[i][j][0]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + pf->facets = (facet_t*)Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high ); + ::memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) ); + pf->planes = (patchPlane_t*)Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high ); + ::memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); +} + + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) { + patchCollide_t *pf; + cGrid_t grid; + int i, j; + + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, (void *)points ); + } + + if ( !(width & 1) || !(height & 1) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" ); + } + + // build a grid + grid.width = width; + grid.height = height; + grid.wrapWidth = false; + grid.wrapHeight = false; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j*width + i], grid.points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + CM_TransposeGrid( &grid ); + + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + pf = (patchCollide_t*)Hunk_Alloc( sizeof( *pf ), h_high ); + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid.width ; i++ ) { + for ( j = 0 ; j < grid.height ; j++ ) { + AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( &grid, pf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + + return pf; +} + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + +/* +==================== +CM_TracePointThroughPatchCollide + + special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + bool frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer || !tw->isPoint ) { + return; + } +#endif + + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = false; + } else { + frontFacing[i] = true; + } + if ( d1 == d2 ) { + intersection[i] = 99999; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = 99999; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > tw->trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( frontFacing[k] ^ facet->borderInward[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( tw->trace.fraction < 0 ) { + tw->trace.fraction = 0; + } + + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } + } +} + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) { + float d1, d2, f; + + *hit = false; + + d1 = DotProduct( start, plane ) - plane[3]; + d2 = DotProduct( end, plane ) - plane[3]; + + // if completely in front of face, no intersection with the entire facet + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return false; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + return true; + } + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + //always favor previous plane hits and thus also the surface plane hit + if (f > *enterFrac) { + *enterFrac = f; + *hit = true; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < *leaveFrac) { + *leaveFrac = f; + } + } + return true; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4] = {0, 0, 0, 0}, bestplane[4] = {0, 0, 0, 0}; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + + if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], + pc->bounds[0], pc->bounds[1] ) ) { + return; + } + + if (tw->isPoint) { + CM_TracePointThroughPatchCollide( tw, pc ); + return; + } + + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + continue; + } + if (hit) { + Vector4Copy(plane, bestplane); + } + + for ( j = 0; j < facet->numBorders; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + break; + } + if (hit) { + hitnum = j; + Vector4Copy(plane, bestplane); + } + } + if (j < facet->numBorders) continue; + //never clip against the back side + if (hitnum == facet->numBorders - 1) continue; + + if (enterFrac < leaveFrac && enterFrac >= 0) { + if (enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv && cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + + tw->trace.fraction = enterFrac; + VectorCopy( bestplane, tw->trace.plane.normal ); + tw->trace.plane.dist = bestplane[3]; + } + } + } +} + + +/* +======================================================================= + +POSITION TEST + +======================================================================= +*/ + +/* +==================== +CM_PositionTestInPatchCollide +==================== +*/ +bool CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int i, j; + float offset, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4]; + vec3_t startp; + + if (tw->isPoint) { + return false; + } + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + } + + if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { + continue; + } + + for ( j = 0; j < facet->numBorders; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + } + + if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { + break; + } + } + if (j < facet->numBorders) { + continue; + } + // inside this patch facet + return true; + } + return false; +} + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) { + static cvar_t *cv; +#ifndef BSPC + static cvar_t *cv2; +#endif + const patchCollide_t *pc; + facet_t *facet; + winding_t *w; + int i, j, k, n; + int curplanenum, planenum, curinward, inward; + float plane[4]; + vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28}; + //vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0}; + vec3_t v1, v2; + +#ifndef BSPC + if ( !cv2 ) + { + cv2 = Cvar_Get( "r_debugSurface", "0", 0 ); + } + + if (cv2->integer != 1) + { + return; + } +#endif + + if ( !debugPatchCollide ) { + return; + } + +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "cm_debugSize", "2", 0 ); + } +#endif + pc = debugPatchCollide; + + for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) { + + for ( k = 0 ; k < facet->numBorders + 1; k++ ) { + // + if (k < facet->numBorders) { + planenum = facet->borderPlanes[k]; + inward = facet->borderInward[k]; + } + else { + planenum = facet->surfacePlane; + inward = false; + //continue; + } + + Vector4Copy( pc->planes[ planenum ].plane, plane ); + + //planenum = facet->surfacePlane; + if ( inward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + plane[3] += cv->value; + //* + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] += fabs(DotProduct(v1, v2)); + //*/ + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) { + // + if (j < facet->numBorders) { + curplanenum = facet->borderPlanes[j]; + curinward = facet->borderInward[j]; + } + else { + curplanenum = facet->surfacePlane; + curinward = false; + //continue; + } + // + if (curplanenum == planenum) continue; + + Vector4Copy( pc->planes[ curplanenum ].plane, plane ); + if ( !curinward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + // if ( !facet->borderNoAdjust[j] ) { + plane[3] -= cv->value; + // } + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] -= fabs(DotProduct(v1, v2)); + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( w ) { + if ( facet == debugFacet ) { + drawPoly( 4, w->numpoints, w->p[0] ); + //Com_Printf("blue facet has %d border planes\n", facet->numBorders); + } else { + drawPoly( 1, w->numpoints, w->p[0] ); + } + FreeWinding( w ); + } + else + Com_Printf("winding chopped away by border planes\n"); + } + } + + // draw the debug block + { + vec3_t v[3]; + + VectorCopy( debugBlockPoints[0], v[0] ); + VectorCopy( debugBlockPoints[1], v[1] ); + VectorCopy( debugBlockPoints[2], v[2] ); + drawPoly( 2, 3, v[0] ); + + VectorCopy( debugBlockPoints[2], v[0] ); + VectorCopy( debugBlockPoints[3], v[1] ); + VectorCopy( debugBlockPoints[0], v[2] ); + drawPoly( 2, 3, v[0] ); + } + +#if 0 + vec3_t v[4]; + + v[0][0] = pc->bounds[1][0]; + v[0][1] = pc->bounds[1][1]; + v[0][2] = pc->bounds[1][2]; + + v[1][0] = pc->bounds[1][0]; + v[1][1] = pc->bounds[0][1]; + v[1][2] = pc->bounds[1][2]; + + v[2][0] = pc->bounds[0][0]; + v[2][1] = pc->bounds[0][1]; + v[2][2] = pc->bounds[1][2]; + + v[3][0] = pc->bounds[0][0]; + v[3][1] = pc->bounds[1][1]; + v[3][2] = pc->bounds[1][2]; + + drawPoly( 4, v[0] ); +#endif +} diff --git a/src/qcommon/cm_patch.h b/src/qcommon/cm_patch.h new file mode 100644 index 0000000..50e10af --- /dev/null +++ b/src/qcommon/cm_patch.h @@ -0,0 +1,105 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +bool CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + + +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + bool borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + bool wrapWidth; + bool wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 + + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); diff --git a/src/qcommon/cm_polylib.cpp b/src/qcommon/cm_polylib.cpp new file mode 100644 index 0000000..ca1c94c --- /dev/null +++ b/src/qcommon/cm_polylib.cpp @@ -0,0 +1,737 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// this is only used for visualization tools in cm_ debug functions + + +#include "cm_local.h" + + +// counters are only bumped when running single threaded, +// because they are an awful coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + + s = sizeof(vec_t)*3*points + sizeof(int); + w = (winding_t*)Z_Malloc (s); + ::memset (w, 0, s); + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + c_active_windings--; + Z_Free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize2(v1,v1); + VectorNormalize2(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + c_removed += w->numpoints - nump; + w->numpoints = nump; + ::memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize2(normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +/* +============= +WindingBounds +============= +*/ +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = MAX_MAP_BOUNDS; + maxs[0] = maxs[1] = maxs[2] = -MAX_MAP_BOUNDS; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -MAX_MAP_BOUNDS; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize2(vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, MAX_MAP_BOUNDS, vup); + VectorScale (vright, MAX_MAP_BOUNDS, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + intptr_t size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (intptr_t) ((winding_t *)0)->p[w->numpoints]; + ::memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4] = { 0 }; + int sides[MAX_POINTS_ON_WINDING+4] = { 0 }; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4] = { 0 }; + int sides[MAX_POINTS_ON_WINDING+4] = { 0 }; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Com_Error (ERR_DROP, "CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS) + Com_Error (ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize2 (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Com_Error (ERR_DROP, "CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + bool front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + bool hullSide[MAX_HULL_POINTS]; + bool outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = (*hull)->numpoints; + ::memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize2( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = false; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = true; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = (j+1)%numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ (j+k+1) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + ::memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + ::memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) ); +} diff --git a/src/qcommon/cm_polylib.h b/src/qcommon/cm_polylib.h new file mode 100644 index 0000000..0a04bd6 --- /dev/null +++ b/src/qcommon/cm_polylib.h @@ -0,0 +1,74 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// this is only used for visualization tools in cm_ debug functions + +#ifndef CM_POLYLIB_H +#define CM_POLYLIB_H 1 + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define CLIP_EPSILON 0.1f + +#define MAX_MAP_BOUNDS 65535 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1f +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); + +#endif diff --git a/src/qcommon/cm_public.h b/src/qcommon/cm_public.h new file mode 100644 index 0000000..5b3a46f --- /dev/null +++ b/src/qcommon/cm_public.h @@ -0,0 +1,79 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef _CM_PUBLIC_H_ +#define _CM_PUBLIC_H_ + +#include "qfiles.h" + +void CM_LoadMap( const char *name, bool clientload, int *checksum); +void CM_ClearMap( void ); +clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ); + +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); + +int CM_NumClusters (void); +int CM_NumInlineModels( void ); +char *CM_EntityString (void); + +// returns an ORed contents mask +int CM_PointContents( const vec3_t p, clipHandle_t model ); +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, traceType_t type ); +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, traceType_t type ); +void CM_BiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask ); +void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask, + const vec3_t origin ); + +byte *CM_ClusterPVS (int cluster); + +int CM_PointLeafnum( const vec3_t p ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, + int listsize, int *lastLeaf ); + +int CM_LeafCluster (int leafnum); +int CM_LeafArea (int leafnum); + +void CM_AdjustAreaPortalState( int area1, int area2, bool open ); +bool CM_AreasConnected( int area1, int area2 ); + +int CM_WriteAreaBits( byte *buffer, int area ); + +// cm_patch.c +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ); + +#endif diff --git a/src/qcommon/cm_test.cpp b/src/qcommon/cm_test.cpp new file mode 100644 index 0000000..b7f8b8f --- /dev/null +++ b/src/qcommon/cm_test.cpp @@ -0,0 +1,526 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "cm_local.h" + +/* +================== +CM_PointLeafnum_r + +================== +*/ +int CM_PointLeafnum_r( const vec3_t p, int num ) { + float d; + cNode_t *node; + cplane_t *plane; + + while (num >= 0) + { + node = cm.nodes + num; + plane = node->plane; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + c_pointcontents++; // optimize counter + + return -1 - num; +} + +int CM_PointLeafnum( const vec3_t p ) { + if ( !cm.numNodes ) { // map not loaded + return 0; + } + return CM_PointLeafnum_r (p, 0); +} + + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ + + +void CM_StoreLeafs( leafList_t *ll, int nodenum ) { + int leafNum; + + leafNum = -1 - nodenum; + + // store the lastLeaf even if the list is overflowed + if ( cm.leafs[ leafNum ].cluster != -1 ) { + ll->lastLeaf = leafNum; + } + + if ( ll->count >= ll->maxcount) { + ll->overflowed = true; + return; + } + ll->list[ ll->count++ ] = leafNum; +} + +void CM_StoreBrushes( leafList_t *ll, int nodenum ) { + int i, k; + int leafnum; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + + leafnum = -1 - nodenum; + + leaf = &cm.leafs[leafnum]; + + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + for ( i = 0 ; i < 3 ; i++ ) { + if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) { + break; + } + } + if ( i != 3 ) { + continue; + } + if ( ll->count >= ll->maxcount) { + ll->overflowed = true; + return; + } + ((cbrush_t **)ll->list)[ ll->count++ ] = b; + } +#if 0 + // store patches? + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ]; + if ( !patch ) { + continue; + } + } +#endif +} + +/* +============= +CM_BoxLeafnums + +Fills in a list of all the leafs touched +============= +*/ +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) { + cplane_t *plane; + cNode_t *node; + int s; + + while (1) { + if (nodenum < 0) { + ll->storeLeafs( ll, nodenum ); + return; + } + + node = &cm.nodes[nodenum]; + plane = node->plane; + s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane ); + if (s == 1) { + nodenum = node->children[0]; + } else if (s == 2) { + nodenum = node->children[1]; + } else { + // go down both + CM_BoxLeafnums_r( ll, node->children[0] ); + nodenum = node->children[1]; + } + + } +} + +/* +================== +CM_BoxLeafnums +================== +*/ +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *lastLeaf) { + leafList_t ll; + + cm.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = list; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = false; + + CM_BoxLeafnums_r( &ll, 0 ); + + *lastLeaf = ll.lastLeaf; + return ll.count; +} + +/* +================== +CM_BoxBrushes +================== +*/ +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ) { + leafList_t ll; + + cm.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = (int *)list; + ll.storeLeafs = CM_StoreBrushes; + ll.lastLeaf = 0; + ll.overflowed = false; + + CM_BoxLeafnums_r( &ll, 0 ); + + return ll.count; +} + + +//==================================================================== + + +/* +================== +CM_PointContents + +================== +*/ +int CM_PointContents( const vec3_t p, clipHandle_t model ) { + int leafnum; + int i, k; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + int contents; + float d; + cmodel_t *clipm; + + if (!cm.numNodes) { // map not loaded + return 0; + } + + if ( model ) { + clipm = CM_ClipHandleToModel( model ); + leaf = &clipm->leaf; + } else { + leafnum = CM_PointLeafnum_r (p, 0); + leaf = &cm.leafs[leafnum]; + } + + if(leaf->area == -1) + return CONTENTS_SOLID; + + contents = 0; + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + b = &cm.brushes[brushnum]; + + if ( !CM_BoundsIntersectPoint( b->bounds[0], b->bounds[1], p ) ) { + continue; + } + + // see if the point is in the brush + for ( i = 0 ; i < b->numsides ; i++ ) { + d = DotProduct( p, b->sides[i].plane->normal ); +// FIXME test for Cash +// if ( d >= b->sides[i].plane->dist ) { + if ( d > b->sides[i].plane->dist ) { + break; + } + } + + if ( i == b->numsides ) { + contents |= b->contents; + } + } + + return contents; +} + +/* +================== +CM_TransformedPointContents + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) { + vec3_t p_l; + vec3_t temp; + vec3_t forward, right, up; + + // subtract origin offset + VectorSubtract (p, origin, p_l); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) + { + AngleVectors (angles, forward, right, up); + + VectorCopy (p_l, temp); + p_l[0] = DotProduct (temp, forward); + p_l[1] = -DotProduct (temp, right); + p_l[2] = DotProduct (temp, up); + } + + return CM_PointContents( p_l, model ); +} + + + +/* +=============================================================================== + +PVS + +=============================================================================== +*/ + +byte *CM_ClusterPVS (int cluster) { + if (cluster < 0 || cluster >= cm.numClusters || !cm.vised ) { + return cm.visibility; + } + + return cm.visibility + cluster * cm.clusterBytes; +} + + + +/* +=============================================================================== + +AREAPORTALS + +=============================================================================== +*/ + +void CM_FloodArea_r( int areaNum, int floodnum) { + int i; + cArea_t *area; + int *con; + + area = &cm.areas[ areaNum ]; + + if ( area->floodvalid == cm.floodvalid ) { + if (area->floodnum == floodnum) + return; + Com_Error (ERR_DROP, "FloodArea_r: reflooded"); + } + + area->floodnum = floodnum; + area->floodvalid = cm.floodvalid; + con = cm.areaPortals + areaNum * cm.numAreas; + for ( i=0 ; i < cm.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum ); + } + } +} + +/* +==================== +CM_FloodAreaConnections + +==================== +*/ +void CM_FloodAreaConnections( void ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cm.floodvalid++; + floodnum = 0; + + for (i = 0 ; i < cm.numAreas ; i++) { + area = &cm.areas[i]; + if (area->floodvalid == cm.floodvalid) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r (i, floodnum); + } + +} + +/* +==================== +CM_AdjustAreaPortalState + +==================== +*/ +void CM_AdjustAreaPortalState( int area1, int area2, bool open ) { + if ( area1 < 0 || area2 < 0 ) { + return; + } + + if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) { + Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number"); + } + + if ( open ) { + cm.areaPortals[ area1 * cm.numAreas + area2 ]++; + cm.areaPortals[ area2 * cm.numAreas + area1 ]++; + } else { + cm.areaPortals[ area1 * cm.numAreas + area2 ]--; + cm.areaPortals[ area2 * cm.numAreas + area1 ]--; + if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] < 0 ) { + Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count"); + } + } + + CM_FloodAreaConnections (); +} + +/* +==================== +CM_AreasConnected + +==================== +*/ +bool CM_AreasConnected( int area1, int area2 ) { +#ifndef BSPC + if ( cm_noAreas->integer ) { + return true; + } +#endif + + if ( area1 < 0 || area2 < 0 ) { + return false; + } + + if (area1 >= cm.numAreas || area2 >= cm.numAreas) { + Com_Error (ERR_DROP, "area >= cm.numAreas"); + } + + if (cm.areas[area1].floodnum == cm.areas[area2].floodnum) { + return true; + } + return false; +} + + +/* +================= +CM_WriteAreaBits + +Writes a bit vector of all the areas +that are in the same flood as the area parameter +Returns the number of bytes needed to hold all the bits. + +The bits are OR'd in, so you can CM_WriteAreaBits from multiple +viewpoints and get the union of all visible areas. + +This is used to cull non-visible entities from snapshots +================= +*/ +int CM_WriteAreaBits (byte *buffer, int area) +{ + int i; + int floodnum; + int bytes; + + bytes = (cm.numAreas+7)>>3; + +#ifndef BSPC + if (cm_noAreas->integer || area == -1) +#else + if ( area == -1) +#endif + { // for debugging, send everything + ::memset (buffer, 255, bytes); + } + else + { + floodnum = cm.areas[area].floodnum; + for (i=0 ; i>3] |= 1<<(i&7); + } + } + + return bytes; +} + +/* +==================== +CM_BoundsIntersect +==================== +*/ +bool CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 ) +{ + if (maxs[0] < mins2[0] - SURFACE_CLIP_EPSILON || + maxs[1] < mins2[1] - SURFACE_CLIP_EPSILON || + maxs[2] < mins2[2] - SURFACE_CLIP_EPSILON || + mins[0] > maxs2[0] + SURFACE_CLIP_EPSILON || + mins[1] > maxs2[1] + SURFACE_CLIP_EPSILON || + mins[2] > maxs2[2] + SURFACE_CLIP_EPSILON) + { + return false; + } + + return true; +} + +/* +==================== +CM_BoundsIntersectPoint +==================== +*/ +bool CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ) +{ + if (maxs[0] < point[0] - SURFACE_CLIP_EPSILON || + maxs[1] < point[1] - SURFACE_CLIP_EPSILON || + maxs[2] < point[2] - SURFACE_CLIP_EPSILON || + mins[0] > point[0] + SURFACE_CLIP_EPSILON || + mins[1] > point[1] + SURFACE_CLIP_EPSILON || + mins[2] > point[2] + SURFACE_CLIP_EPSILON) + { + return false; + } + + return true; +} diff --git a/src/qcommon/cm_trace.cpp b/src/qcommon/cm_trace.cpp new file mode 100644 index 0000000..b773f2a --- /dev/null +++ b/src/qcommon/cm_trace.cpp @@ -0,0 +1,1801 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "cm_local.h" + +// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa +//#define ALWAYS_BBOX_VS_BBOX +// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa +//#define ALWAYS_CAPSULE_VS_CAPSULE + +//#define CAPSULE_DEBUG + +/* +=============================================================================== + +BASIC MATH + +=============================================================================== +*/ + +/* +================ +RotatePoint +================ +*/ +void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // FIXME + vec3_t tvec; + + VectorCopy(point, tvec); + point[0] = DotProduct(matrix[0], tvec); + point[1] = DotProduct(matrix[1], tvec); + point[2] = DotProduct(matrix[2], tvec); +} + +/* +================ +TransposeMatrix +================ +*/ +void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // FIXME + int i, j; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + transpose[i][j] = matrix[j][i]; + } + } +} + +/* +================ +CreateRotationMatrix +================ +*/ +void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) { + AngleVectors(angles, matrix[0], matrix[1], matrix[2]); + VectorInverse(matrix[1]); +} + +/* +================ +CM_ProjectPointOntoVector +================ +*/ +void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj ) +{ + vec3_t pVec; + + VectorSubtract( point, vStart, pVec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj ); +} + +/* +================ +CM_DistanceFromLineSquared +================ +*/ +float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) { + vec3_t proj, t; + int j; + + CM_ProjectPointOntoVector(p, lp1, dir, proj); + for (j = 0; j < 3; j++) + if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || + (proj[j] < lp1[j] && proj[j] < lp2[j])) + break; + if (j < 3) { + if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) + VectorSubtract(p, lp1, t); + else + VectorSubtract(p, lp2, t); + return VectorLengthSquared(t); + } + VectorSubtract(p, proj, t); + return VectorLengthSquared(t); +} + +/* +================ +CM_VectorDistanceSquared +================ +*/ +float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) { + vec3_t dir; + + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} + +/* +================ +SquareRootFloat +================ +*/ +float SquareRootFloat(float number) { + floatint_t t; + float x, y; + const float f = 1.5F; + + x = number * 0.5F; + t.f = number; + t.i = 0x5f3759df - ( t.i >> 1 ); + y = t.f; + y = y * ( f - ( x * y * y ) ); + y = y * ( f - ( x * y * y ) ); + return number * y; +} + + +/* +=============================================================================== + +POSITION TESTING + +=============================================================================== +*/ + +/* +================ +CM_TestBoxInBrush +================ +*/ +void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane; + float dist; + float d1; + cbrushside_t *side; + float t; + vec3_t startp; + + if (!brush->numsides) { + return; + } + + // special test for axial + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + if ( tw->type == TT_CAPSULE ) { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + d1 = DotProduct( startp, plane->normal ) - dist; + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } else { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } + + // inside this brush + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = brush->contents; +} + + + +/* +================ +CM_TestInLeaf +================ +*/ +void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // test box position against all brushes in the leaf + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + b = &cm.brushes[brushnum]; + if (b->checkcount == cm.checkcount) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + + if ( !(b->contents & tw->contents)) { + continue; + } + + CM_TestBoxInBrush( tw, b ); + if ( tw->trace.allsolid ) { + return; + } + } + + // test against all patches +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif //BSPC + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + patch->checkcount = cm.checkcount; + + if ( !(patch->contents & tw->contents)) { + continue; + } + + if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = patch->contents; + return; + } + } + } +} + +/* +================== +CM_TestCapsuleInCapsule + +capsule inside capsule check +================== +*/ +void CM_TestCapsuleInCapsule( traceWork_t *tw, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom; + vec3_t p1, p2, tmp; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, r; + + CM_ModelBounds(model, mins, maxs); + + VectorAdd(tw->start, tw->sphere.offset, top); + VectorSubtract(tw->start, tw->sphere.offset, bottom); + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + + r = Square(tw->sphere.radius + radius); + // check if any of the spheres overlap + VectorCopy(offset, p1); + p1[2] += offs; + VectorSubtract(p1, top, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorSubtract(p1, bottom, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorCopy(offset, p2); + p2[2] -= offs; + VectorSubtract(p2, top, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorSubtract(p2, bottom, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + // if between cylinder up and lower bounds + if ( (top[2] >= p1[2] && top[2] <= p2[2]) || + (bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) { + // 2d coordinates + top[2] = p1[2] = 0; + // if the cylinders overlap + VectorSubtract(top, p1, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + } +} + +/* +================== +CM_TestBoundingBoxInCapsule + +bounding box inside capsule check +================== +*/ +void CM_TestBoundingBoxInCapsule( traceWork_t *tw, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds(model, mins, maxs); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->type = TT_CAPSULE; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel(tw->size[0], tw->size[1], false); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TestInLeaf( tw, &cmod->leaf ); +} + +/* +================== +CM_PositionTest +================== +*/ +#define MAX_POSITION_LEAFS 1024 +void CM_PositionTest( traceWork_t *tw ) { + int leafs[MAX_POSITION_LEAFS]; + int i; + leafList_t ll; + + // identify the leafs we are touching + VectorAdd( tw->start, tw->size[0], ll.bounds[0] ); + VectorAdd( tw->start, tw->size[1], ll.bounds[1] ); + + for (i=0 ; i<3 ; i++) { + ll.bounds[0][i] -= 1; + ll.bounds[1][i] += 1; + } + + ll.count = 0; + ll.maxcount = MAX_POSITION_LEAFS; + ll.list = leafs; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = false; + + cm.checkcount++; + + CM_BoxLeafnums_r( &ll, 0 ); + + + cm.checkcount++; + + // test the contents of the leafs + for (i=0 ; i < ll.count ; i++) { + CM_TestInLeaf( tw, &cm.leafs[leafs[i]] ); + if ( tw->trace.allsolid ) { + break; + } + } +} + +/* +=============================================================================== + +TRACING + +=============================================================================== +*/ + +/* +================ +CM_TraceThroughPatch +================ +*/ + +void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) { + float oldFrac; + + c_patch_traces++; + + oldFrac = tw->trace.fraction; + + CM_TraceThroughPatchCollide( tw, patch->pc ); + + if ( tw->trace.fraction < oldFrac ) { + tw->trace.surfaceFlags = patch->surfaceFlags; + tw->trace.contents = patch->contents; + } +} + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane, *clipplane; + float dist; + float enterFrac, leaveFrac; + float d1, d2; + bool getout, startout; + float f; + cbrushside_t *side, *leadside; + float t; + vec3_t startp; + vec3_t endp; + + enterFrac = -1.0; + leaveFrac = 1.0; + clipplane = NULL; + + if ( !brush->numsides ) { + return; + } + + c_brush_traces++; + + getout = false; + startout = false; + + leadside = NULL; + + if( tw->type == TT_BISPHERE ) + { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for( i = 0; i < brush->numsides; i++ ) + { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + d1 = DotProduct( tw->start, plane->normal ) - + ( plane->dist + tw->biSphere.startRadius ); + d2 = DotProduct( tw->end, plane->normal ) - + ( plane->dist + tw->biSphere.endRadius ); + + if( d2 > 0 ) + getout = true; // endpoint is not in solid + + if( d1 > 0 ) + startout = true; + + // if completely in front of face, no intersection with the entire brush + if( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) + return; + + // if it doesn't cross the plane, the plane isn't relevent + if( d1 <= 0 && d2 <= 0 ) + continue; + + brush->collided = true; + + // crosses face + if( d1 > d2 ) + { + // enter + f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if( f < 0 ) + f = 0; + + if( f > enterFrac ) + { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } + else + { + // leave + f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if( f > 1 ) + f = 1; + + if( f < leaveFrac ) + leaveFrac = f; + } + } + } + else if ( tw->type == TT_CAPSULE ) { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i = 0; i < brush->numsides; i++) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + + d1 = DotProduct( startp, plane->normal ) - dist; + d2 = DotProduct( endp, plane->normal ) - dist; + + if (d2 > 0) { + getout = true; // endpoint is not in solid + } + if (d1 > 0) { + startout = true; + } + + // if completely in front of face, no intersection with the entire brush + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + brush->collided = true; + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + if (f > enterFrac) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < leaveFrac) { + leaveFrac = f; + } + } + } + } else { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i = 0; i < brush->numsides; i++) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + d2 = DotProduct( tw->end, plane->normal ) - dist; + + if (d2 > 0) { + getout = true; // endpoint is not in solid + } + if (d1 > 0) { + startout = true; + } + + // if completely in front of face, no intersection with the entire brush + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + brush->collided = true; + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + if (f > enterFrac) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < leaveFrac) { + leaveFrac = f; + } + } + } + } + + // + // all planes have been checked, and the trace was not + // completely outside the brush + // + if (!startout) { // original point was inside brush + tw->trace.startsolid = qtrue; + if (!getout) { + tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = brush->contents; + } + return; + } + + if (enterFrac < leaveFrac) { + if (enterFrac > -1 && enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } + tw->trace.fraction = enterFrac; + if (clipplane != NULL) { + tw->trace.plane = *clipplane; + } + if (leadside != NULL) { + tw->trace.surfaceFlags = leadside->surfaceFlags; + } + tw->trace.contents = brush->contents; + } + } +} + +/* +================ +CM_ProximityToBrush +================ +*/ +static void CM_ProximityToBrush( traceWork_t *tw, cbrush_t *brush ) +{ + int i; + cbrushedge_t *edge; + float dist, minDist = 1e+10f; + float s, t; + float sAtMin = 0.0f; + float radius = 0.0f, fraction; + traceWork_t tw2; + + // cheapish purely linear trace to test for intersection + ::memset( &tw2, 0, sizeof( tw2 ) ); + tw2.trace.fraction = 1.0f; + tw2.type = TT_CAPSULE; + tw2.sphere.radius = 0.0f; + VectorClear( tw2.sphere.offset ); + VectorCopy( tw->start, tw2.start ); + VectorCopy( tw->end, tw2.end ); + + CM_TraceThroughBrush( &tw2, brush ); + + if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) + { + for( i = 0; i < brush->numEdges; i++ ) + { + edge = &brush->edges[ i ]; + + dist = DistanceBetweenLineSegmentsSquared( tw->start, tw->end, + edge->p0, edge->p1, &s, &t ); + + if( dist < minDist ) + { + minDist = dist; + sAtMin = s; + } + } + + if( tw->type == TT_BISPHERE ) + { + radius = tw->biSphere.startRadius + + ( sAtMin * ( tw->biSphere.endRadius - tw->biSphere.startRadius ) ); + } + else if( tw->type == TT_CAPSULE ) + { + radius = tw->sphere.radius; + } + else if( tw->type == TT_AABB ) + { + //FIXME + } + + fraction = minDist / ( radius * radius ); + + if( fraction < tw->trace.lateralFraction ) + tw->trace.lateralFraction = fraction; + } + else + tw->trace.lateralFraction = 0.0f; +} + +/* +================ +CM_ProximityToPatch +================ +*/ +static void CM_ProximityToPatch( traceWork_t *tw, cPatch_t *patch ) +{ + traceWork_t tw2; + + // cheapish purely linear trace to test for intersection + ::memset( &tw2, 0, sizeof( tw2 ) ); + tw2.trace.fraction = 1.0f; + tw2.type = TT_CAPSULE; + tw2.sphere.radius = 0.0f; + VectorClear( tw2.sphere.offset ); + VectorCopy( tw->start, tw2.start ); + VectorCopy( tw->end, tw2.end ); + + CM_TraceThroughPatch( &tw2, patch ); + + if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) + { + //FIXME: implement me + } + else + tw->trace.lateralFraction = 0.0f; +} + +/* +================ +CM_TraceThroughLeaf +================ +*/ +void CM_TraceThroughLeaf( traceWork_t *tw, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // trace line against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + + if ( !(b->contents & tw->contents) ) { + continue; + } + + b->collided = false; + + if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], + b->bounds[0], b->bounds[1] ) ) { + continue; + } + + CM_TraceThroughBrush( tw, b ); + if ( !tw->trace.fraction ) { + tw->trace.lateralFraction = 0.0f; + return; + } + } + + // trace line against all patches in the leaf +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == cm.checkcount ) { + continue; // already checked this patch in another leaf + } + patch->checkcount = cm.checkcount; + + if ( !(patch->contents & tw->contents) ) { + continue; + } + + CM_TraceThroughPatch( tw, patch ); + + if ( !tw->trace.fraction ) { + tw->trace.lateralFraction = 0.0f; + return; + } + } + } + + if( tw->testLateralCollision && tw->trace.fraction < 1.0f ) + { + for( k = 0; k < leaf->numLeafBrushes; k++ ) + { + brushnum = cm.leafbrushes[ leaf->firstLeafBrush + k ]; + + b = &cm.brushes[ brushnum ]; + + // This brush never collided, so don't bother + if( !b->collided ) + continue; + + if( !( b->contents & tw->contents ) ) + continue; + + CM_ProximityToBrush( tw, b ); + + if( !tw->trace.lateralFraction ) + return; + } + + for( k = 0; k < leaf->numLeafSurfaces; k++ ) + { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if( !patch ) + continue; + + if( !( patch->contents & tw->contents ) ) + continue; + + CM_ProximityToPatch( tw, patch ); + + if( !tw->trace.lateralFraction ) + return; + } + } +} + +#define RADIUS_EPSILON 1.0f + +/* +================ +CM_TraceThroughSphere + +get the first intersection of the ray with the sphere +================ +*/ +void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) { + float l1, l2, length, scale, fraction; + //float a; + float b, c, d, sqrtd; + vec3_t v1, dir, intersection; + + // if inside the sphere + VectorSubtract(start, origin, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.fraction = 0; + tw->trace.startsolid = qtrue; + // test for allsolid + VectorSubtract(end, origin, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.allsolid = qtrue; + } + return; + } + // + VectorSubtract(end, start, dir); + length = VectorNormalize(dir); + // + l1 = CM_DistanceFromLineSquared(origin, start, end, dir); + VectorSubtract(end, origin, v1); + l2 = VectorLengthSquared(v1); + // if no intersection with the sphere and the end point is at least an epsilon away + if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { + return; + } + // + // | origin - (start + t * dir) | = radius + // a = dir[0]^2 + dir[1]^2 + dir[2]^2; + // b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2])); + // c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2; + // + VectorSubtract(start, origin, v1); + // dir is normalized so a = 1 + //a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; + b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]); + c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + + d = b * b - 4.0f * c;// * a; + if (d > 0) { + sqrtd = SquareRootFloat(d); + // = (- b + sqrtd) * 0.5f; // / (2.0f * a); + fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a); + // + if (fraction < 0) { + fraction = 0; + } + else { + fraction /= length; + } + if ( fraction < tw->trace.fraction ) { + tw->trace.fraction = fraction; + VectorSubtract(end, start, dir); + VectorMA(start, fraction, dir, intersection); + VectorSubtract(intersection, origin, dir); + #ifdef CAPSULE_DEBUG + l2 = VectorLength(dir); + if (l2 < radius) { + int bah = 1; + } + #endif + scale = 1 / (radius+RADIUS_EPSILON); + VectorScale(dir, scale, dir); + VectorCopy(dir, tw->trace.plane.normal); + VectorAdd( tw->modelOrigin, intersection, intersection); + tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); + tw->trace.contents = CONTENTS_BODY; + } + } + else if (d == 0) { + //t1 = (- b ) / 2; + // slide along the sphere + } + // no intersection at all +} + +/* +================ +CM_TraceThroughVerticalCylinder + +get the first intersection of the ray with the cylinder +the cylinder extends halfheight above and below the origin +================ +*/ +void CM_TraceThroughVerticalCylinder( traceWork_t *tw, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end) { + float length, scale, fraction, l1, l2; + //float a; + float b, c, d, sqrtd; + vec3_t v1, dir, start2d, end2d, org2d, intersection; + + // 2d coordinates + VectorSet(start2d, start[0], start[1], 0); + VectorSet(end2d, end[0], end[1], 0); + VectorSet(org2d, origin[0], origin[1], 0); + // if between lower and upper cylinder bounds + if (start[2] <= origin[2] + halfheight && + start[2] >= origin[2] - halfheight) { + // if inside the cylinder + VectorSubtract(start2d, org2d, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.fraction = 0; + tw->trace.startsolid = qtrue; + VectorSubtract(end2d, org2d, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.allsolid = qtrue; + } + return; + } + } + // + VectorSubtract(end2d, start2d, dir); + length = VectorNormalize(dir); + // + l1 = CM_DistanceFromLineSquared(org2d, start2d, end2d, dir); + VectorSubtract(end2d, org2d, v1); + l2 = VectorLengthSquared(v1); + // if no intersection with the cylinder and the end point is at least an epsilon away + if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { + return; + } + // + // + // (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2 + // (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2; + // v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 + + // v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2 + // t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) + + // v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0 + // + VectorSubtract(start, origin, v1); + // dir is normalized so we can use a = 1 + //a = 1.0f;// * (dir[0] * dir[0] + dir[1] * dir[1]); + b = 2.0f * (v1[0] * dir[0] + v1[1] * dir[1]); + c = v1[0] * v1[0] + v1[1] * v1[1] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + + d = b * b - 4.0f * c;// * a; + if (d > 0) { + sqrtd = SquareRootFloat(d); + // = (- b + sqrtd) * 0.5f;// / (2.0f * a); + fraction = (- b - sqrtd) * 0.5f;// / (2.0f * a); + // + if (fraction < 0) { + fraction = 0; + } + else { + fraction /= length; + } + if ( fraction < tw->trace.fraction ) { + VectorSubtract(end, start, dir); + VectorMA(start, fraction, dir, intersection); + // if the intersection is between the cylinder lower and upper bound + if (intersection[2] <= origin[2] + halfheight && + intersection[2] >= origin[2] - halfheight) { + // + tw->trace.fraction = fraction; + VectorSubtract(intersection, origin, dir); + dir[2] = 0; + #ifdef CAPSULE_DEBUG + l2 = VectorLength(dir); + if (l2 <= radius) { + int bah = 1; + } + #endif + scale = 1 / (radius+RADIUS_EPSILON); + VectorScale(dir, scale, dir); + VectorCopy(dir, tw->trace.plane.normal); + VectorAdd( tw->modelOrigin, intersection, intersection); + tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); + tw->trace.contents = CONTENTS_BODY; + } + } + } + else if (d == 0) { + //t[0] = (- b ) / 2 * a; + // slide along the cylinder + } + // no intersection at all +} + +/* +================ +CM_TraceCapsuleThroughCapsule + +capsule vs. capsule collision (not rotated) +================ +*/ +void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom, starttop, startbottom, endtop, endbottom; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, h; + + CM_ModelBounds(model, mins, maxs); + // test trace bounds vs. capsule bounds + if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON + || tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON + || tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON + || tw->bounds[1][0] < mins[0] - RADIUS_EPSILON + || tw->bounds[1][1] < mins[1] - RADIUS_EPSILON + || tw->bounds[1][2] < mins[2] - RADIUS_EPSILON + ) { + return; + } + // top origin and bottom origin of each sphere at start and end of trace + VectorAdd(tw->start, tw->sphere.offset, starttop); + VectorSubtract(tw->start, tw->sphere.offset, startbottom); + VectorAdd(tw->end, tw->sphere.offset, endtop); + VectorSubtract(tw->end, tw->sphere.offset, endbottom); + + // calculate top and bottom of the capsule spheres to collide with + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + VectorCopy(offset, top); + top[2] += offs; + VectorCopy(offset, bottom); + bottom[2] -= offs; + // expand radius of spheres + radius += tw->sphere.radius; + // if there is horizontal movement + if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) { + // height of the expanded cylinder is the height of both cylinders minus the radius of both spheres + h = halfheight + tw->sphere.halfheight - radius; + // if the cylinder has a height + if ( h > 0 ) { + // test for collisions between the cylinders + CM_TraceThroughVerticalCylinder(tw, offset, radius, h, tw->start, tw->end); + } + } + // test for collision between the spheres + CM_TraceThroughSphere(tw, top, radius, startbottom, endbottom); + CM_TraceThroughSphere(tw, bottom, radius, starttop, endtop); +} + +/* +================ +CM_TraceBoundingBoxThroughCapsule + +bounding box vs. capsule collision +================ +*/ +void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds(model, mins, maxs); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->type = TT_CAPSULE; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel(tw->size[0], tw->size[1], false); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TraceThroughLeaf( tw, &cmod->leaf ); +} + +//========================================================================================= + +/* +================== +CM_TraceThroughTree + +Traverse all the contacted leafs from the start to the end position. +If the trace is a point, they will be exactly in order, but for larger +trace volumes it is possible to hit something in a later leaf with +a smaller intercept fraction. +================== +*/ +void CM_TraceThroughTree( traceWork_t *tw, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { + cNode_t *node; + cplane_t *plane; + float t1, t2, offset; + float frac, frac2; + float idist; + vec3_t mid; + int side; + float midf; + + if (tw->trace.fraction <= p1f) { + return; // already hit something nearer + } + + // if < 0, we are in a leaf node + if (num < 0) { + CM_TraceThroughLeaf( tw, &cm.leafs[-1-num] ); + return; + } + + // + // find the point distances to the seperating plane + // and the offset for the size of the box + // + node = cm.nodes + num; + plane = node->plane; + + // adjust the plane distance apropriately for mins/maxs + if ( plane->type < 3 ) { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + offset = tw->extents[plane->type]; + } else { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + if ( tw->isPoint ) { + offset = 0; + } else { + // this is silly + offset = 2048; + } + } + + // see which sides we need to consider + if ( t1 >= offset + 1 && t2 >= offset + 1 ) { + CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < -offset - 1 && t2 < -offset - 1 ) { + CM_TraceThroughTree( tw, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side + if ( t1 < t2 ) { + idist = 1.0/(t1-t2); + side = 1; + frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; + } else if (t1 > t2) { + idist = 1.0/(t1-t2); + side = 0; + frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; + frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + } else { + side = 0; + frac = 1; + frac2 = 0; + } + + // move up to the node + if ( frac < 0 ) { + frac = 0; + } + if ( frac > 1 ) { + frac = 1; + } + + midf = p1f + (p2f - p1f)*frac; + + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0 ) { + frac2 = 0; + } + if ( frac2 > 1 ) { + frac2 = 1; + } + + midf = p1f + (p2f - p1f)*frac2; + + mid[0] = p1[0] + frac2*(p2[0] - p1[0]); + mid[1] = p1[1] + frac2*(p2[1] - p1[1]); + mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, node->children[side^1], midf, p2f, mid, p2 ); +} + +//====================================================================== + + +/* +================== +CM_Trace +================== +*/ +void CM_Trace( trace_t *results, const vec3_t start, + const vec3_t end, vec3_t mins, vec3_t maxs, + clipHandle_t model, const vec3_t origin, int brushmask, + traceType_t type, sphere_t *sphere ) { + int i; + traceWork_t tw; + vec3_t offset; + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + + cm.checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + ::memset( &tw, 0, sizeof(tw) ); + tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise + VectorCopy(origin, tw.modelOrigin); + tw.type = type; + + if (!cm.numNodes) { + *results = tw.trace; + + return; // map not loaded, shouldn't happen + } + + // allow NULL to be passed in for 0,0,0 + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // set basic parms + tw.contents = brushmask; + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + tw.size[0][i] = mins[i] - offset[i]; + tw.size[1][i] = maxs[i] - offset[i]; + tw.start[i] = start[i] + offset[i]; + tw.end[i] = end[i] + offset[i]; + } + + // if a sphere is already specified + if ( sphere ) { + tw.sphere = *sphere; + } + else { + tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0]; + tw.sphere.halfheight = tw.size[1][2]; + VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius ); + } + + tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2]; + + // tw.offsets[signbits] = vector to apropriate corner from origin + tw.offsets[0][0] = tw.size[0][0]; + tw.offsets[0][1] = tw.size[0][1]; + tw.offsets[0][2] = tw.size[0][2]; + + tw.offsets[1][0] = tw.size[1][0]; + tw.offsets[1][1] = tw.size[0][1]; + tw.offsets[1][2] = tw.size[0][2]; + + tw.offsets[2][0] = tw.size[0][0]; + tw.offsets[2][1] = tw.size[1][1]; + tw.offsets[2][2] = tw.size[0][2]; + + tw.offsets[3][0] = tw.size[1][0]; + tw.offsets[3][1] = tw.size[1][1]; + tw.offsets[3][2] = tw.size[0][2]; + + tw.offsets[4][0] = tw.size[0][0]; + tw.offsets[4][1] = tw.size[0][1]; + tw.offsets[4][2] = tw.size[1][2]; + + tw.offsets[5][0] = tw.size[1][0]; + tw.offsets[5][1] = tw.size[0][1]; + tw.offsets[5][2] = tw.size[1][2]; + + tw.offsets[6][0] = tw.size[0][0]; + tw.offsets[6][1] = tw.size[1][1]; + tw.offsets[6][2] = tw.size[1][2]; + + tw.offsets[7][0] = tw.size[1][0]; + tw.offsets[7][1] = tw.size[1][1]; + tw.offsets[7][2] = tw.size[1][2]; + + // + // calculate bounds + // + if ( tw.type == TT_CAPSULE ) { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; + tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; + } else { + tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; + tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; + } + } + } + else { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i]; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i]; + } + } + } + + // + // check for position test special case + // + if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) { + if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX // FIXME - compile time flag? + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + tw.type = TT_AABB; + CM_TestInLeaf( &tw, &cmod->leaf ); + } + else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + CM_TestCapsuleInCapsule( &tw, model ); + } + else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) { + if ( tw.type == TT_CAPSULE ) { + CM_TestCapsuleInCapsule( &tw, model ); + } + else { + CM_TestBoundingBoxInCapsule( &tw, model ); + } + } + else { + CM_TestInLeaf( &tw, &cmod->leaf ); + } + } else { + CM_PositionTest( &tw ); + } + } else { + // + // check for point special case + // + if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) { + tw.isPoint = true; + VectorClear( tw.extents ); + } else { + tw.isPoint = false; + tw.extents[0] = tw.size[1][0]; + tw.extents[1] = tw.size[1][1]; + tw.extents[2] = tw.size[1][2]; + } + + // + // general sweeping through world + // + if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + tw.type = TT_AABB; + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } + else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } + else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) { + if ( tw.type == TT_CAPSULE ) { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } + else { + CM_TraceBoundingBoxThroughCapsule( &tw, model ); + } + } + else { + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } + } else { + CM_TraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end ); + } + } + + // generate endpos from the original, unmodified start/end + if ( tw.trace.fraction == 1 ) { + VectorCopy (end, tw.trace.endpos); + } else { + for ( i=0 ; i<3 ; i++ ) { + tw.trace.endpos[i] = start[i] + tw.trace.fraction * (end[i] - start[i]); + } + } + + // If allsolid is set (was entirely inside something solid), the plane is not valid. + // If fraction == 1.0, we never hit anything, and thus the plane is not valid. + // Otherwise, the normal on the plane should have unit length + assert(tw.trace.allsolid || + tw.trace.fraction == 1.0 || + VectorLengthSquared(tw.trace.plane.normal) > 0.9999); + *results = tw.trace; +} + +/* +================== +CM_BoxTrace +================== +*/ +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, traceType_t type ) { + CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, type, NULL ); +} + +/* +================== +CM_TransformedBoxTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, traceType_t type ) { + trace_t trace; + vec3_t start_l, end_l; + bool rotated; + vec3_t offset; + vec3_t symetricSize[2]; + vec3_t matrix[3], transpose[3]; + int i; + float halfwidth; + float halfheight; + float t; + sphere_t sphere; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + start_l[i] = start[i] + offset[i]; + end_l[i] = end[i] + offset[i]; + } + + // subtract origin offset + VectorSubtract( start_l, origin, start_l ); + VectorSubtract( end_l, origin, end_l ); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) { + rotated = true; + } else { + rotated = false; + } + + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + + sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + sphere.halfheight = halfheight; + t = halfheight - sphere.radius; + + if (rotated) { + // rotation on trace line (start-end) instead of rotating the bmodel + // NOTE: This is still incorrect for bounding boxes because the actual bounding + // box that is swept through the model is not rotated. We cannot rotate + // the bounding box or the bmodel because that would make all the brush + // bevels invalid. + // However this is correct for capsules since a capsule itself is rotated too. + CreateRotationMatrix(angles, matrix); + RotatePoint(start_l, matrix); + RotatePoint(end_l, matrix); + // rotated sphere offset for capsule + sphere.offset[0] = matrix[0][ 2 ] * t; + sphere.offset[1] = -matrix[1][ 2 ] * t; + sphere.offset[2] = matrix[2][ 2 ] * t; + } + else { + VectorSet( sphere.offset, 0, 0, t ); + } + + // sweep the box through the model + CM_Trace( &trace, start_l, end_l, symetricSize[0], symetricSize[1], + model, origin, brushmask, type, &sphere ); + + // if the bmodel was rotated and there was a collision + if ( rotated && trace.fraction != 1.0 ) { + // rotation of bmodel collision plane + TransposeMatrix(matrix, transpose); + RotatePoint(trace.plane.normal, transpose); + } + + // re-calculate the end position of the trace because the trace.endpos + // calculated by CM_Trace could be rotated and have an offset + trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); + trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); + trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + + *results = trace; +} + +/* +================== +CM_BiSphereTrace +================== +*/ +void CM_BiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask ) +{ + int i; + traceWork_t tw; + float largestRadius = startRad > endRad ? startRad : endRad; + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + + cm.checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + ::memset( &tw, 0, sizeof( tw ) ); + tw.trace.fraction = 1.0f; // assume it goes the entire distance until shown otherwise + VectorCopy( vec3_origin, tw.modelOrigin ); + tw.type = TT_BISPHERE; + tw.testLateralCollision = true; + tw.trace.lateralFraction = 1.0f; + + if( !cm.numNodes ) + { + *results = tw.trace; + + return; // map not loaded, shouldn't happen + } + + // set basic parms + tw.contents = mask; + + VectorCopy( start, tw.start ); + VectorCopy( end, tw.end ); + + tw.biSphere.startRadius = startRad; + tw.biSphere.endRadius = endRad; + + // + // calculate bounds + // + for( i = 0 ; i < 3 ; i++ ) + { + if( tw.start[ i ] < tw.end[ i ] ) + { + tw.bounds[ 0 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius; + tw.bounds[ 1 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius; + } + else + { + tw.bounds[ 0 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius; + tw.bounds[ 1 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius; + } + } + + tw.isPoint = false; + tw.extents[ 0 ] = largestRadius; + tw.extents[ 1 ] = largestRadius; + tw.extents[ 2 ] = largestRadius; + + // + // general sweeping through world + // + if( model ) + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + else + CM_TraceThroughTree( &tw, 0, 0.0f, 1.0f, tw.start, tw.end ); + + // generate endpos from the original, unmodified start/end + if( tw.trace.fraction == 1.0f ) + { + VectorCopy( end, tw.trace.endpos ); + } + else + { + for( i = 0; i < 3; i++ ) + tw.trace.endpos[ i ] = start[ i ] + tw.trace.fraction * ( end[ i ] - start[ i ] ); + } + + // If allsolid is set (was entirely inside something solid), the plane is not valid. + // If fraction == 1.0, we never hit anything, and thus the plane is not valid. + // Otherwise, the normal on the plane should have unit length + assert( tw.trace.allsolid || + tw.trace.fraction == 1.0 || + VectorLengthSquared(tw.trace.plane.normal ) > 0.9999 ); + + *results = tw.trace; +} + +/* +================== +CM_TransformedBiSphereTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask, + const vec3_t origin ) +{ + trace_t trace; + vec3_t start_l, end_l; + + // subtract origin offset + VectorSubtract( start, origin, start_l ); + VectorSubtract( end, origin, end_l ); + + CM_BiSphereTrace( &trace, start_l, end_l, startRad, endRad, model, mask ); + + // re-calculate the end position of the trace because the trace.endpos + // calculated by CM_BiSphereTrace could be rotated and have an offset + trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); + trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); + trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + + *results = trace; +} diff --git a/src/qcommon/cmd.cpp b/src/qcommon/cmd.cpp new file mode 100644 index 0000000..97b1226 --- /dev/null +++ b/src/qcommon/cmd.cpp @@ -0,0 +1,941 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// cmd.c -- Quake script command processing module + +#include "cmd.h" + +#include "cvar.h" +#include "files.h" +#include "q_shared.h" +#include "qcommon.h" + +#ifndef DEDICATED +#include "client/client.h" +#endif + +#define MAX_CMD_BUFFER 128*1024 +#define MAX_CMD_LINE 1024 + +typedef struct { + byte *data; + int maxsize; + int cursize; +} cmd_t; + +int cmd_wait; +cmd_t cmd_text; +byte cmd_text_buf[MAX_CMD_BUFFER]; + + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) { + if ( Cmd_Argc() == 2 ) { + cmd_wait = atoi( Cmd_Argv( 1 ) ); + if ( cmd_wait < 0 ) + cmd_wait = 1; // ignore the argument + } else { + cmd_wait = 1; + } +} + + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = MAX_CMD_BUFFER; + cmd_text.cursize = 0; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void Cbuf_AddText( const char *text ) { + int l; + + l = strlen (text); + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Com_Printf ("Cbuf_AddText: overflow\n"); + return; + } + ::memcpy(&cmd_text.data[cmd_text.cursize], text, l); + cmd_text.cursize += l; +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertFmtText( const char *fmt, ... ) { + int len; + int i; + + char text[MAXPRINTMSG]; + + va_list args; + va_start(args, fmt); + Q_vsnprintf(text, sizeof(text), fmt, args); + va_end(args); + + len = strlen( text ) + 1; + if ( len + cmd_text.cursize > cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + cmd_text.data[ i + len ] = cmd_text.data[ i ]; + } + + // copy the new text in + ::memcpy( cmd_text.data, text, len - 1 ); + + // add a \n + cmd_text.data[ len - 1 ] = '\n'; + + cmd_text.cursize += len; +} + +void Cbuf_InsertText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + if ( len + cmd_text.cursize > cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + cmd_text.data[ i + len ] = cmd_text.data[ i ]; + } + + // copy the new text in + ::memcpy( cmd_text.data, text, len - 1 ); + + // add a \n + cmd_text.data[ len - 1 ] = '\n'; + + cmd_text.cursize += len; +} + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText (int exec_when, const char *text) +{ + switch (exec_when) + { + case EXEC_NOW: + if (text && strlen(text) > 0) { + Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text); + Cmd_ExecuteString (text); + } else { + Cbuf_Execute(); + Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data); + } + break; + case EXEC_INSERT: + Cbuf_InsertText (text); + break; + case EXEC_APPEND: + Cbuf_AddText (text); + break; + default: + Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when"); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[MAX_CMD_LINE]; + int quotes; + + // This will keep // style comments all on one line by not breaking on + // a semicolon. It will keep /* ... */ style comments all on one line by not + // breaking it for semicolon or newline. + bool in_star_comment = false; + bool in_slash_comment = false; + while (cmd_text.cursize) + { + if ( cmd_wait > 0 ) { + // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait--; + break; + } + + // find a \n or ; line break or comment: // or /* */ + text = (char *)cmd_text.data; + + quotes = 0; + for (i=0 ; i< cmd_text.cursize ; i++) + { + if (text[i] == '"') + quotes++; + + if ( !(quotes&1)) { + if (i < cmd_text.cursize - 1) { + if (! in_star_comment && text[i] == '/' && text[i+1] == '/') + in_slash_comment = true; + else if (! in_slash_comment && text[i] == '/' && text[i+1] == '*') + in_star_comment = true; + else if (in_star_comment && text[i] == '*' && text[i+1] == '/') { + in_star_comment = false; + // If we are in a star comment, then the part after it is valid + // Note: This will cause it to NUL out the terminating '/' + // but ExecuteString doesn't require it anyway. + i++; + break; + } + } + if (! in_slash_comment && ! in_star_comment && text[i] == ';') + break; + } + if (! in_star_comment && (text[i] == '\n' || text[i] == '\r')) { + in_slash_comment = false; + break; + } + } + + if( i >= (MAX_CMD_LINE - 1)) { + i = MAX_CMD_LINE - 1; + } + + ::memcpy (line, text, i); + line[i] = 0; + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec) can insert data at the +// beginning of the text buffer + + if (i == cmd_text.cursize) + cmd_text.cursize = 0; + else + { + i++; + cmd_text.cursize -= i; + memmove (text, text+i, cmd_text.cursize); + } + +// execute the command line + + Cmd_ExecuteString (line); + } +} + + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f( void ) { + bool quiet; + union { + char *c; + void *v; + } f; + char filename[MAX_QPATH]; + + quiet = !Q_stricmp(Cmd_Argv(0), "execq"); + + if (Cmd_Argc () != 2) { + Com_Printf ("exec%s : execute a script file%s\n", + quiet ? "q" : "", quiet ? " without notification" : ""); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + FS_ReadFile( filename, &f.v); + if (!f.c) { + Com_Printf ("couldn't exec %s\n", filename); + return; + } + if (!quiet) + Com_Printf ("execing %s\n", filename); + + Cbuf_InsertText (f.c); + + FS_FreeFile (f.v); +} + + +/* +=============== +Cmd_Vstr_f + +Inserts the current value of a variable as command text +=============== +*/ +void Cmd_Vstr_f( void ) { + if (Cmd_Argc() != 2) { + Com_Printf ("vstr : execute a variable command\n"); + return; + } + + const char* v = Cvar_VariableString( Cmd_Argv( 1 ) ); + Cbuf_InsertFmtText( "%s\n", v ); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ + Com_Printf ("%s\n", Cmd_Args()); +} + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +struct cmd_function_t +{ + cmd_function_t *next; + char *name; + xcommand_t function; + completionFunc_t complete; +}; + + +typedef struct cmdContext_s +{ + int argc; + char *argv[ MAX_STRING_TOKENS ]; // points into cmd.tokenized + char tokenized[ BIG_INFO_STRING + MAX_STRING_TOKENS ]; // will have 0 bytes inserted + char cmd[ BIG_INFO_STRING ]; // the original command we received (no token processing) +} cmdContext_t; + +static cmdContext_t cmd; +static cmdContext_t savedCmd; +static cmd_function_t *cmd_functions; // possible commands to execute + +/* +============ +Cmd_SaveCmdContext +============ +*/ +void Cmd_SaveCmdContext( void ) +{ + ::memcpy( &savedCmd, &cmd, sizeof( cmdContext_t ) ); +} + +/* +============ +Cmd_RestoreCmdContext +============ +*/ +void Cmd_RestoreCmdContext( void ) +{ + ::memcpy( &cmd, &savedCmd, sizeof( cmdContext_t ) ); +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc( void ) { + return cmd.argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char* Cmd_Argv( int arg ) { + if ( arg >= cmd.argc ) { + return (char*)"\0"; + } + return cmd.argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + static char cmd_args[MAX_STRING_CHARS]; + int i; + + cmd_args[0] = 0; + for ( i = 1 ; i < cmd.argc ; i++ ) { + strcat( cmd_args, cmd.argv[i] ); + if ( i != cmd.argc-1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_Args + +Returns a single string containing argv(arg) to argv(argc()-1) +============ +*/ +char *Cmd_ArgsFrom( int arg ) { + static char cmd_args[BIG_INFO_STRING]; + int i; + + cmd_args[0] = 0; + if (arg < 0) + arg = 0; + for ( i = arg ; i < cmd.argc ; i++ ) { + strcat( cmd_args, cmd.argv[i] ); + if ( i != cmd.argc-1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_ArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Args(), bufferLength ); +} + +/* +============ +Cmd_LiteralArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_LiteralArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, cmd.cmd, bufferLength ); +} + +/* +============ +Cmd_Cmd + +Retrieve the unmodified command string +For rcon use when you want to transmit without altering quoting +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 +============ +*/ +char *Cmd_Cmd(void) +{ + return cmd.cmd; +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +// NOTE TTimo define that to track tokenization issues +//#define TKN_DBG +static void Cmd_TokenizeString2( const char *text_in, bool ignoreQuotes ) { + const char *text; + char *textOut; + +#ifdef TKN_DBG + // FIXME TTimo blunt hook to try to find the tokenization of userinfo + Com_DPrintf("Cmd_TokenizeString: %s\n", text_in); +#endif + + // clear previous args + cmd.argc = 0; + cmd.cmd[ 0 ] = '\0'; + + if ( !text_in ) { + return; + } + + Q_strncpyz( cmd.cmd, text_in, sizeof(cmd.cmd) ); + + text = text_in; + textOut = cmd.tokenized; + + while ( 1 ) { + if ( cmd.argc == MAX_STRING_TOKENS ) { + return; // this is usually something malicious + } + + while ( 1 ) { + // skip whitespace + while ( *text && *text <= ' ' ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + + // skip // comments + if ( text[0] == '/' && text[1] == '/' ) { + return; // all tokens parsed + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + while ( *text && ( text[0] != '*' || text[1] != '/' ) ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + text += 2; + } else { + break; // we are ready to parse a token + } + } + + // handle quoted strings + // NOTE TTimo this doesn't handle \" escaping + if ( !ignoreQuotes && *text == '"' ) { + cmd.argv[cmd.argc] = textOut; + cmd.argc++; + text++; + while ( *text && *text != '"' ) { + *textOut++ = *text++; + } + *textOut++ = 0; + if ( !*text ) { + return; // all tokens parsed + } + text++; + continue; + } + + // regular token + cmd.argv[cmd.argc] = textOut; + cmd.argc++; + + // skip until whitespace, quote, or command + while ( *text > ' ' ) { + if ( !ignoreQuotes && text[0] == '"' ) { + break; + } + + if ( text[0] == '/' && text[1] == '/' ) { + break; + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + break; + } + + *textOut++ = *text++; + } + + *textOut++ = 0; + + if ( !*text ) { + return; // all tokens parsed + } + } + +} + +/* +============ +Cmd_TokenizeString +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { + Cmd_TokenizeString2( text_in, false ); +} + +/* +============ +Cmd_TokenizeStringIgnoreQuotes +============ +*/ +void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) { + Cmd_TokenizeString2( text_in, true ); +} + +/* +============ +Cmd_FindCommand +============ +*/ +cmd_function_t *Cmd_FindCommand( const char *cmd_name ) +{ + cmd_function_t *cmd; + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + if( !Q_stricmp( cmd_name, cmd->name ) ) + return cmd; + return nullptr; +} + +/* +============ +Cmd_FindCommand +============ +*/ +bool Cmd_CommadExists( const char *cmd_name ) +{ + return Cmd_FindCommand( cmd_name ) ? true : false; +} + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { + cmd_function_t *cmd; + + // fail if the command already exists + if( Cmd_FindCommand( cmd_name ) ) + { + // allow completion-only commands to be silently doubled + if( function != nullptr ) + Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name ); + return; + } + + // use a small malloc to avoid zone fragmentation + cmd = new cmd_function_t; + cmd->name = CopyString( cmd_name ); + cmd->function = function; + cmd->complete = nullptr; + cmd->next = cmd_functions; + cmd_functions = cmd; +} + +/* +============ +Cmd_SetCommandCompletionFunc +============ +*/ +void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) { + cmd_function_t *cmd; + + for( cmd = cmd_functions; cmd; cmd = cmd->next ) { + if( !Q_stricmp( command, cmd->name ) ) { + cmd->complete = complete; + return; + } + } +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) +{ + cmd_function_t *cmd, **back; + + back = &cmd_functions; + for ( ;; ) + { + cmd = *back; + if ( !cmd ) { + // command wasn't active + return; + } + if ( !strcmp( cmd_name, cmd->name ) ) { + *back = cmd->next; + if (cmd->name) { + Z_Free(cmd->name); + } + delete cmd; + return; + } + back = &cmd->next; + } +} + +/* +============ +Cmd_RemoveCommandSafe + +Only remove commands with no associated function +============ +*/ +void Cmd_RemoveCommandSafe( const char *cmd_name ) +{ + cmd_function_t *cmd = Cmd_FindCommand( cmd_name ); + + if( !cmd ) + return; + + if( cmd->function ) + { + Com_Error( ERR_DROP, "Restricted source tried to remove system command \"%s\"", + cmd_name ); + return; + } + + Cmd_RemoveCommand( cmd_name ); +} + +/* +============ +Cmd_CommandCompletion +============ +*/ +void Cmd_CommandCompletion( void(*callback)(const char *s) ) { + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { + callback( cmd->name ); + } +} + +/* +============ +Cmd_CompleteArgument +============ +*/ +void Cmd_CompleteArgument( const char *command, char *args, int argNum ) +{ + cmd_function_t *cmd; + + // FIXIT-H: There needs to be a way to toggle this functionality at runtime + // rather than just crashing when a cgame doesn't provide support. #45 + // https://github.com/GrangerHub/tremulous/issues/45 +#if 0 +#ifndef DEDICATED + // Forward command argument completion to CGAME VM + if( cls.cgame && !VM_Call( cls.cgame, CG_CONSOLE_COMPLETARGUMENT, argNum ) ) +#endif +#endif + // Call local completion if VM doesn't pick up + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + if( !Q_stricmp( command, cmd->name ) && cmd->complete ) + cmd->complete( args, argNum ); +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( const char *text ) { + cmd_function_t *cmdFunc, **prev; + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + // check registered command functions + for ( prev = &cmd_functions ; *prev ; prev = &cmdFunc->next ) { + cmdFunc = *prev; + if ( !Q_stricmp( cmd.argv[0], cmdFunc->name ) ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + *prev = cmdFunc->next; + cmdFunc->next = cmd_functions; + cmd_functions = cmdFunc; + + // perform the action + if ( !cmdFunc->function ) { + // let the cgame or game handle it + break; + } else { + cmdFunc->function (); + } + return; + } + } + + // check cvars + if ( Cvar_Command() ) { + return; + } + + // check client game commands + if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) { + return; + } + + // check server game commands + if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { + return; + } + + // check ui commands + if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { + return; + } + + // send it as a server command if we are connected + CL_ForwardCommandToServer ( text ); +} + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t* cmd; + int i; + const char* match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = nullptr; + } + + i = 0; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { + if (match && !Com_Filter(match, cmd->name, false)) continue; + + Com_Printf ("%s\n", cmd->name); + i++; + } + Com_Printf ("%i commands\n", i); +} + +/* +================== +Cmd_CompleteCfgName +================== +*/ +void Cmd_CompleteCfgName( char *args, int argNum ) { + if( argNum == 2 ) { + Field_CompleteFilename( "", "cfg", false, true ); + } +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) { + Cmd_AddCommand ("cmdlist",Cmd_List_f); + Cmd_AddCommand ("exec",Cmd_Exec_f); + Cmd_AddCommand ("execq",Cmd_Exec_f); + Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName ); + Cmd_SetCommandCompletionFunc( "execq", Cmd_CompleteCfgName ); + Cmd_AddCommand ("vstr",Cmd_Vstr_f); + Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName ); + Cmd_AddCommand ("echo",Cmd_Echo_f); + Cmd_AddCommand ("wait", Cmd_Wait_f); +} diff --git a/src/qcommon/cmd.h b/src/qcommon/cmd.h new file mode 100644 index 0000000..390fa85 --- /dev/null +++ b/src/qcommon/cmd.h @@ -0,0 +1,115 @@ +/* + * This file is part of Tremulous. + * Copyright © 2017 Victor Roemer (blowfish) + * Copyright (C) 2015-2019 GrangerHub + * + * 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 3 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, see . + */ + +#ifndef CMD_H +#define CMD_H + +/* +============================================================== + +CMD + +Command text buffering and command execution + +============================================================== +*/ + +/* + +Any number of commands can be added in a frame, from several different sources. +Most commands come from either keybindings or console line input, but entire text +files can be execed. + +*/ + +void Cbuf_Init(void); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText(const char *text); +// Adds command text at the end of the buffer, does NOT add a final \n + +void Cbuf_ExecuteText(int exec_when, const char *text); +// this can be used in place of either Cbuf_AddText or Cbuf_InsertText + +void Cbuf_Execute(void); +// Pulls off \n terminated lines of text from the command buffer and sends +// them through Cmd_ExecuteString. Stops when the buffer is empty. +// Normally called once per frame, but may be explicitly invoked. +// Do not call inside a command function, or current args will be destroyed. + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +*/ + +using xcommand_t = void(*)(); + +void Cmd_Init(void); + +bool Cmd_CommadExists( const char *cmd_name ); + +void Cmd_AddCommand(const char *cmd_name, xcommand_t function); +// called by the init functions of other parts of the program to +// register commands and functions to call for them. +// The cmd_name is referenced later, so it should not be in temp memory +// if function is NULL, the command will be forwarded to the server +// as a clc_clientCommand instead of executed locally + +void Cmd_RemoveCommand(const char *cmd_name); + +typedef void (*completionFunc_t)(char *args, int argNum); + +// don't allow VMs to remove system commands +void Cmd_RemoveCommandSafe(const char *cmd_name); + +void Cmd_CommandCompletion(void (*callback)(const char *s)); +// callback with each valid string +void Cmd_SetCommandCompletionFunc(const char *command, completionFunc_t complete); +void Cmd_CompleteArgument(const char *command, char *args, int argNum); +void Cmd_CompleteCfgName(char *args, int argNum); + +int Cmd_Argc(void); +char *Cmd_Argv(int arg); +void Cmd_ArgvBuffer(int arg, char *buffer, int bufferLength); +char *Cmd_Args(void); +char *Cmd_ArgsFrom(int arg); +void Cmd_ArgsBuffer(char *buffer, int bufferLength); +void Cmd_LiteralArgsBuffer(char *buffer, int bufferLength); +char *Cmd_Cmd(void); +// The functions that execute commands get their parameters with these +// functions. Cmd_Argv () will return an empty string, not a NULL +// if arg > argc, so string operations are allways safe. + +void Cmd_TokenizeString(const char *text); +void Cmd_TokenizeStringIgnoreQuotes(const char *text_in); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +void Cmd_ExecuteString(const char *text); +// Parses a single line of text into arguments and tries to execute it +// as if it was typed at the console + +void Cmd_SaveCmdContext(void); +void Cmd_RestoreCmdContext(void); + +#endif diff --git a/src/qcommon/common.cpp b/src/qcommon/common.cpp new file mode 100644 index 0000000..051f475 --- /dev/null +++ b/src/qcommon/common.cpp @@ -0,0 +1,3662 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// common.c -- misc functions used in client and server + +#include "qcommon.h" + +#include +#ifdef _WIN32 +#include +#else +#include +//#include // umask +#endif + +#include "sys/sys_shared.h" + +#include "cmd.h" +#include "crypto.h" +#include "cvar.h" +#include "files.h" +#define JSON_IMPLEMENTATION +#include "json.h" +#include "msg.h" +#include "q_shared.h" +#include "vm.h" + +int demo_protocols[] = { PROTOCOL_VERSION, 70, 69, 0 }; + +#define MAX_NUM_ARGVS 50 + +#define MIN_DEDICATED_COMHUNKMEGS 16 +#define MIN_COMHUNKMEGS 256 +#define DEF_COMHUNKMEGS 256 +#define DEF_COMZONEMEGS 48 +#define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS) +#define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS) + +int com_argc; +char* com_argv[MAX_NUM_ARGVS+1]; + +jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame + + +FILE *debuglogfile; +static fileHandle_t pipefile; +static fileHandle_t logfile; +fileHandle_t com_journalFile; // events are written here +fileHandle_t com_journalDataFile; // config files are written here + +cvar_t *com_speeds; +cvar_t *com_developer; +cvar_t *com_dedicated; +cvar_t *com_timescale; +cvar_t *com_fixedtime; +cvar_t *com_journal; +cvar_t *com_maxfps; +cvar_t *com_altivec; +cvar_t *com_timedemo; +cvar_t *com_sv_running; +cvar_t *com_cl_running; +cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print +cvar_t *com_pipefile; +cvar_t *com_showtrace; +cvar_t *com_version; +cvar_t *com_buildScript; // for automated data building scripts +#ifdef CINEMATICS_INTRO +cvar_t *com_introPlayed; +#endif +cvar_t *cl_paused; +cvar_t *sv_paused; +cvar_t *cl_packetdelay; +cvar_t *sv_packetdelay; +cvar_t *com_cameraMode; +cvar_t *com_ansiColor; +cvar_t *com_unfocused; +cvar_t *com_maxfpsUnfocused; +cvar_t *com_minimized; +cvar_t *com_maxfpsMinimized; +cvar_t *com_standalone; +cvar_t *com_gamename; +cvar_t *com_protocol; +#ifdef LEGACY_PROTOCOL +cvar_t *com_legacyprotocol; +#endif +cvar_t *com_basegame; +cvar_t *com_homepath; +cvar_t *com_busyWait; + +#if id386 +void (QDECL *Q_SnapVector)(vec3_t vec); +#endif + +// com_speeds times +int time_game; +int time_frontend; // renderer frontend time +int time_backend; // renderer backend time + +int com_frameTime; +int com_frameNumber; + +bool com_errorEntered = false; +bool com_fullyInitialized = false; +bool com_gameRestarting = false; + +char com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); +void CIN_CloseAllVideos( void ); + +//============================================================================ + +static char* rd_buffer; +static unsigned int rd_buffersize; +static void (*rd_flush)( char *buffer ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) ) +{ + if (!buffer || !buffersize || !flush) + return; + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +void Com_EndRedirect(void) +{ + if ( rd_flush ) + rd_flush(rd_buffer); + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + static bool opening_qconsole = false; + + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( rd_buffer ) + { + if ((strlen(msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) + { + rd_flush(rd_buffer); + *rd_buffer = 0; + } + Q_strcat(rd_buffer, rd_buffersize, msg); + // TTimo nooo .. that would defeat the purpose + //rd_flush(rd_buffer); + //*rd_buffer = 0; + return; + } + +#ifndef DEDICATED + CL_ConsolePrint( msg ); +#endif + + Q_StripIndentMarker( msg ); + + // echo to dedicated console and early console + Sys_Print( msg ); + + // logfile + if ( com_logfile && com_logfile->integer ) { + // TTimo: only open the qconsole.log if the filesystem is in an initialized state + // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on) + if ( !logfile && FS_Initialized() && !opening_qconsole) { + struct tm *newtime; + time_t aclock; + + opening_qconsole = true; + + time( &aclock ); + newtime = localtime( &aclock ); + + logfile = FS_FOpenFileWrite( "qconsole.log" ); + + if(logfile) + { + Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); + + if ( com_logfile->integer > 1 ) + { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + else + { + Com_Printf("Opening qconsole.log failed!\n"); + Cvar_SetValue("logfile", 0); + } + + opening_qconsole = false; + } + if ( logfile && FS_Initialized()) { + FS_Write(msg, strlen(msg), logfile); + } + } +} + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if ( !com_developer || !com_developer->integer ) + return; + + va_start(argptr,fmt); + Q_vsnprintf(msg, sizeof(msg), fmt, argptr); + va_end(argptr); + + Com_Printf("%s", msg); +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the appropriate thing. +============= +*/ +void QDECL Com_Error( int code, const char *fmt, ... ) +{ + va_list argptr; + static int lastErrorTime; + static int errorCount; + int currentTime; + + if(com_errorEntered) + Sys_Error("recursive error after: %s", com_errorMessage); + + com_errorEntered = true; + + Cvar_Set("com_errorCode", va("%i", code)); + + // when we are running automated scripts, make sure we + // know if anything failed + if ( com_buildScript && com_buildScript->integer ) + code = ERR_FATAL; + + // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) + { + if ( ++errorCount > 3 ) + code = ERR_FATAL; + } + else + { + errorCount = 0; + } + lastErrorTime = currentTime; + + va_start(argptr,fmt); + Q_vsnprintf(com_errorMessage, sizeof(com_errorMessage),fmt,argptr); + va_end(argptr); + + if ( code != ERR_DISCONNECT ) + Cvar_Set("com_errorMessage", com_errorMessage); + + if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) + { + VM_Forced_Unload_Start(); + SV_Shutdown( "Server disconnected" ); + CL_Disconnect( true ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + // make sure we can get at our local stuff + FS_PureServerSetLoadedPaks("", ""); + com_errorEntered = false; + longjmp (abortframe, -1); + } + else if (code == ERR_DROP || code == ERR_RECONNECT) + { + Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage); + VM_Forced_Unload_Start(); + SV_Shutdown(va("Server crashed: %s", com_errorMessage)); + CL_Disconnect( true ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + FS_PureServerSetLoadedPaks("", ""); + com_errorEntered = false; + + static int reconnectCount = 0; + if ( code == ERR_RECONNECT && reconnectCount <= 0 ) + { + reconnectCount++; + Cbuf_AddText("reconnect\n"); + } + else + { + reconnectCount = 0; + } + + longjmp(abortframe, -1); + } + else + { + VM_Forced_Unload_Start(); + CL_Shutdown(va("Client fatal crashed: %s", com_errorMessage), true, true); + SV_Shutdown(va("Server fatal crashed: %s", com_errorMessage)); + VM_Forced_Unload_Done(); + } + + Com_Shutdown(); + + Sys_Error("%s", com_errorMessage); +} + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Engine_Exit(const char* p ) +{ + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) + { + // Some VMs might execute "quit" command directly, + // which would trigger an unload of active VM error. + // Sys_Quit will kill this process anyways, so + // a corrupt call stack makes no difference + VM_Forced_Unload_Start(); + SV_Shutdown(p[0] ? p : "Server quit"); + CL_Shutdown(p[0] ? p : "Client quit", true, true); + VM_Forced_Unload_Done(); + Com_Shutdown(); + FS_Shutdown(true); + } + Sys_Quit (); +} + +void Com_Quit_f( void ) +{ + char *p = Cmd_Args(); + Engine_Exit(p); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +tremulous +set test blah +map test +tremulous set test blah+map test +tremulous set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +char* com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) +{ + int inq = 0; + com_consoleLines[0] = commandLine; + com_numConsoleLines = 1; + + while ( *commandLine ) + { + if ( *commandLine == '"' ) + inq = !inq; + + // look for a + seperating character + // if commandLine came from a file, we might have real line seperators + if ( (commandLine[0] == '+' && !inq) || commandLine[0] == '\n' || commandLine[0] == '\r' ) + { + if ( com_numConsoleLines == MAX_CONSOLE_LINES ) + return; + + com_consoleLines[com_numConsoleLines] = commandLine + 1; + com_numConsoleLines++; + *commandLine = 0; + } + commandLine++; + } +} + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of autogen.cfg +=================== +*/ +bool Com_SafeMode( void ) +{ + for ( int i = 0 ; i < com_numConsoleLines ; i++ ) + { + Cmd_TokenizeString(com_consoleLines[i]); + if ( !Q_stricmp(Cmd_Argv(0), "safe") + || !Q_stricmp(Cmd_Argv(0), "cvar_restart") ) + { + com_consoleLines[i][0] = 0; + return true; + } + } + + return false; +} + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) +{ + for (int i = 0 ; i < com_numConsoleLines ; i++) + { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( strcmp( Cmd_Argv(0), "set" ) ) { + continue; + } + + const char* s = Cmd_Argv(1); + if(!match || !strcmp(s, match)) + { + if(Cvar_Flags(s) == CVAR_NONEXISTENT) + Cvar_Get(s, Cmd_ArgsFrom(2), CVAR_USER_CREATED); + else + Cvar_Set2(s, Cmd_ArgsFrom(2), false); + } + } +} + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns true if any late commands were added +================= +*/ +bool Com_AddStartupCommands( void ) +{ + bool added = false; + + // quote every token, so args with semicolons can work + for ( int i = 0 ; i < com_numConsoleLines ; i++) + { + if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) + continue; + + // set commands already added with Com_StartupVariable + if ( !Q_stricmpn(com_consoleLines[i], "set ", 4) ) + continue; + + added = true; + Cbuf_AddText( com_consoleLines[i] ); + Cbuf_AddText( "\n" ); + } + + return added; +} + +//============================================================================ + +void Info_Print( const char *s ) +{ + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + char* o; + int l; + + if (*s == '\\') + s++; + + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; + if (l < 20) + { + ::memset(o, ' ', 20-l); + key[20] = 0; + } + else + *o = 0; + Com_Printf("%s ", key); + + if (!*s) + { + Com_Printf("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + + Com_Printf("%s\n", value); + } +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains(char *str1, char *str2, int casesensitive) +{ + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) { + for (j = 0; str2[j]; j++) { + if (casesensitive) { + if (str1[j] != str2[j]) { + break; + } + } + else { + if (toupper(str1[j]) != toupper(str2[j])) { + break; + } + } + } + if (!str2[j]) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter(const char* filter, char *name, int casesensitive) +{ + char buf[MAX_TOKEN_CHARS]; + char *ptr; + int i, found; + + while(*filter) { + if (*filter == '*') { + filter++; + for (i = 0; *filter; i++) { + if (*filter == '*' || *filter == '?') break; + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if (strlen(buf)) { + ptr = Com_StringContains(name, buf, casesensitive); + if (!ptr) return false; + name = ptr + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[' && *(filter+1) == '[') { + filter++; + } + else if (*filter == '[') { + filter++; + found = false; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') break; + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) found = true; + } + else { + if (toupper(*name) >= toupper(*filter) && + toupper(*name) <= toupper(*(filter+2))) found = true; + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) found = true; + } + else { + if (toupper(*filter) == toupper(*name)) found = true; + } + filter++; + } + } + if (!found) return false; + while(*filter) { + if (*filter == ']' && *(filter+1) != ']') break; + filter++; + } + filter++; + name++; + } + else { + if (casesensitive) { + if (*filter != *name) return false; + } + else { + if (toupper(*filter) != toupper(*name)) return false; + } + filter++; + name++; + } + } + return true; +} + +/* +============ +Com_FilterPath +============ +*/ +int Com_FilterPath(const char *filter, char *name, int casesensitive) +{ + int i; + char new_filter[MAX_QPATH]; + char new_name[MAX_QPATH]; + + for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) { + if ( filter[i] == '\\' || filter[i] == ':' ) { + new_filter[i] = '/'; + } + else { + new_filter[i] = filter[i]; + } + } + new_filter[i] = '\0'; + for (i = 0; i < MAX_QPATH-1 && name[i]; i++) { + if ( name[i] == '\\' || name[i] == ':' ) { + new_name[i] = '/'; + } + else { + new_name[i] = name[i]; + } + } + new_name[i] = '\0'; + return Com_Filter(new_filter, new_name, casesensitive); +} + +/* +================ +Com_RealTime +================ +*/ +int Com_RealTime(qtime_t *qtime) +{ + time_t t; + struct tm *tms; + + t = time(NULL); + if (!qtime) + return t; + tms = localtime(&t); + if (tms) { + qtime->tm_sec = tms->tm_sec; + qtime->tm_min = tms->tm_min; + qtime->tm_hour = tms->tm_hour; + qtime->tm_mday = tms->tm_mday; + qtime->tm_mon = tms->tm_mon; + qtime->tm_year = tms->tm_year; + qtime->tm_wday = tms->tm_wday; + qtime->tm_yday = tms->tm_yday; + qtime->tm_isdst = tms->tm_isdst; + } + return t; +} + +/* +============================================================================== + +ZONE MEMORY ALLOCATION + +There is never any space between memblocks, and there will never be two +contiguous free memblocks. + +The rover can be left pointing at a non-empty block + +The zone calls are pretty much only used for small strings and structures, +all big things are allocated on the hunk. +============================================================================== +*/ + +#define ZONEID 0x1d4a11 +#define MINFRAGMENT 64 + +typedef struct zonedebug_s { + const char *label; + const char *file; + int line; + int allocSize; +} zonedebug_t; + +typedef struct memblock_s { + int size; // including the header and possibly tiny fragments + int tag; // a tag of 0 is a free block + struct memblock_s *next, *prev; + int id; // should be ZONEID +#ifdef ZONE_DEBUG + zonedebug_t d; +#endif +} memblock_t; + +typedef struct { + int size; // total bytes malloced, including header + int used; // total bytes used + memblock_t blocklist; // start / end cap for linked list + memblock_t *rover; +} memzone_t; + +// main zone for all "dynamic" memory allocation +memzone_t *mainzone; +// we also have a small zone for small allocations that would only +// fragment the main zone (think of cvar and cmd strings) +memzone_t *smallzone; + +void Z_CheckHeap( void ); + +/* +======================== +Z_ClearZone +======================== +*/ +void Z_ClearZone( memzone_t *zone, int size ) +{ + memblock_t *block; + + // set the entire zone to one free block + + zone->blocklist.next = zone->blocklist.prev = block = + (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + zone->blocklist.tag = 1; // in use block + zone->blocklist.id = 0; + zone->blocklist.size = 0; + zone->rover = block; + zone->size = size; + zone->used = 0; + + block->prev = block->next = &zone->blocklist; + block->tag = 0; // free block + block->id = ZONEID; + block->size = size - sizeof(memzone_t); +} + +/* +======================== +Z_AvailableZoneMemory +======================== +*/ +int Z_AvailableZoneMemory( memzone_t *zone ) +{ + return zone->size - zone->used; +} + +/* +======================== +Z_AvailableMemory +======================== +*/ +int Z_AvailableMemory( void ) +{ + return Z_AvailableZoneMemory( mainzone ); +} + +/* +======================== +Z_Free +======================== +*/ +void Z_Free( void *ptr ) +{ + memblock_t *block, *other; + memzone_t *zone; + + if (!ptr) { + Com_Printf(S_COLOR_YELLOW "Z_Free: NULL pointer" ); + return; + } + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + if (block->id != ZONEID) { + Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" ); + } + if (block->tag == 0) { + Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" ); + } + // if static memory + if (block->tag == TAG_STATIC) { + return; + } + + // check the memory trash tester + if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) { + Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" ); + } + + if (block->tag == TAG_SMALL) { + zone = smallzone; + } + else { + zone = mainzone; + } + + zone->used -= block->size; + // set the block to something that should cause problems + // if it is referenced... + ::memset( ptr, 0xaa, block->size - sizeof( *block ) ); + + block->tag = 0; // mark as free + + other = block->prev; + if (!other->tag) { + // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + if (block == zone->rover) { + zone->rover = other; + } + block = other; + } + + zone->rover = block; + + other = block->next; + if ( !other->tag ) { + // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + } +} + + +/* +================ +Z_FreeTags +================ +*/ +void Z_FreeTags( int tag ) +{ + memzone_t *zone; + + if ( tag == TAG_SMALL ) + { + zone = smallzone; + } + else + { + zone = mainzone; + } + // use the rover as our pointer, because + // Z_Free automatically adjusts it + zone->rover = zone->blocklist.next; + do { + if ( zone->rover->tag == tag ) { + Z_Free( (void *)(zone->rover + 1) ); + continue; + } + zone->rover = zone->rover->next; + } while ( zone->rover != &zone->blocklist ); +} + + +/* +================ +Z_TagMalloc +================ +*/ +#ifdef ZONE_DEBUG +void *Z_TagMallocDebug( int size, int tag, const char *label, const char *file, int line ) +#else +void *Z_TagMalloc( int size, int tag ) +#endif +{ + int extra; + memblock_t *start, *rover, *_new, *base; + memzone_t *zone; + + if (!tag) + Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" ); + + if ( tag == TAG_SMALL ) + zone = smallzone; + else + zone = mainzone; + +#ifdef ZONE_DEBUG + int allocSize = size; +#endif + // + // scan through the block list looking for the first free block + // of sufficient size + // + size += sizeof(memblock_t); // account for size of block header + size += 4; // space for memory trash tester + size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary + + base = rover = zone->rover; + start = base->prev; + + do { + if (rover == start) + { + // scaned all the way around the list +#ifdef ZONE_DEBUG + Z_LogHeap(); + + Com_Error(ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone: %s, line: %d (%s)", + size, zone == smallzone ? "small" : "main", file, line, label); +#else + Com_Error(ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone", + size, zone == smallzone ? "small" : "main"); +#endif + return NULL; + } + if (rover->tag) { + base = rover = rover->next; + } else { + rover = rover->next; + } + } while (base->tag || base->size < size); + + // + // found a block big enough + // + extra = base->size - size; + if (extra > MINFRAGMENT) { + // there will be a free fragment after the allocated block + _new = (memblock_t *) ((byte *)base + size ); + _new->size = extra; + _new->tag = 0; // free block + _new->prev = base; + _new->id = ZONEID; + _new->next = base->next; + _new->next->prev = _new; + base->next = _new; + base->size = size; + } + + base->tag = tag; // n_o longer a free block + + zone->rover = base->next; // next allocation will start looking here + zone->used += base->size; + + base->id = ZONEID; + +#ifdef ZONE_DEBUG + base->d.label = label; + base->d.file = file; + base->d.line = line; + base->d.allocSize = allocSize; +#endif + + // marker for memory trash testing + *(int *)((byte *)base + base->size - 4) = ZONEID; + + return (void *) ((byte *)base + sizeof(memblock_t)); +} + +/* +======================== +Z_Malloc +======================== +*/ +#ifdef ZONE_DEBUG +void *Z_MallocDebug( int size, const char *label, const char *file, int line ) +#else +void *Z_Malloc( int size ) +#endif +{ + void *buf; + + //Z_CheckHeap(); // XXX DEBUG + +#ifdef ZONE_DEBUG + buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line ); +#else + buf = Z_TagMalloc( size, TAG_GENERAL ); +#endif + ::memset( buf, 0, size ); + + return buf; +} + +#ifdef ZONE_DEBUG +void *S_MallocDebug( int size, const char *label, const char *file, int line ) +{ + return Z_TagMallocDebug( size, TAG_SMALL, label, file, line ); +} +#else +void *S_Malloc( int size ) +{ + return Z_TagMalloc( size, TAG_SMALL ); +} +#endif + +/* +======================== +Z_CheckHeap +======================== +*/ +void Z_CheckHeap( void ) +{ + memblock_t *block; + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->next == &mainzone->blocklist) + break; // all blocks have been hit + + if ( (byte *)block + block->size != (byte *)block->next) + Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block" ); + + if ( block->next->prev != block) + Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link" ); + + if ( !block->tag && !block->next->tag ) + Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks" ); + } +} + +/* +======================== +Z_LogZoneHeap +======================== +*/ +void Z_LogZoneHeap( memzone_t *zone, const char *name ) +{ +#ifdef ZONE_DEBUG + char dump[32], *ptr; + int i, j; +#endif + memblock_t *block; + char buf[4096]; + int size, allocSize, numBlocks; + + if (!logfile || !FS_Initialized()) + return; + + size = numBlocks = 0; +#ifdef ZONE_DEBUG + allocSize = 0; +#endif + Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name); + FS_Write(buf, strlen(buf), logfile); + + for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) + { + if (block->tag) + { +#ifdef ZONE_DEBUG + ptr = ((char *) block) + sizeof(memblock_t); + j = 0; + for (i = 0; i < 20 && i < block->d.allocSize; i++) + { + if (ptr[i] >= 32 && ptr[i] < 127) { + dump[j++] = ptr[i]; + } + else { + dump[j++] = '_'; + } + } + dump[j] = '\0'; + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump); + FS_Write(buf, strlen(buf), logfile); + allocSize += block->d.allocSize; +#endif + size += block->size; + numBlocks++; + } + } +#ifdef ZONE_DEBUG + // subtract debug memory + size -= numBlocks * sizeof(zonedebug_t); +#else + allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment +#endif + Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks); + FS_Write(buf, strlen(buf), logfile); + Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name); + FS_Write(buf, strlen(buf), logfile); +} + +/* +======================== +Z_LogHeap +======================== +*/ +void Z_LogHeap( void ) +{ + Z_LogZoneHeap( mainzone, "MAIN" ); + Z_LogZoneHeap( smallzone, "SMALL" ); +} + +// static mem blocks to reduce a lot of small zone overhead +typedef struct memstatic_s { + memblock_t b; + byte mem[2]; +} memstatic_t; + +memstatic_t emptystring = { + {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} +}; + +memstatic_t numberstring[] = { + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} } +}; + +/* +======================== +CopyString + +NOTE: never write over the memory CopyString returns because +memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) +{ + char *out; + + if (!in[0]) { + return ((char *)&emptystring) + sizeof(memblock_t); + } + else if (!in[1]) { + if (in[0] >= '0' && in[0] <= '9') { + return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t); + } + } + out = (char*)S_Malloc(strlen(in)+1); + strcpy (out, in); + return out; +} + +/* +============================================================================== + +Goals: + reproducable without history effects -- no out of memory errors on weird map to map changes + allow restarting of the client without fragmentation + minimize total pages in use at run time + minimize total pages needed during load time + + Single block of memory with stack allocators coming from both ends towards the middle. + + One side is designated the temporary memory allocator. + + Temporary memory can be allocated and freed in any order. + + A highwater mark is kept of the most in use at any time. + + When there is no temporary memory allocated, the permanent and temp sides + can be switched, allowing the already touched temp memory to be used for + permanent storage. + + Temp memory must never be allocated on two ends at once, or fragmentation + could occur. + + If we have any in-use temp memory, additional temp allocations must come from + that side. + + If not, we can choose to make either side the new temp side and push future + permanent allocations to the other side. Permanent allocations should be + kept on the side that has the current greatest wasted highwater mark. + +============================================================================== +*/ + + +#define HUNK_MAGIC 0x89537892 +#define HUNK_FREE_MAGIC 0x89537893 + +typedef struct { + unsigned int magic; + unsigned int size; +} hunkHeader_t; + +typedef struct { + int mark; + int permanent; + int temp; + int tempHighwater; +} hunkUsed_t; + +typedef struct hunkblock_s { + int size; + byte printed; + struct hunkblock_s *next; + const char *label; + const char *file; + int line; +} hunkblock_t; + +static hunkblock_t *hunkblocks; + +static hunkUsed_t hunk_low, hunk_high; +static hunkUsed_t *hunk_permanent, *hunk_temp; + +static byte* s_hunkData = NULL; +static int s_hunkTotal; + +static int s_zoneTotal; +static int s_smallZoneTotal; + +/* +================= +Com_Meminfo_f +================= +*/ +void Com_Meminfo_f( void ) +{ + memblock_t *block; + int zoneBytes, zoneBlocks; + int smallZoneBytes; + int botlibBytes, rendererBytes; + int unused; + + zoneBytes = 0; + botlibBytes = 0; + rendererBytes = 0; + zoneBlocks = 0; + for (block = mainzone->blocklist.next ; ; block = block->next) { + if ( Cmd_Argc() != 1 ) { + Com_Printf ("block:%p size:%7i tag:%3i\n", + (void *)block, block->size, block->tag); + } + if ( block->tag ) { + zoneBytes += block->size; + zoneBlocks++; + if ( block->tag == TAG_BOTLIB ) { + botlibBytes += block->size; + } else if ( block->tag == TAG_RENDERER ) { + rendererBytes += block->size; + } + } + + if (block->next == &mainzone->blocklist) { + break; // all blocks have been hit + } + if ( (byte *)block + block->size != (byte *)block->next) { + Com_Printf ("ERROR: block size does not touch the next block\n"); + } + if ( block->next->prev != block) { + Com_Printf ("ERROR: next block doesn't have proper back link\n"); + } + if ( !block->tag && !block->next->tag ) { + Com_Printf ("ERROR: two consecutive free blocks\n"); + } + } + + smallZoneBytes = 0; + for (block = smallzone->blocklist.next ; ; block = block->next) + { + if ( block->tag ) + smallZoneBytes += block->size; + + if (block->next == &smallzone->blocklist) + break; // all blocks have been hit + } + + Com_Printf( "%8i bytes total hunk\n", s_hunkTotal ); + Com_Printf( "%8i bytes total zone\n", s_zoneTotal ); + Com_Printf( "\n" ); + Com_Printf( "%8i low mark\n", hunk_low.mark ); + Com_Printf( "%8i low permanent\n", hunk_low.permanent ); + if ( hunk_low.temp != hunk_low.permanent ) { + Com_Printf( "%8i low temp\n", hunk_low.temp ); + } + Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i high mark\n", hunk_high.mark ); + Com_Printf( "%8i high permanent\n", hunk_high.permanent ); + if ( hunk_high.temp != hunk_high.permanent ) { + Com_Printf( "%8i high temp\n", hunk_high.temp ); + } + Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent ); + unused = 0; + if ( hunk_low.tempHighwater > hunk_low.permanent ) { + unused += hunk_low.tempHighwater - hunk_low.permanent; + } + if ( hunk_high.tempHighwater > hunk_high.permanent ) { + unused += hunk_high.tempHighwater - hunk_high.permanent; + } + Com_Printf( "%8i unused highwater\n", unused ); + Com_Printf( "\n" ); + Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks ); + Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes ); + Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes ); + Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) ); + Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes ); +} + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) +{ + int start, end; + int i, j; + int sum; + memblock_t *block; + + Z_CheckHeap(); + + start = Sys_Milliseconds(); + + sum = 0; + + j = hunk_low.permanent >> 2; + for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)s_hunkData)[i]; + } + + i = ( s_hunkTotal - hunk_high.permanent ) >> 2; + j = hunk_high.permanent >> 2; + for ( ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)s_hunkData)[i]; + } + + for (block = mainzone->blocklist.next ; ; block = block->next) { + if ( block->tag ) { + j = block->size >> 2; + for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)block)[i]; + } + } + if ( block->next == &mainzone->blocklist ) { + break; // all blocks have been hit + } + } + + end = Sys_Milliseconds(); + + Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); +} + + + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitSmallZoneMemory( void ) +{ + s_smallZoneTotal = (512 * 1024); + smallzone = (memzone_t*)calloc(s_smallZoneTotal, 1); + if ( !smallzone ) + Com_Error(ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024)); + + Z_ClearZone( smallzone, s_smallZoneTotal ); +} + +void Com_InitZoneMemory( void ) +{ + // Please note: com_zoneMegs can only be set on the command line, and not + // in q3config.cfg or Com_StartupVariable, as they haven't been executed by + // this point. It's a chicken and egg problem. We need the memory manager + // configured to handle those places where you would configure the memory + // manager. + + // allocate the random block zone + cvar_t* cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + + if ( cv->integer < DEF_COMZONEMEGS ) { + s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS; + } else { + s_zoneTotal = cv->integer * 1024 * 1024; + } + + mainzone = (memzone_t*)calloc( s_zoneTotal, 1 ); + if ( !mainzone ) { + Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) ); + } + Z_ClearZone( mainzone, s_zoneTotal ); +} + +/* +================= +Hunk_Log +================= +*/ +void Hunk_Log( void) +{ + char buf[4096]; + + if (!logfile || !FS_Initialized()) + return; + + int size = 0; + int numBlocks = 0; + + Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n"); + FS_Write(buf, strlen(buf), logfile); + + for ( hunkblock_t* block = hunkblocks; block; block = block->next ) + { +#ifdef HUNK_DEBUG + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label); + FS_Write(buf, strlen(buf), logfile); +#endif + size += block->size; + numBlocks++; + } + + Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); + FS_Write(buf, strlen(buf), logfile); + + Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); + FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Hunk_SmallLog +================= +*/ +void Hunk_SmallLog(void) +{ + char buf[4096]; + + if (!logfile || !FS_Initialized()) + return; + + for ( hunkblock_t* block = hunkblocks ; block; block = block->next ) + block->printed = false; + + int size = 0; + int numBlocks = 0; + + Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n"); + FS_Write(buf, strlen(buf), logfile); + + for ( hunkblock_t* block = hunkblocks; block; block = block->next ) + { + if (block->printed) + continue; + + int locsize = block->size; + for ( hunkblock_t* block2 = block->next; block2; block2 = block2->next ) + { + if (block->line != block2->line) + continue; + + if (Q_stricmp(block->file, block2->file)) + continue; + + size += block2->size; + locsize += block2->size; + block2->printed = true; + } +#ifdef HUNK_DEBUG + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label); + FS_Write(buf, strlen(buf), logfile); +#endif + size += block->size; + numBlocks++; + } + Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); + FS_Write(buf, strlen(buf), logfile); + + Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); + FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Com_InitHunkZoneMemory +================= +*/ +void Com_InitHunkMemory( void ) +{ + cvar_t *cv; + int nMinAlloc; + const char *pMsg = NULL; + + // make sure the file system has allocated and "not" freed any temp blocks + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if (FS_LoadStack() != 0) + Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero"); + + // allocate the stack based hunk allocator + cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + Cvar_SetDescription(cv, "The size of the hunk memory segment"); + + // if we are not dedicated min allocation is 56, otherwise min is 1 + if (com_dedicated && com_dedicated->integer) + { + nMinAlloc = MIN_DEDICATED_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n"; + } + else + { + nMinAlloc = MIN_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n"; + } + + if ( cv->integer < nMinAlloc ) + { + s_hunkTotal = 1024 * 1024 * nMinAlloc; + Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024)); + } + else + { + s_hunkTotal = cv->integer * 1024 * 1024; + } + + s_hunkData = (byte*)calloc( s_hunkTotal + 31, 1 ); + if ( !s_hunkData ) + { + Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) ); + } + // cacheline align + s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 ); + Hunk_Clear(); + + Cmd_AddCommand( "meminfo", Com_Meminfo_f ); +#ifdef ZONE_DEBUG + Cmd_AddCommand( "zonelog", Z_LogHeap ); +#endif +#ifdef HUNK_DEBUG + Cmd_AddCommand( "hunklog", Hunk_Log ); + Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog ); +#endif +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int Hunk_MemoryRemaining( void ) +{ + int low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp; + int high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp; + return s_hunkTotal - ( low + high ); +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) +{ + hunk_low.mark = hunk_low.permanent; + hunk_high.mark = hunk_high.permanent; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) +{ + hunk_low.permanent = hunk_low.temp = hunk_low.mark; + hunk_high.permanent = hunk_high.temp = hunk_high.mark; +} + +/* +================= +Hunk_CheckMark +================= +*/ +bool Hunk_CheckMark( void ) +{ + if( hunk_low.mark || hunk_high.mark ) + return true; + return false; +} + +void CL_ShutdownCGame( void ); +void CL_ShutdownUI( void ); +void SV_ShutdownGameProgs( void ); + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void Hunk_Clear( void ) +{ +#ifndef DEDICATED + CL_ShutdownCGame(); + CL_ShutdownUI(); +#endif + SV_ShutdownGameProgs(); +#ifndef DEDICATED + CIN_CloseAllVideos(); +#endif + hunk_low.mark = 0; + hunk_low.permanent = 0; + hunk_low.temp = 0; + hunk_low.tempHighwater = 0; + + hunk_high.mark = 0; + hunk_high.permanent = 0; + hunk_high.temp = 0; + hunk_high.tempHighwater = 0; + + hunk_permanent = &hunk_low; + hunk_temp = &hunk_high; + + Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); + VM_Clear(); +#ifdef HUNK_DEBUG + hunkblocks = NULL; +#endif +} + +static void Hunk_SwapBanks( void ) +{ + hunkUsed_t *swap; + + // can't swap banks if there is any temp already allocated + if ( hunk_temp->temp != hunk_temp->permanent ) + return; + + // if we have a larger highwater mark on this side, start making + // our permanent allocations here and use the other side for temp + if ( hunk_temp->tempHighwater - hunk_temp->permanent + > hunk_permanent->tempHighwater - hunk_permanent->permanent ) + { + swap = hunk_temp; + hunk_temp = hunk_permanent; + hunk_permanent = swap; + } +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +#ifdef HUNK_DEBUG +void *Hunk_AllocDebug( int size, ha_pref preference, const char *label, const char *file, int line ) +#else +void *Hunk_Alloc( int size, ha_pref preference ) +#endif +{ + void* buf; + + if ( s_hunkData == NULL) + { + Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" ); + } + + // can't do preference if there is any temp allocated + if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) { + Hunk_SwapBanks(); + } else { + if (preference == h_low && hunk_permanent != &hunk_low) { + Hunk_SwapBanks(); + } else if (preference == h_high && hunk_permanent != &hunk_high) { + Hunk_SwapBanks(); + } + } + +#ifdef HUNK_DEBUG + size += sizeof(hunkblock_t); +#endif + + // round to cacheline + size = (size+31)&~31; + + if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) { +#ifdef HUNK_DEBUG + Hunk_Log(); + Hunk_SmallLog(); + + Com_Error(ERR_DROP, "Hunk_Alloc failed on %i: %s, line: %d (%s)", size, file, line, label); +#else + Com_Error(ERR_DROP, "Hunk_Alloc failed on %i", size); +#endif + } + + if ( hunk_permanent == &hunk_low ) { + buf = (void *)(s_hunkData + hunk_permanent->permanent); + hunk_permanent->permanent += size; + } else { + hunk_permanent->permanent += size; + buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent ); + } + + hunk_permanent->temp = hunk_permanent->permanent; + + ::memset( buf, 0, size ); + +#ifdef HUNK_DEBUG + { + hunkblock_t *block; + + block = (hunkblock_t *) buf; + block->size = size - sizeof(hunkblock_t); + block->file = file; + block->label = label; + block->line = line; + block->next = hunkblocks; + hunkblocks = block; + buf = ((byte *) buf) + sizeof(hunkblock_t); + } +#endif + return buf; +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory( int size ) +{ + void* buf; + hunkHeader_t* hdr; + + // return a Z_Malloc'd block if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( s_hunkData == NULL ) + return Z_Malloc(size); + + Hunk_SwapBanks(); + + size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t ); + + if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) + Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size ); + + if ( hunk_temp == &hunk_low ) + { + buf = (void *)(s_hunkData + hunk_temp->temp); + hunk_temp->temp += size; + } + else + { + hunk_temp->temp += size; + buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ); + } + + if ( hunk_temp->temp > hunk_temp->tempHighwater ) + hunk_temp->tempHighwater = hunk_temp->temp; + + hdr = (hunkHeader_t *)buf; + buf = (void *)(hdr+1); + + hdr->magic = HUNK_MAGIC; + hdr->size = size; + + // don't bother clearing, because we are going to load a file over it + return buf; +} + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory( void *buf ) +{ + // free with Z_Free if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be + // loaded by the file system without redunant routines in the file system + // utilizing different memory systems + if ( s_hunkData == NULL ) + { + Z_Free(buf); + return; + } + + hunkHeader_t* hdr = ( (hunkHeader_t *)buf ) - 1; + if ( hdr->magic != HUNK_MAGIC ) + Com_Error(ERR_FATAL, "Hunk_FreeTempMemory: bad magic"); + + hdr->magic = HUNK_FREE_MAGIC; + + // this only works if the files are freed in stack order, + // otherwise the memory will stay around until Hunk_ClearTempMemory + if ( hunk_temp == &hunk_low ) + { + if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) + hunk_temp->temp -= hdr->size; + else + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } + else + { + if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) + hunk_temp->temp -= hdr->size; + else + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } +} + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed. If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory( void ) +{ + if ( s_hunkData != NULL ) + hunk_temp->temp = hunk_temp->permanent; +} + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +#define MAX_PUSHED_EVENTS 1024 +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +Com_InitJournaling +================= +*/ +void Com_InitJournaling( void ) +{ + Com_StartupVariable( "journal" ); + com_journal = Cvar_Get ("journal", "0", CVAR_INIT); + if ( !com_journal->integer ) { + return; + } + + if ( com_journal->integer == 1 ) { + Com_Printf( "Journaling events\n"); + com_journalFile = FS_FOpenFileWrite( "journal.dat" ); + com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); + } else if ( com_journal->integer == 2 ) { + Com_Printf( "Replaying journaled events\n"); + FS_FOpenFileRead( "journal.dat", &com_journalFile, true ); + FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, true ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + Cvar_Set( "com_journal", "0" ); + com_journalFile = 0; + com_journalDataFile = 0; + Com_Printf( "Couldn't open journal files\n" ); + } +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUEUED_EVENTS 256 +#define MASK_QUEUED_EVENTS ( MAX_QUEUED_EVENTS - 1 ) + +static sysEvent_t eventQueue[ MAX_QUEUED_EVENTS ]; +static int eventHead = 0; +static int eventTail = 0; + +/* +================ +Com_QueueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) +{ + sysEvent_t *ev; + + // combine mouse movement with previous mouse event + if ( type == SE_MOUSE && eventHead != eventTail ) + { + ev = &eventQueue[ ( eventHead + MAX_QUEUED_EVENTS - 1 ) & MASK_QUEUED_EVENTS ]; + + if ( ev->evType == SE_MOUSE ) + { + ev->evValue += value; + ev->evValue2 += value2; + return; + } + } + + ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ]; + + if ( eventHead - eventTail >= MAX_QUEUED_EVENTS ) + { + Com_Printf("Com_QueueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) + { + Z_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + if ( time == 0 ) + { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +================ +Com_GetSystemEvent + +================ +*/ +sysEvent_t Com_GetSystemEvent( void ) +{ + sysEvent_t ev; + char *s; + + // return if we have data + if ( eventHead > eventTail ) + { + eventTail++; + return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; + } + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) + { + char *b; + int len; + + len = strlen( s ) + 1; + b = (char*)Z_Malloc( len ); + strcpy( b, s ); + Com_QueueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // return if we have data + if ( eventHead > eventTail ) + { + eventTail++; + return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; + } + + // create an empty event to return + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = Sys_Milliseconds(); + + return ev; +} + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t Com_GetRealEvent( void ) +{ + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal->integer == 2 ) + { + int r = FS_Read( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) + Com_Error( ERR_FATAL, "Error reading from journal file" ); + + if ( ev.evPtrLength ) + { + ev.evPtr = Z_Malloc( ev.evPtrLength ); + r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + } + } + else + { + ev = Com_GetSystemEvent(); + + // write the journal value out if needed + if ( com_journal->integer == 1 ) + { + int r = FS_Write( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) + Com_Error( ERR_FATAL, "Error writing to journal file" ); + + if ( ev.evPtrLength ) + { + r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + } + } + + return ev; +} + + +/* +================= +Com_InitPushEvent +================= +*/ +void Com_InitPushEvent( void ) { + // clear the static buffer array + // this requires SE_NONE to be accepted as a valid but NOP event + memset( com_pushedEvents, 0, sizeof(com_pushedEvents) ); + // reset counters while we are at it + // beware: GetEvent might still return an SE_NONE from the buffer + com_pushedEventsHead = 0; + com_pushedEventsTail = 0; +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) +{ + static int printedWarning = 0; + sysEvent_t *ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) + { + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) + { + printedWarning = true; + Com_Printf("WARNING: Com_PushEvent overflow\n"); + } + + if ( ev->evPtr ) + Z_Free( ev->evPtr ); + + com_pushedEventsTail++; + } + else + { + printedWarning = false; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t Com_GetEvent(void) +{ + if ( com_pushedEventsHead > com_pushedEventsTail ) + { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } + return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) +{ + int t1 = 0; + if ( com_speeds->integer ) + t1 = Sys_Milliseconds(); + + SV_PacketEvent(*evFrom, buf); + + if ( com_speeds->integer ) + { + int t2 = Sys_Milliseconds(); + int msec = t2 - t1; + if ( com_speeds->integer == 3 ) + Com_Printf("SV_PacketEvent time: %i\n", msec); + } +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop(void) +{ + sysEvent_t ev; + netadr_t evFrom; + byte bufData[MAX_MSGLEN]; + msg_t buf; + + MSG_Init(&buf, bufData, sizeof(bufData)); + + for (;;) + { + ev = Com_GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) + { + // manually send packet events for the loopback channel + while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) + CL_PacketEvent( evFrom, &buf ); + + // if the server just shut down, flush the events + while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) + if ( com_sv_running->integer ) + Com_RunAndTimeServerPacket( &evFrom, &buf ); + + return ev.evTime; + } + + switch(ev.evType) + { + case SE_KEY: + CL_KeyEvent( ev.evValue, (bool)ev.evValue2, ev.evTime ); + break; + case SE_CHAR: + CL_CharEvent( ev.evValue ); + break; + case SE_MOUSE: + CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_JOYSTICK_AXIS: + CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CONSOLE: + Cbuf_AddText( (char *)ev.evPtr ); + Cbuf_AddText( "\n" ); + break; + default: + Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); + break; + } + + // free any block data + if ( ev.evPtr ) + Z_Free( ev.evPtr ); + } + + return 0; // never reached +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds(void) +{ + sysEvent_t ev; + // get events and push them until we get a null event with the current time + do { + ev = Com_GetRealEvent(); + + if ( ev.evType != SE_NONE ) + Com_PushEvent( &ev ); + + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void __attribute__((__noreturn__)) Com_Error_f (void) +{ + if ( Cmd_Argc() > 1 ) + Com_Error( ERR_DROP, "Testing drop error" ); + else + Com_Error( ERR_FATAL, "Testing fatal error" ); +} + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f (void) +{ + float s; + int start, now; + + if ( Cmd_Argc() != 2 ) + { + Com_Printf( "freeze \n" ); + return; + } + + s = atof(Cmd_Argv(1)); + start = Com_Milliseconds(); + + for (;;) + { + now = Com_Milliseconds(); + if ( (now - start) * 0.001 > s ) + break; + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) +{ + *( volatile int * )0 = 0x12345678; +} + +/* +================== +Com_ExecuteCfg + +For controlling environment variables +================== +*/ + +void Com_ExecuteCfg(void) +{ + Cbuf_ExecuteText(EXEC_NOW, "exec default.cfg\n"); + Cbuf_Execute(); // Always execute after exec to prevent text buffer overflowing + + if(!Com_SafeMode()) + { + // skip the q3config.cfg and autoexec.cfg if "safe" is on the command line + Cbuf_ExecuteText(EXEC_NOW, "exec " Q3CONFIG_CFG "\n"); + Cbuf_Execute(); + Cbuf_ExecuteText(EXEC_NOW, "exec autoexec.cfg\n"); + Cbuf_Execute(); + } +} + +/* +================== +Com_GameRestart + +Change to a new mod properly with cleaning up cvars before switching. +================== +*/ + +void Com_GameRestart(int checksumFeed, bool disconnect) +{ + // make sure no recursion can be triggered + if(!com_gameRestarting && com_fullyInitialized) + { + int clWasRunning; + + com_gameRestarting = true; + clWasRunning = com_cl_running->integer; + + // Kill server if we have one + if(com_sv_running->integer) + SV_Shutdown("Game directory changed"); + + if(clWasRunning) + { + if(disconnect) + CL_Disconnect(false); + + CL_Shutdown("Game directory changed", disconnect, false); + } + + FS_Restart(checksumFeed); + + // Clean out any user and VM created cvars + Cvar_Restart(true); + Com_ExecuteCfg(); + + if(disconnect) + { + // We don't want to change any network settings if gamedir + // change was triggered by a connect to server because the + // new network settings might make the connection fail. + NET_Restart_f(); + } + + if(clWasRunning) + { + CL_Init(); + CL_StartHunkUsers(false); + } + + com_gameRestarting = false; + } +} + +/* +================== +Com_GameRestart_f + +Expose possibility to change current running mod to the user +================== +*/ + +void Com_GameRestart_f(void) +{ + if(!FS_FilenameCompare(Cmd_Argv(1), BASEGAME)) + { + // This is the standard base game. Servers and clients should + // use "" and not the standard basegame name because this messes + // up pak file negotiation and lots of other stuff + + Cvar_Set("fs_game", ""); + } + else + Cvar_Set("fs_game", Cmd_Argv(1)); + + Com_GameRestart(0, true); +} + +static void Com_DetectAltivec(void) +{ + // Only detect if user hasn't forcibly disabled it. + if ( com_altivec->integer ) + { + static bool altivec = false; + static bool detected = false; + + if (!detected) + { + altivec = ( Sys_GetProcessorFeatures( ) & CF_ALTIVEC ) == CF_ALTIVEC; + detected = true; + } + + if (!altivec) + Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support! + } +} + +/* +================= +Com_DetectSSE +Find out whether we have SSE support +================= +*/ + +#if id386 || idx64 +static void Com_DetectSSE(void) +{ +#if !idx64 + cpuFeatures_t feat = Sys_GetProcessorFeatures(); + if(feat & CF_SSE) + { + if(feat & CF_SSE2) + Q_SnapVector = qsnapvectorsse; + else + Q_SnapVector = qsnapvectorx87; +#endif + Com_Printf("Have SSE support\n"); +#if !idx64 + } + else + { + Q_SnapVector = qsnapvectorx87; + + Com_Printf("No SSE support on this machine\n"); + } +#endif +} + +#else + +#define Com_DetectSSE() + +#endif + +/* +================= +Com_InitRand +Seed the random number generator, if possible with an OS supplied random seed. +================= +*/ +static void Com_InitRand(void) +{ + unsigned int seed; + + if(Sys_RandomBytes((byte *) &seed, sizeof(seed))) + srand(seed); + else + srand(time(NULL)); +} + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) +{ + int qport; + + if ( setjmp (abortframe) ) { + Sys_Error ("Error during initialization"); + } + + // Clear queues + ::memset( &eventQueue[ 0 ], 0, MAX_QUEUED_EVENTS * sizeof( sysEvent_t ) ); + + // initialize the weak pseudo-random number generator for use later. + Com_InitRand(); + + // do this before anything else decides to push events + Com_InitPushEvent(); + + Com_InitSmallZoneMemory(); + Cvar_Init(); + + // prepare enough of the subsystems to handle + // cvar and command buffer management + Com_ParseCommandLine( commandLine ); + + //Swap_Init (); + Cbuf_Init (); + + Com_DetectSSE(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + Com_InitZoneMemory(); + Cmd_Init (); + + // get the developer cvar set as early as possible + com_developer = Cvar_Get("developer", "0", CVAR_TEMP); + + // done early so bind command exists + CL_InitKeyCommands(); + + com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); + + FS_InitFilesystem (); + + Com_InitJournaling(); + + // Add some commands here already so users can use them from config files + if (com_developer && com_developer->integer) + { + Cmd_AddCommand ("error", Com_Error_f); + Cmd_AddCommand ("crash", Com_Crash_f); + Cmd_AddCommand ("freeze", Com_Freeze_f); + } + Cmd_AddCommand ("quit", Com_Quit_f); + Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f ); + Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); + Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteCfgName ); + Cmd_AddCommand("game_restart", Com_GameRestart_f); + + Com_ExecuteCfg(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // get dedicated here for proper hunk megs initialization +#ifdef DEDICATED + com_dedicated = Cvar_Get ("dedicated", "1", CVAR_INIT); + Cvar_CheckRange( com_dedicated, 1, 2, true ); +#else + com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH); + Cvar_CheckRange( com_dedicated, 0, 2, true ); +#endif + // allocate the stack based hunk allocator + Com_InitHunkMemory(); + + // if any archived cvars are modified after this, we will trigger a writing + // of the config file + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + // + // init commands and vars + // + com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE); + com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE); + + com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP ); + + com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO ); + com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT); + com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT); + com_speeds = Cvar_Get ("com_speeds", "0", 0); + com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT); + com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT); + + cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM); + sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM); + cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT); + sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT); + com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM); + com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM); + com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + com_ansiColor = Cvar_Get( "com_ansiColor", "0", CVAR_ARCHIVE ); + + com_unfocused = Cvar_Get( "com_unfocused", "0", CVAR_ROM ); + com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "0", CVAR_ARCHIVE ); + com_minimized = Cvar_Get( "com_minimized", "0", CVAR_ROM ); + com_maxfpsMinimized = Cvar_Get( "com_maxfpsMinimized", "0", CVAR_ARCHIVE ); + com_busyWait = Cvar_Get("com_busyWait", "0", CVAR_ARCHIVE); + Cvar_Get("com_errorMessage", "", CVAR_ROM | CVAR_NORESTART); + Cvar_Get("com_demoErrorMessage", "", CVAR_ROM | CVAR_NORESTART); + + com_version = Cvar_Get ("version", PRODUCT_NAME, CVAR_ROM | CVAR_SERVERINFO ); + Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + com_gamename = Cvar_Get("com_gamename", GAMENAME_FOR_MASTER, CVAR_SERVERINFO | CVAR_INIT); + + Sys_Init(); + + // Pick a random port value + Com_RandomBytes( (byte*)&qport, sizeof(int) ); + Netchan_Init( qport & 0xffff ); + + VM_Init(); + Crypto_Init(); + SV_Init(); + + com_dedicated->modified = false; +#ifndef DEDICATED + CL_Init(); +#endif + + // set com_frameTime so that if a map is started on the + // command line it will still be able to count on com_frameTime + // being random enough for a serverid + com_frameTime = Com_Milliseconds(); + + // add + commands from command line + if ( !Com_AddStartupCommands() ) { +#ifdef CINEMATICS_LOGO + // if the user didn't give any commands, run default action + if ( !com_dedicated->integer ) { + Cbuf_AddText ("cinematic splash.RoQ\n"); + } +#endif + } + + // start in full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + CL_StartHunkUsers( false ); + + com_fullyInitialized = true; + + // always set the cvar, but only print the info if it makes sense. + Com_DetectAltivec(); +#if idppc + Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled"); +#endif + + com_pipefile = Cvar_Get( "com_pipefile", "", CVAR_ARCHIVE|CVAR_LATCH ); + if( com_pipefile->string[0] ) + { + pipefile = FS_FCreateOpenPipeFile( com_pipefile->string ); + } + + Com_Printf ("--- Common Initialization Complete ---\n"); +} + +/* +=============== +Com_ReadFromPipe + +Read whatever is in com_pipefile, if anything, and execute it +=============== +*/ +void Com_ReadFromPipe( void ) +{ + static char buf[MAX_STRING_CHARS]; + static int accu = 0; + int read; + + if( !pipefile ) + return; + + while( ( read = FS_Read( buf + accu, sizeof( buf ) - accu - 1, pipefile ) ) > 0 ) + { + char *brk = NULL; + + for( int i = accu; i < accu + read; ++i ) + { + if( buf[ i ] == '\0' ) + buf[ i ] = '\n'; + if( buf[ i ] == '\n' || buf[ i ] == '\r' ) + brk = &buf[ i + 1 ]; + } + buf[ accu + read ] = '\0'; + + accu += read; + + if( brk ) + { + char tmp = *brk; + *brk = '\0'; + Cbuf_ExecuteText( EXEC_APPEND, buf ); + *brk = tmp; + + accu -= brk - buf; + memmove( buf, brk, accu + 1 ); + } + else if( accu >= sizeof( buf ) - 1 ) // full + { + Cbuf_ExecuteText( EXEC_APPEND, buf ); + accu = 0; + } + } +} + + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) +{ + fileHandle_t f; + + f = FS_FOpenFileWrite( filename ); + if ( !f ) + { + Com_Printf("Couldn't write %s.\n", filename ); + return; + } + + FS_Printf(f, "// generated by tremulous, do not modify\n"); + + Key_WriteBindings(f); + Cvar_WriteVariables(f); + FS_FCloseFile(f); +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) +{ + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) + return; + + if ( !(cvar_modifiedFlags & CVAR_ARCHIVE) ) + return; + + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + Com_WriteConfigToFile(Q3CONFIG_CFG); +} + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) +{ + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: writeconfig \n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + + if ( !COM_CompareExtension(filename, ".cfg") ) + { + Com_Printf("Com_WriteConfig_f: Only the \".cfg\" extension is supported by this command!\n"); + return; + } + + Com_Printf( "Writing %s.\n", filename ); + Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ +int Com_ModifyMsec( int msec ) +{ + int clampTime; + + // + // modify time for debugging values + // + if ( com_fixedtime->integer ) + msec = com_fixedtime->integer; + else if ( com_timescale->value ) + msec *= com_timescale->value; + else if (com_cameraMode->integer) + msec *= com_timescale->value; + + // don't let it scale below 1 msec + if ( msec < 1 && com_timescale->value) + msec = 1; + + if ( com_dedicated->integer ) + { + // dedicated servers don't want to clamp for a much longer + // period, because it would mess up all the client's views + // of time. + if (com_sv_running->integer && msec > 500) + Com_Printf("Hitch warning: %i msec frame time\n", msec); + + clampTime = 5000; + } + else + { + if ( !com_sv_running->integer ) + { + // clients of remote servers do not want to clamp time, because + // it would skew their view of the server's time temporarily + clampTime = 5000; + } + else + { + // for local single player gaming + // we may want to clamp the time to prevent players from + // flying off edges when something hitches. + clampTime = 200; + } + } + + if ( msec > clampTime ) + msec = clampTime; + + return msec; +} + +/* +================= +Com_TimeVal +================= +*/ + +int Com_TimeVal(int minMsec) +{ + int timeVal = Sys_Milliseconds() - com_frameTime; + + if(timeVal >= minMsec) + timeVal = 0; + else + timeVal = minMsec - timeVal; + + return timeVal; +} + +/* +================= +Com_Frame +================= +*/ +void Com_Frame( void ) +{ + + int msec, minMsec; + int timeVal, timeValSV; + static int lastTime = 0, bias = 0; + + int timeBeforeFirstEvents; + int timeBeforeServer; + int timeBeforeEvents; + int timeBeforeClient; + int timeAfter; + + if ( setjmp(abortframe) ) + return; // an ERR_DROP was thrown + + timeBeforeFirstEvents =0; + timeBeforeServer =0; + timeBeforeEvents =0; + timeBeforeClient = 0; + timeAfter = 0; + + // write config file if anything changed + Com_WriteConfiguration(); + + // + // main event loop + // + if ( com_speeds->integer ) + timeBeforeFirstEvents = Sys_Milliseconds(); + + // Figure out how much time we have + if ( !com_timedemo->integer ) + { + if(com_dedicated->integer) + { + minMsec = SV_FrameMsec(); + } + else + { + if(com_minimized->integer && com_maxfpsMinimized->integer > 0) + minMsec = 1000 / com_maxfpsMinimized->integer; + else if(com_unfocused->integer && com_maxfpsUnfocused->integer > 0) + minMsec = 1000 / com_maxfpsUnfocused->integer; + else if(com_maxfps->integer > 0) + minMsec = 1000 / com_maxfps->integer; + else + minMsec = 1; + + timeVal = com_frameTime - lastTime; + bias += timeVal - minMsec; + + if(bias > minMsec) + bias = minMsec; + + // Adjust minMsec if previous frame took too long to render so + // that framerate is stable at the requested value. + minMsec -= bias; + } + } + else + { + minMsec = 1; + } + + do { + if ( com_sv_running->integer ) + { + timeValSV = SV_SendQueuedPackets(); + timeVal = Com_TimeVal(minMsec); + + if ( timeValSV < timeVal ) + timeVal = timeValSV; + } + else + { + timeVal = Com_TimeVal(minMsec); + } + + if ( com_busyWait->integer || timeVal < 1 ) + NET_Sleep(0); + else + NET_Sleep(timeVal - 1); + } while( Com_TimeVal(minMsec) ); + + IN_Frame(); + + lastTime = com_frameTime; + com_frameTime = Com_EventLoop(); + + msec = com_frameTime - lastTime; + + Cbuf_Execute(); + + if ( com_altivec->modified ) + { + Com_DetectAltivec(); + com_altivec->modified = false; + } + + // mess with msec if needed + msec = Com_ModifyMsec(msec); + + // + // server side + // + if ( com_speeds->integer ) + timeBeforeServer = Sys_Milliseconds(); + + SV_Frame(msec); + + // if "dedicated" has been modified, start up + // or shut down the client system. + // Do this after the server may have started, + // but before the client tries to auto-connect + if ( com_dedicated->modified ) + { + // get the latched value + Cvar_Get("dedicated", "0", 0); + com_dedicated->modified = false; + + if ( !com_dedicated->integer ) + { + SV_Shutdown("dedicated set to 0"); + CL_FlushMemory(); + } + } + +#ifndef DEDICATED + // + // client system + // + // + // run event loop a second time to get server to client packets + // without a frame of latency + // + if ( com_speeds->integer ) + timeBeforeEvents = Sys_Milliseconds(); + + Com_EventLoop(); + Cbuf_Execute(); + + // + // client side + // + if ( com_speeds->integer ) + timeBeforeClient = Sys_Milliseconds(); + + CL_Frame(msec); + + if ( com_speeds->integer ) + timeAfter = Sys_Milliseconds(); +#else + if ( com_speeds->integer ) + { + timeAfter = Sys_Milliseconds(); + timeBeforeEvents = timeAfter; + timeBeforeClient = timeAfter; + } +#endif + + NET_FlushPacketQueue(); + + // + // report timing information + // + if ( com_speeds->integer ) + { + int all = timeAfter - timeBeforeServer; + int sv = timeBeforeEvents - timeBeforeServer; + int ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; + int cl = timeAfter - timeBeforeClient; + + sv -= time_game; + cl -= time_frontend + time_backend; + + Com_Printf("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", + com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); + } + + // + // trace optimization tracking + // + if ( com_showtrace->integer ) + { + extern int c_traces, c_brush_traces, c_patch_traces; + extern int c_pointcontents; + + Com_Printf("%4i traces (%ib %ip) %4i points\n", + c_traces, c_brush_traces, c_patch_traces, c_pointcontents); + + c_traces = 0; + c_brush_traces = 0; + c_patch_traces = 0; + c_pointcontents = 0; + } + + Com_ReadFromPipe(); + + com_frameNumber++; +} + +/* +================= +Com_Shutdown +================= +*/ +void Com_Shutdown(void) +{ + if (logfile) + { + FS_FCloseFile (logfile); + logfile = 0; + } + + if ( com_journalFile ) + { + FS_FCloseFile( com_journalFile ); + com_journalFile = 0; + } + + if( pipefile ) + { + FS_FCloseFile( pipefile ); + FS_HomeRemove( com_pipefile->string ); + } +} + +/* +=========================================== +command line completion +=========================================== +*/ + +/* +================== +Field_Clear +================== +*/ +void Field_Clear( field_t *edit ) +{ + memset(edit->buffer, 0, MAX_EDIT_LINE); + edit->cursor = 0; + edit->scroll = 0; +} + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int matchCount; +// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance) +static field_t *completionField; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) +{ + if ( Q_stricmpn(s, completionString, strlen(completionString)) ) + return; + + matchCount++; + if ( matchCount == 1 ) + { + Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); + return; + } + + // cut shortestMatch to the amount common with s + for ( int i = 0 ; shortestMatch[i] ; i++ ) + { + if ( i >= strlen(s) ) + { + shortestMatch[i] = 0; + break; + } + + if ( tolower(shortestMatch[i]) != tolower(s[i]) ) + shortestMatch[i] = 0; + } +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) +{ + if ( !Q_stricmpn(s, shortestMatch, strlen(shortestMatch)) ) + Com_Printf(" %s\n", s); +} + +/* +=============== +PrintCvarMatches + +=============== +*/ +static void PrintCvarMatches( const char *s ) +{ + char value[ TRUNCATE_LENGTH ]; + if ( !Q_stricmpn(s, shortestMatch, strlen( shortestMatch)) ) + { + Com_TruncateLongString( value, Cvar_VariableString( s ) ); + Com_Printf( " %s = \"%s\"\n", s, value ); + } +} + +/* +=============== +Field_FindFirstSeparator +=============== +*/ +static char *Field_FindFirstSeparator( char *s ) +{ + for( int i = 0; i < strlen( s ); i++ ) + if( s[ i ] == ';' ) + return &s[ i ]; + + return NULL; +} + +/* +=============== +Field_Complete +=============== +*/ +static bool Field_Complete( void ) +{ + if( matchCount == 0 ) + return true; + + int completionOffset = strlen( completionField->buffer ) - strlen( completionString ); + + Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch, + sizeof( completionField->buffer ) - completionOffset ); + + completionField->cursor = strlen( completionField->buffer ); + + if( matchCount == 1 ) + { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + completionField->cursor++; + return true; + } + + Com_Printf( "]%s\n", completionField->buffer ); + + return false; +} + +#ifndef DEDICATED +/* +=============== +Field_CompleteKeyname +=============== +*/ +void Field_CompleteKeyname( void ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + Key_KeynameCompletion( FindMatches ); + + if( !Field_Complete( ) ) + Key_KeynameCompletion( PrintMatches ); +} +#endif + +/* +=============== +Field_CompleteFilename +=============== +*/ +void Field_CompleteFilename( const char *dir, const char *ext, + bool stripExt, bool allowNonPureFilesOnDisk ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + FS_FilenameCompletion( dir, ext, stripExt, FindMatches, allowNonPureFilesOnDisk ); + + if( !Field_Complete( ) ) + FS_FilenameCompletion( dir, ext, stripExt, PrintMatches, allowNonPureFilesOnDisk ); +} + +/* +============ +Field_ListCompletion +============ +*/ +void Field_ListCompletion( char *listJson, void(*callback)(const char *s) ) +{ + char item[ 256 ]; + const char *arrayPtr; + const char *listEnd = listJson + strlen( listJson ); + + // JSON parse array + for ( arrayPtr = JSON_ArrayGetFirstValue( listJson, listEnd ); + arrayPtr ; + arrayPtr = JSON_ArrayGetNextValue( arrayPtr, listEnd ) ) + { + JSON_ValueGetString( arrayPtr, listEnd, item, 256 ); + callback( item ); + } +} + +/* +=============== +Field_CompleteList + +Completes an arbirary list of JSON encoded items passed from a VM +=============== +*/ +void Field_CompleteList( char *listJson ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + Field_ListCompletion( listJson, FindMatches ); + + if( !Field_Complete() ) + Field_ListCompletion( listJson, PrintMatches ); +} + +/* +=============== +Field_CompleteCommand +=============== +*/ +void Field_CompleteCommand( char *cmd, bool doCommands, bool doCvars ) +{ + // Skip leading whitespace and quotes + cmd = Com_SkipCharset( cmd, " \"" ); + + Cmd_TokenizeStringIgnoreQuotes( cmd ); + int completionArgument = Cmd_Argc( ); + + // If there is trailing whitespace on the cmd + if( *( cmd + strlen( cmd ) - 1 ) == ' ' ) + { + completionString = ""; + completionArgument++; + } + else + completionString = Cmd_Argv( completionArgument - 1 ); + + if ( completionString == nullptr ) + return; + +#ifndef DEDICATED + // Unconditionally add a '\' to the start of the buffer + if( completionField->buffer[ 0 ] && + completionField->buffer[ 0 ] != '\\' ) + { + if( completionField->buffer[ 0 ] != '/' ) + { + // Buffer is full, refuse to complete + if( strlen( completionField->buffer ) + 1 >= + sizeof( completionField->buffer ) ) + return; + + memmove( &completionField->buffer[ 1 ], + &completionField->buffer[ 0 ], + strlen( completionField->buffer ) + 1 ); + completionField->cursor++; + } + + completionField->buffer[ 0 ] = '\\'; + } +#endif + + if( completionArgument > 1 ) + { + const char *baseCmd = Cmd_Argv( 0 ); + char *p; + +#ifndef DEDICATED + // This should always be true + if( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' ) + baseCmd++; +#endif + + if( ( p = Field_FindFirstSeparator( cmd ) ) ) + Field_CompleteCommand( p + 1, true, true ); // Compound command + else + Cmd_CompleteArgument( baseCmd, cmd, completionArgument ); + } + else + { + if( completionString[0] == '\\' || completionString[0] == '/' ) + completionString++; + + matchCount = 0; + shortestMatch[ 0 ] = 0; + + if( strlen( completionString ) == 0 ) + return; + + if( doCommands ) + Cmd_CommandCompletion( FindMatches ); + + if( doCvars ) + Cvar_CommandCompletion( FindMatches ); + + if( !Field_Complete( ) ) + { + // run through again, printing matches + if( doCommands ) + Cmd_CommandCompletion( PrintMatches ); + + if( doCvars ) + Cvar_CommandCompletion( PrintCvarMatches ); + } + } +} + +/* +=============== +Field_AutoComplete + +Perform Tab expansion +=============== +*/ +void Field_AutoComplete( field_t *field ) +{ + completionField = field; + Field_CompleteCommand( completionField->buffer, true, true ); +} + +/* +================== +Com_RandomBytes + +fills string array with len random bytes, preferably from the OS randomizer +================== +*/ +void Com_RandomBytes( byte *string, int len ) +{ + if( Sys_RandomBytes( string, len ) ) + return; + + Com_Printf( "Com_RandomBytes: using weak randomization\n" ); + for( int i = 0; i < len; i++ ) + string[i] = (unsigned char)( rand() % 256 ); +} + + +/* +================== +Com_IsVoipTarget + +Returns non-zero if given clientNum is enabled in voipTargets, zero otherwise. +If clientNum is negative return if any bit is set. +================== +*/ +bool Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum) +{ + int i = 0; + + if ( clientNum < 0 ) + { + for ( i = 0; i < voipTargetsSize; i++ ) + { + if(voipTargets[i]) + return true; + } + + return false; + } + + i = clientNum >> 3; + + if( i < voipTargetsSize ) + return (bool)(voipTargets[i] & (1 << (clientNum & 0x07))); + + return false; +} + +/* +=============== +Field_CompletePlayerName +=============== +*/ +static bool Field_CompletePlayerNameFinal( bool whitespace ) +{ + if( matchCount == 0 ) + return true; + + int completionOffset = strlen( completionField->buffer ) - strlen( completionString ); + + Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch, + sizeof( completionField->buffer ) - completionOffset ); + + completionField->cursor = strlen( completionField->buffer ); + + if( matchCount == 1 && whitespace ) + { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + completionField->cursor++; + return true; + } + + return false; +} + +static void Name_PlayerNameCompletion( const char **names, int nameCount, void(*callback)(const char *s) ) +{ + for( int i = 0; i < nameCount; i++ ) + callback( names[ i ] ); +} + +bool Com_FieldStringToPlayerName( char *name, int length, const char *rawname ) +{ + char hex[5]; + + if( name == NULL || rawname == NULL ) + return false; + + if( length <= 0 ) + return true; + + int i; + for( i = 0; *rawname && i + 1 <= length; rawname++, i++ ) + { + if( *rawname == '\\' ) + { + Q_strncpyz( hex, rawname + 1, sizeof(hex) ); + int ch = Com_HexStrToInt( hex ); + if( ch > -1 ) + { + name[i] = ch; + rawname += 4; //hex string length, 0xXX + } + else + { + name[i] = *rawname; + } + } else { + name[i] = *rawname; + } + } + name[i] = '\0'; + + return true; +} + +bool Com_PlayerNameToFieldString( char *str, int length, const char *name ) +{ + const char *p; + int i; + int x1, x2; + + if( str == NULL || name == NULL ) + return false; + + if( length <= 0 ) + return true; + + *str = '\0'; + p = name; + + for( i = 0; *p != '\0'; i++, p++ ) + { + if( i + 1 >= length ) + break; + + if( *p <= ' ' ) + { + if( i + 5 + 1 >= length ) + break; + + x1 = *p >> 4; + x2 = *p & 15; + + str[i+0] = '\\'; + str[i+1] = '0'; + str[i+2] = 'x'; + str[i+3] = x1 > 9 ? x1 - 10 + 'a' : x1 + '0'; + str[i+4] = x2 > 9 ? x2 - 10 + 'a' : x2 + '0'; + + i += 4; + } else { + str[i] = *p; + } + } + str[i] = '\0'; + + return true; +} + +void Field_CompletePlayerName( const char **names, int nameCount ) +{ + + matchCount = 0; + shortestMatch[ 0 ] = 0; + + if( nameCount <= 0 ) + return; + + Name_PlayerNameCompletion( names, nameCount, FindMatches ); + + if( completionString[0] == '\0' ) + Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ 0 ] ); + + //allow to tab player names + //if full player name switch to next player name + if( completionString[0] != '\0' + && Q_stricmp( shortestMatch, completionString ) == 0 + && nameCount > 1 ) + { + for( int i = 0; i < nameCount; i++ ) + { + if( Q_stricmp( names[ i ], completionString ) == 0 ) + { + i++; + + if( i >= nameCount ) + i = 0; + + Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ i ] ); + break; + } + } + } + + if( matchCount > 1 ) + { + Com_Printf( "]%s\n", completionField->buffer ); + + Name_PlayerNameCompletion( names, nameCount, PrintMatches ); + } + + bool whitespace = nameCount == 1 ? true : false; + Field_CompletePlayerNameFinal(whitespace); +} + +int QDECL Com_strCompare( const void *a, const void *b ) +{ + const char **pa = (const char **)a; + const char **pb = (const char **)b; + return strcmp( *pa, *pb ); +} diff --git a/src/qcommon/crypto.cpp b/src/qcommon/crypto.cpp new file mode 100644 index 0000000..dd71371 --- /dev/null +++ b/src/qcommon/crypto.cpp @@ -0,0 +1,92 @@ +/* +=========================================================================== +Copyright (C) 2007-2008 Amanieu d'Antras (amanieu@gmail.com) +Copyright (C) 2015-2016 Jeff Kent (jeff@jkent.net) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "crypto.h" + +#include "sys/sys_shared.h" + +#include "cvar.h" +#include "q_shared.h" +#include "qcommon.h" + +#define TO_REAL_PTR(x) ((uint8_t*)x - sizeof(size_t)) +#define TO_MOCK_PTR(x) ((uint8_t*)x + sizeof(size_t)) +#define REAL_PTR_SIZE(x) (*((size_t *)x)) +#define MOCK_PTR_SIZE(x) (REAL_PTR_SIZE(TO_REAL_PTR(x))) + + +static void *crypto_alloc( size_t size ) +{ + void *p; + + assert( size > 0 ); + + p = malloc( sizeof(size_t) + size ); + if ( !p ) + Com_Error( ERR_FATAL, "crypto_alloc: Virtual memory exhausted." ); + + REAL_PTR_SIZE( p ) = size; + return TO_MOCK_PTR( p ); +} + +static void *crypto_realloc( void *old, size_t old_size, size_t new_size ) +{ + void *p; + + old_size = MOCK_PTR_SIZE( old ); + if ( new_size == old_size ) { + return old; + } + + p = malloc( sizeof(size_t) + new_size ); + if ( !p ) + Com_Error( ERR_FATAL, "crypto_realloc: Virtual memory exhausted." ); + REAL_PTR_SIZE( p ) = new_size; + + p = TO_MOCK_PTR( p ); + memcpy( p, old, MIN( old_size, new_size ) ); + old = TO_REAL_PTR( old ); + memset( old, 0, sizeof(size_t) + old_size ); + free( old ); + + return p; +} + +static void crypto_free( void *p, size_t size ) +{ + p = TO_REAL_PTR( p ); + size = REAL_PTR_SIZE( p ); + memset( p, 0, sizeof(size_t) + size ); + free( p ); +} + +void Crypto_Init( void ) +{ + mp_set_memory_functions( crypto_alloc, crypto_realloc, crypto_free ); +} + +void qnettle_random( void *ctx, size_t length, uint8_t *dst ) +{ + Sys_CryptoRandomBytes( dst, length ); +} diff --git a/src/qcommon/crypto.h b/src/qcommon/crypto.h new file mode 100644 index 0000000..9b21943 --- /dev/null +++ b/src/qcommon/crypto.h @@ -0,0 +1,45 @@ +/* +=========================================================================== +Copyright (C) 2007-2008 Amanieu d'Antras (amanieu@gmail.com) +Copyright (C) 2015-2016 Jeff Kent (jeff@jkent.net) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __CRYPTO_H__ +#define __CRYPTO_H__ + +#include "nettle/bignum.h" +#include "nettle/buffer.h" +#include "nettle/rsa.h" +#include "nettle/sha2.h" + + +#define RSA_PRIVATE_KEY_FILE "rsa_private_key" +#define RSA_PUBLIC_KEY_FILE "rsa_public_key" + +#define RSA_PUBLIC_EXPONENT 65537 + +#define RSA_KEY_LENGTH 4096 +#define RSA_STRING_LENGTH (RSA_KEY_LENGTH / 4 + 1) + +void Crypto_Init( void ); +void qnettle_random( void *ctx, size_t length, uint8_t *dst ); + +#endif /* __CRYPTO_H__ */ diff --git a/src/qcommon/cvar.cpp b/src/qcommon/cvar.cpp new file mode 100644 index 0000000..560138a --- /dev/null +++ b/src/qcommon/cvar.cpp @@ -0,0 +1,1498 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// cvar.c -- dynamic variable tracking + +#include "cvar.h" + +#include "cmd.h" +#include "files.h" +#include "q_shared.h" +#include "qcommon.h" + +static cvar_t *cvar_vars = nullptr; +cvar_t *cvar_cheats; +int cvar_modifiedFlags = 0; + +#define MAX_CVARS 2048 +static cvar_t cvar_indexes[MAX_CVARS]; +static int cvar_numIndexes; + +#define FILE_HASH_SIZE 256 +static cvar_t *hashTable[FILE_HASH_SIZE]; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue(const char *fname) +{ + long hash = 0; + int i = 0; + while (fname[i] != '\0') + { + char letter = tolower(fname[i]); + hash += (long)(letter) * (i + 119); + i++; + } + hash &= (FILE_HASH_SIZE - 1); + return hash; +} + +/* +============ +Cvar_ValidateString +============ +*/ +bool Cvar_ValidateString(const char *s) +{ + if (!s) + { + return false; + } + if (strchr(s, '\\')) + { + return false; + } + if (strchr(s, '\"')) + { + return false; + } + if (strchr(s, ';')) + { + return false; + } + return true; +} + +/* +============ +Cvar_FindVar +============ +*/ +cvar_t *Cvar_FindVar(const char *var_name) +{ + long hash = generateHashValue(var_name); + + for (cvar_t *var = hashTable[hash]; var; var = var->hashNext) + if (!Q_stricmp(var_name, var->name)) + return var; + + return nullptr; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue(const char *var_name) +{ + cvar_t *var = Cvar_FindVar(var_name); + if (!var) + return 0; + return var->value; +} + +/* +============ +Cvar_VariableIntegerValue +============ +*/ +int Cvar_VariableIntegerValue(const char *var_name) +{ + cvar_t *var = Cvar_FindVar(var_name); + if (!var) + return 0; + return var->integer; +} + +/* +============ +Cvar_VariableString +============ +*/ +const char *Cvar_VariableString(const char *var_name) +{ + cvar_t *var = Cvar_FindVar(var_name); + if (!var) + return ""; + return var->string; +} + +/* +============ +Cvar_VariableStringBuffer +============ +*/ +void Cvar_VariableStringBuffer(const char *var_name, char *buffer, int bufsize) +{ + cvar_t *var = Cvar_FindVar(var_name); + if (var) + Q_strncpyz(buffer, var->string, bufsize); + else + *buffer = 0; +} + +/* +============ +Cvar_Flags +============ +*/ +unsigned int Cvar_Flags(const char *var_name) +{ + cvar_t *var; + + if (!(var = Cvar_FindVar(var_name))) + return CVAR_NONEXISTENT; + else if (var->modified) + return var->flags | CVAR_MODIFIED; + + return var->flags; +} + +/* +============ +Cvar_CommandCompletion +============ +*/ +void Cvar_CommandCompletion(void (*callback)(const char *s)) +{ + for (cvar_t *cvar = cvar_vars; cvar; cvar = cvar->next) + { + if (cvar->name) + callback(cvar->name); + } +} + +/* +============ +Cvar_Validate +============ +*/ +const char *Cvar_Validate(cvar_t *var, const char *value, bool warn) +{ + static char s[MAX_CVAR_VALUE_STRING]; + float valuef; + bool changed = false; + + if (!var->validate) + return value; + + if (!value) + return nullptr; + + if (Q_isanumber(value)) + { + valuef = atof(value); + + if (var->integral) + { + if (!Q_isintegral(valuef)) + { + if (warn) + Com_Printf("WARNING: cvar '%s' must be integral", var->name); + + valuef = (int)valuef; + changed = true; + } + } + } + else + { + if (warn) + Com_Printf("WARNING: cvar '%s' must be numeric", var->name); + + valuef = atof(var->resetString); + changed = true; + } + + if (valuef < var->min) + { + if (warn) + { + if (changed) + Com_Printf(" and is"); + else + Com_Printf("WARNING: cvar '%s'", var->name); + + if (Q_isintegral(var->min)) + Com_Printf(" out of range (min %d)", (int)var->min); + else + Com_Printf(" out of range (min %f)", var->min); + } + + valuef = var->min; + changed = true; + } + else if (valuef > var->max) + { + if (warn) + { + if (changed) + Com_Printf(" and is"); + else + Com_Printf("WARNING: cvar '%s'", var->name); + + if (Q_isintegral(var->max)) + Com_Printf(" out of range (max %d)", (int)var->max); + else + Com_Printf(" out of range (max %f)", var->max); + } + + valuef = var->max; + changed = true; + } + + if (changed) + { + if (Q_isintegral(valuef)) + { + Com_sprintf(s, sizeof(s), "%d", (int)valuef); + + if (warn) + Com_Printf(", setting to %d\n", (int)valuef); + } + else + { + Com_sprintf(s, sizeof(s), "%f", valuef); + + if (warn) + Com_Printf(", setting to %f\n", valuef); + } + + return s; + } + + return value; +} + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set unless CVAR_ROM +The flags will be or'ed in if the variable exists. +============ +*/ +cvar_t *Cvar_Get(const char *var_name, const char *var_value, int flags) +{ + if (!var_name || !var_value) + { + Com_Error(ERR_FATAL, "Cvar_Get: nullptr parameter"); + } + + if (!Cvar_ValidateString(var_name)) + { + Com_Printf("invalid cvar name string: %s\n", var_name); + var_name = "BADNAME"; + } + +#if 0 // FIXME: values with backslash happen + if ( !Cvar_ValidateString( var_value ) ) { + Com_Printf("invalid cvar value string: %s\n", var_value ); + var_value = "BADVALUE"; + } +#endif + + cvar_t *var = Cvar_FindVar(var_name); + if (var) + { + var_value = Cvar_Validate(var, var_value, false); + + // Make sure the game code cannot mark engine-added variables as gamecode vars + if (var->flags & CVAR_VM_CREATED) + { + if (!(flags & CVAR_VM_CREATED)) + var->flags &= ~CVAR_VM_CREATED; + } + else if (!(var->flags & CVAR_USER_CREATED)) + { + if (flags & CVAR_VM_CREATED) + flags &= ~CVAR_VM_CREATED; + } + + // if the C code is now specifying a variable that the user already + // set a value for, take the new value as the reset value + if (var->flags & CVAR_USER_CREATED) + { + var->flags &= ~CVAR_USER_CREATED; + Z_Free(var->resetString); + var->resetString = CopyString(var_value); + + if (flags & CVAR_ROM) + { + // this variable was set by the user, + // so force it to value given by the engine. + + if (var->latchedString) + Z_Free(var->latchedString); + + var->latchedString = CopyString(var_value); + } + } + + // Make sure servers cannot mark engine-added variables as SERVER_CREATED + if (var->flags & CVAR_SERVER_CREATED) + { + if (!(flags & CVAR_SERVER_CREATED)) + var->flags &= ~CVAR_SERVER_CREATED; + } + else + { + if (flags & CVAR_SERVER_CREATED) + flags &= ~CVAR_SERVER_CREATED; + } + + var->flags |= flags; + + // only allow one non-empty reset string without a warning + if (!var->resetString[0]) + { + // we don't have a reset string yet + Z_Free(var->resetString); + var->resetString = CopyString(var_value); + } + else if (var_value[0] && strcmp(var->resetString, var_value)) + { + Com_DPrintf("Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", + var_name, var->resetString, var_value); + } + // if we have a latched string, take that value now + if (var->latchedString) + { + char *s = var->latchedString; + var->latchedString = nullptr; // otherwise cvar_set2 would free it + Cvar_Set2(var_name, s, true); + Z_Free(s); + } + + // ZOID--needs to be set so that cvars the game sets as + // SERVERINFO get sent to clients + cvar_modifiedFlags |= flags; + if (flags & CVAR_ALTERNATE_SYSTEMINFO) + { + cvar_modifiedFlags |= CVAR_SYSTEMINFO; + } + + return var; + } + + // + // allocate a new cvar + // + + // find a free cvar + int i; + for (i = 0; i < MAX_CVARS; i++) + if (!cvar_indexes[i].name) + break; + + if (i >= MAX_CVARS) + { + if (!com_errorEntered) + Com_Error(ERR_FATAL, "Error: Too many cvars, cannot create a new one!"); + return nullptr; + } + + var = &cvar_indexes[i]; + + if (i >= cvar_numIndexes) + cvar_numIndexes = i + 1; + + var->name = CopyString(var_name); + var->string = CopyString(var_value); + var->modified = true; + var->modificationCount = 1; + var->value = atof(var->string); + var->integer = atoi(var->string); + var->resetString = CopyString(var_value); + var->validate = false; + var->description = nullptr; + + // link the variable in + var->next = cvar_vars; + if (cvar_vars) + cvar_vars->prev = var; + + var->prev = nullptr; + cvar_vars = var; + + var->flags = flags; + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= var->flags; + if (var->flags & CVAR_ALTERNATE_SYSTEMINFO) + cvar_modifiedFlags |= CVAR_SYSTEMINFO; + + long hash = generateHashValue(var_name); + var->hashIndex = hash; + + var->hashNext = hashTable[hash]; + if (hashTable[hash]) + hashTable[hash]->hashPrev = var; + + var->hashPrev = nullptr; + hashTable[hash] = var; + + return var; +} + +/* +============ +Cvar_Print + +Prints the value, default, and latched string of the given variable +============ +*/ +void Cvar_Print(cvar_t *v) +{ + Com_Printf("\"%s\" is:\"%s" S_COLOR_WHITE "\"", v->name, v->string); + + if (!(v->flags & CVAR_ROM)) + { + if (!Q_stricmp(v->string, v->resetString)) + Com_Printf(", the default"); + else + Com_Printf(" default:\"%s" S_COLOR_WHITE "\"", v->resetString); + } + + Com_Printf("\n"); + + if (v->latchedString) + Com_Printf("latched: \"%s\"\n", v->latchedString); + + if (v->description) + Com_Printf("%s\n", v->description); +} + +/* +============ +Cvar_Set2 +============ +*/ +cvar_t *Cvar_Set2(const char *var_name, const char *value, bool force) +{ + if (!Cvar_ValidateString(var_name)) + { + Com_Printf("invalid cvar name string: %s\n", var_name); + var_name = "BADNAME"; + } + +#if 0 // FIXME + if ( value && !Cvar_ValidateString( value ) ) { + Com_Printf("invalid cvar value string: %s\n", value ); + var_value = "BADVALUE"; + } +#endif + + cvar_t *var = Cvar_FindVar(var_name); + if (!var) + { + if (!value) + return nullptr; + + if (!force) + return Cvar_Get(var_name, value, CVAR_USER_CREATED); + + return Cvar_Get(var_name, value, 0); + } + + if (!value) + value = var->resetString; + + value = Cvar_Validate(var, value, true); + + if ((var->flags & CVAR_LATCH) && var->latchedString) + { + if (!strcmp(value, var->string)) + { + Z_Free(var->latchedString); + var->latchedString = nullptr; + return var; + } + + if (!strcmp(value, var->latchedString)) + return var; + } + else if (!strcmp(value, var->string)) + return var; + + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= var->flags; + if (var->flags & CVAR_ALTERNATE_SYSTEMINFO) + { + cvar_modifiedFlags |= CVAR_SYSTEMINFO; + } + + if (!force) + { + if (var->flags & CVAR_ROM) + { + Com_Printf("%s is read only.\n", var_name); + return var; + } + + if (var->flags & CVAR_INIT) + { + Com_Printf("%s is write protected.\n", var_name); + return var; + } + + if (var->flags & CVAR_LATCH) + { + if (var->latchedString) + { + if (strcmp(value, var->latchedString) == 0) + return var; + Z_Free(var->latchedString); + var->latchedString = nullptr; + } + else + { + if (strcmp(value, var->string) == 0) + return var; + } + + Com_Printf("%s will be changed upon restarting.\n", var_name); + var->latchedString = CopyString(value); + var->modified = true; + var->modificationCount++; + return var; + } + + if ((var->flags & CVAR_CHEAT) && !cvar_cheats->integer) + { + Com_Printf("%s is cheat protected.\n", var_name); + return var; + } + } + else + { + if (var->latchedString) + { + Z_Free(var->latchedString); + var->latchedString = nullptr; + } + } + + if (!strcmp(value, var->string)) + return var; // not changed + + var->modified = true; + var->modificationCount++; + + Z_Free(var->string); // free the old value string + + var->string = CopyString(value); + var->value = atof(var->string); + var->integer = atoi(var->string); + + return var; +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set(const char *var_name, const char *value) +{ + Cvar_Set2(var_name, value, true); +} +/* +============ +Cvar_SetSafe +============ +*/ +void Cvar_SetSafe(const char *var_name, const char *value) +{ + unsigned flags = Cvar_Flags(var_name); + + if ((flags != CVAR_NONEXISTENT) && (flags & CVAR_PROTECTED)) + { + if (value) + Com_Error(ERR_DROP, "Restricted source tried to set \"%s\" to \"%s\"", + var_name, value); + else + Com_Error(ERR_DROP, "Restricted source tried to modify \"%s\"", + var_name); + return; + } + Cvar_Set(var_name, value); +} + +/* +============ +Cvar_SetLatched +============ +*/ +void Cvar_SetLatched(const char *var_name, const char *value) +{ + Cvar_Set2(var_name, value, false); +} +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue(const char *var_name, float value) +{ + char val[32]; + + if (value == (int)value) + { + Com_sprintf(val, sizeof(val), "%i", (int)value); + } + else + { + Com_sprintf(val, sizeof(val), "%f", value); + } + Cvar_Set(var_name, val); +} + +/* +============ +Cvar_SetValueSafe +============ +*/ +void Cvar_SetValueSafe(const char *var_name, float value) +{ + char val[32]; + + if (Q_isintegral(value)) + Com_sprintf(val, sizeof(val), "%i", (int)value); + else + Com_sprintf(val, sizeof(val), "%f", value); + Cvar_SetSafe(var_name, val); +} + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset(const char *var_name) +{ + Cvar_Set2(var_name, nullptr, false); +} +/* +============ +Cvar_ForceReset +============ +*/ +void Cvar_ForceReset(const char *var_name) +{ + Cvar_Set2(var_name, nullptr, true); +} +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState(void) +{ + // set all default vars to the safe value + for (cvar_t *var = cvar_vars; var; var = var->next) + { + if (var->flags & CVAR_CHEAT) + { + // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here + // because of a different var->latchedString + if (var->latchedString) + { + Z_Free(var->latchedString); + var->latchedString = nullptr; + } + if (strcmp(var->resetString, var->string)) + Cvar_Set(var->name, var->resetString); + } + } +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +bool Cvar_Command(void) +{ + cvar_t *v = Cvar_FindVar(Cmd_Argv(0)); + if (!v) + { + return false; + } + + // perform a variable print or set + if (Cmd_Argc() == 1) + { + Cvar_Print(v); + return true; + } + + // set the value if forcing isn't required + Cvar_Set2(v->name, Cmd_Args(), false); + return true; +} + +/* +============ +Cvar_Print_f + +Prints the contents of a cvar +(preferred over Cvar_Command where cvar names and commands conflict) +============ +*/ +void Cvar_Print_f(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf("usage: print \n"); + return; + } + + const char *name = Cmd_Argv(1); + cvar_t *cv = Cvar_FindVar(name); + + if (cv) + Cvar_Print(cv); + else + Com_Printf("Cvar %s does not exist.\n", name); +} + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding, optionally through a list of +given values +============ +*/ +void Cvar_Toggle_f(void) +{ + int c = Cmd_Argc(); + if (c < 2) + { + Com_Printf("usage: toggle [value1, value2, ...]\n"); + return; + } + else if (c == 2) + { + Cvar_Set2(Cmd_Argv(1), va("%d", !Cvar_VariableValue(Cmd_Argv(1))), false); + return; + } + else if (c == 3) + { + Com_Printf("toggle: nothing to toggle to\n"); + return; + } + + const char *curval = Cvar_VariableString(Cmd_Argv(1)); + + // don't bother checking the last arg for a match since the desired + // behaviour is the same as no match (set to the first argument) + for (int i = 2; i + 1 < c; i++) + { + if (strcmp(curval, Cmd_Argv(i)) == 0) + { + Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i + 1), false); + return; + } + } + + // fallback + Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), false); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f(void) +{ + int c = Cmd_Argc(); + const char *cmd = Cmd_Argv(0); + + if (c < 2) + { + Com_Printf("usage: %s \n", cmd); + return; + } + else if (c == 2) + { + Cvar_Print_f(); + return; + } + + cvar_t *v = Cvar_Set2(Cmd_Argv(1), Cmd_ArgsFrom(2), false); + if (!v) + { + return; + } + + switch (cmd[3]) + { + case 'a': + if (!(v->flags & CVAR_ARCHIVE)) + { + v->flags |= CVAR_ARCHIVE; + cvar_modifiedFlags |= CVAR_ARCHIVE; + } + break; + case 'u': + if (!(v->flags & CVAR_USERINFO)) + { + v->flags |= CVAR_USERINFO; + cvar_modifiedFlags |= CVAR_USERINFO; + } + break; + case 's': + if (!(v->flags & CVAR_SERVERINFO)) + { + v->flags |= CVAR_SERVERINFO; + cvar_modifiedFlags |= CVAR_SERVERINFO; + } + break; + } +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf("usage: reset \n"); + return; + } + Cvar_Reset(Cmd_Argv(1)); +} + +/* +============ +Cvar_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to true. +============ +*/ +void Cvar_WriteVariables(fileHandle_t f) +{ + cvar_t *var; + char buffer[1024]; + + for (var = cvar_vars; var; var = var->next) + { + if (!var->name) + continue; + + if (var->flags & CVAR_ARCHIVE) + { + // write the latched value, even if it hasn't taken effect yet + if (var->latchedString) + { + if (strlen(var->name) + strlen(var->latchedString) + 10 > sizeof(buffer)) + { + Com_Printf(S_COLOR_YELLOW + "WARNING: value of variable " + "\"%s\" too long to write to file\n", + var->name); + continue; + } + Com_sprintf(buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString); + } + else + { + if (strlen(var->name) + strlen(var->string) + 10 > sizeof(buffer)) + { + Com_Printf(S_COLOR_YELLOW + "WARNING: value of variable " + "\"%s\" too long to write to file\n", + var->name); + continue; + } + Com_sprintf(buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); + } + FS_Write(buffer, strlen(buffer), f); + } + } +} + +/* +============ +Cvar_List_f +============ +*/ +void Cvar_List_f(void) +{ + cvar_t *var; + int i; + const char *match; + + if (Cmd_Argc() > 1) + { + match = Cmd_Argv(1); + } + else + { + match = nullptr; + } + + i = 0; + for (var = cvar_vars; var; var = var->next, i++) + { + if (!var->name || (match && !Com_Filter(match, var->name, false))) + continue; + + if (var->flags & CVAR_SERVERINFO) + { + Com_Printf("S"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_SYSTEMINFO) + { + Com_Printf("s"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_USERINFO) + { + Com_Printf("U"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_ROM) + { + Com_Printf("R"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_INIT) + { + Com_Printf("I"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_ARCHIVE) + { + Com_Printf("A"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_LATCH) + { + Com_Printf("L"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_CHEAT) + { + Com_Printf("C"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_USER_CREATED) + { + Com_Printf("?"); + } + else + { + Com_Printf(" "); + } + + Com_Printf(" %s \"%s\"\n", var->name, var->string); + } + + Com_Printf("\n%i total cvars\n", i); + Com_Printf("%i cvar indexes\n", cvar_numIndexes); +} + +/* +============ +Cvar_ListModified_f +============ +*/ +void Cvar_ListModified_f(void) +{ + cvar_t *var; + int totalModified; + char *value; + const char *match; + + if (Cmd_Argc() > 1) + { + match = Cmd_Argv(1); + } + else + { + match = nullptr; + } + + totalModified = 0; + for (var = cvar_vars; var; var = var->next) + { + if (!var->name || !var->modificationCount) + continue; + + value = var->latchedString ? var->latchedString : var->string; + if (!strcmp(value, var->resetString)) + continue; + + totalModified++; + + if (match && !Com_Filter(match, var->name, false)) + continue; + + if (var->flags & CVAR_SERVERINFO) + { + Com_Printf("S"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_SYSTEMINFO) + { + Com_Printf("s"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_USERINFO) + { + Com_Printf("U"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_ROM) + { + Com_Printf("R"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_INIT) + { + Com_Printf("I"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_ARCHIVE) + { + Com_Printf("A"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_LATCH) + { + Com_Printf("L"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_CHEAT) + { + Com_Printf("C"); + } + else + { + Com_Printf(" "); + } + if (var->flags & CVAR_USER_CREATED) + { + Com_Printf("?"); + } + else + { + Com_Printf(" "); + } + + Com_Printf(" %s \"%s\", default \"%s\"\n", var->name, value, var->resetString); + } + + Com_Printf("\n%i total modified cvars\n", totalModified); +} + +/* +============ +Cvar_Unset + +Unsets a cvar +============ +*/ + +cvar_t *Cvar_Unset(cvar_t *cv) +{ + cvar_t *next = cv->next; + + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= cv->flags; + + if (cv->name) + Z_Free(cv->name); + if (cv->string) + Z_Free(cv->string); + if (cv->latchedString) + Z_Free(cv->latchedString); + if (cv->resetString) + Z_Free(cv->resetString); + if (cv->description) + Z_Free(cv->description); + + if (cv->prev) + cv->prev->next = cv->next; + else + cvar_vars = cv->next; + if (cv->next) + cv->next->prev = cv->prev; + + if (cv->hashPrev) + cv->hashPrev->hashNext = cv->hashNext; + else + hashTable[cv->hashIndex] = cv->hashNext; + if (cv->hashNext) + cv->hashNext->hashPrev = cv->hashPrev; + + ::memset(cv, '\0', sizeof(*cv)); + + return next; +} + +/* +============ +Cvar_Unset_f + +Unsets a userdefined cvar +============ +*/ + +void Cvar_Unset_f(void) +{ + cvar_t *cv; + + if (Cmd_Argc() != 2) + { + Com_Printf("Usage: %s \n", Cmd_Argv(0)); + return; + } + + cv = Cvar_FindVar(Cmd_Argv(1)); + + if (!cv) + return; + + if (cv->flags & CVAR_USER_CREATED) + Cvar_Unset(cv); + else + Com_Printf("Error: %s: Variable %s is not user created.\n", Cmd_Argv(0), cv->name); +} + +/* +============ +Cvar_Restart + +Resets all cvars to their hardcoded values and removes userdefined variables +and variables added via the VMs if requested. +============ +*/ + +void Cvar_Restart(bool unsetVM) +{ + cvar_t *curvar; + + curvar = cvar_vars; + + while (curvar) + { + if ((curvar->flags & CVAR_USER_CREATED) || (unsetVM && (curvar->flags & CVAR_VM_CREATED))) + { + // throw out any variables the user/vm created + curvar = Cvar_Unset(curvar); + continue; + } + + if (!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART))) + { + // Just reset the rest to their default values. + Cvar_Set2(curvar->name, curvar->resetString, false); + } + + curvar = curvar->next; + } +} + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f(void) +{ + Cvar_Restart(false); +} +/* +===================== +Cvar_InfoString +===================== +*/ +char *Cvar_InfoString(int bit) +{ + static char info[MAX_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for (var = cvar_vars; var; var = var->next) + { + if (var->name && (var->flags & bit)) + Info_SetValueForKey(info, var->name, var->string); + } + + return info; +} + +/* +===================== +Cvar_InfoString_Big + + handles large info strings ( CS_SYSTEMINFO ) +===================== +*/ +char *Cvar_InfoString_Big(int bit) +{ + static char info[BIG_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for (var = cvar_vars; var; var = var->next) + { + if (var->name && (var->flags & bit)) + Info_SetValueForKey_Big(info, var->name, var->string); + } + return info; +} + +/* +===================== +Cvar_InfoStringBuffer +===================== +*/ +void Cvar_InfoStringBuffer(int bit, char *buff, int buffsize) +{ + Q_strncpyz(buff, Cvar_InfoString(bit), buffsize); +} +/* +===================== +Cvar_CheckRange +===================== +*/ +void Cvar_CheckRange(cvar_t *var, float min, float max, bool integral) +{ + var->validate = true; + var->min = min; + var->max = max; + var->integral = integral; + + // Force an initial range check + Cvar_Set(var->name, var->string); +} + +/* +===================== +Cvar_SetDescription +===================== +*/ +void Cvar_SetDescription(cvar_t *var, const char *var_description) +{ + if (var_description && var_description[0] != '\0') + { + if (var->description != nullptr) + { + Z_Free(var->description); + } + var->description = CopyString(var_description); + } +} + +/* +===================== +Cvar_Register + +basically a slightly modified Cvar_Get for the interpreted modules +===================== +*/ +void Cvar_Register(vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags) +{ + cvar_t *cv; + + // There is code in Cvar_Get to prevent CVAR_ROM cvars being changed by the + // user. In other words CVAR_ARCHIVE and CVAR_ROM are mutually exclusive + // flags. Unfortunately some historical game code (including single player + // baseq3) sets both flags. We unset CVAR_ROM for such cvars. + if ((flags & (CVAR_ARCHIVE | CVAR_ROM)) == (CVAR_ARCHIVE | CVAR_ROM)) + { + Com_DPrintf(S_COLOR_YELLOW + "WARNING: Unsetting CVAR_ROM cvar '%s', " + "since it is also CVAR_ARCHIVE\n", + varName); + flags &= ~CVAR_ROM; + } + + cv = Cvar_Get(varName, defaultValue, flags | CVAR_VM_CREATED); + + if (!vmCvar) + return; + + vmCvar->handle = cv - cvar_indexes; + vmCvar->modificationCount = -1; + Cvar_Update(vmCvar); +} + +/* +===================== +Cvar_Update + +updates an interpreted modules' version of a cvar +===================== +*/ +void Cvar_Update(vmCvar_t *vmCvar) +{ + cvar_t *cv = nullptr; + assert(vmCvar); + + if (vmCvar->handle >= cvar_numIndexes) + { + Com_Error(ERR_DROP, "Cvar_Update: handle out of range"); + } + + cv = cvar_indexes + vmCvar->handle; + + if (cv->modificationCount == vmCvar->modificationCount) + { + return; + } + if (!cv->string) + { + return; // variable might have been cleared by a cvar_restart + } + vmCvar->modificationCount = cv->modificationCount; + if (strlen(cv->string) + 1 > MAX_CVAR_VALUE_STRING) + Com_Error(ERR_DROP, "Cvar_Update: src %s length %u exceeds MAX_CVAR_VALUE_STRING", cv->string, + (unsigned int)strlen(cv->string)); + Q_strncpyz(vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING); + + vmCvar->value = cv->value; + vmCvar->integer = cv->integer; +} + +/* +================== +Cvar_CompleteCvarName +================== +*/ +void Cvar_CompleteCvarName(char *args, int argNum) +{ + if (argNum == 2) + { + // Skip " " + char *p = Com_SkipTokens(args, 1, " "); + + if (p > args) + Field_CompleteCommand(p, false, true); + } +} + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init(void) +{ + ::memset(cvar_indexes, '\0', sizeof(cvar_indexes)); + ::memset(hashTable, '\0', sizeof(hashTable)); + + cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO); + + Cmd_AddCommand("print", Cvar_Print_f); + Cmd_AddCommand("toggle", Cvar_Toggle_f); + Cmd_SetCommandCompletionFunc("toggle", Cvar_CompleteCvarName); + Cmd_AddCommand("set", Cvar_Set_f); + Cmd_SetCommandCompletionFunc("set", Cvar_CompleteCvarName); + Cmd_AddCommand("sets", Cvar_Set_f); + Cmd_SetCommandCompletionFunc("sets", Cvar_CompleteCvarName); + Cmd_AddCommand("setu", Cvar_Set_f); + Cmd_SetCommandCompletionFunc("setu", Cvar_CompleteCvarName); + Cmd_AddCommand("seta", Cvar_Set_f); + Cmd_SetCommandCompletionFunc("seta", Cvar_CompleteCvarName); + Cmd_AddCommand("reset", Cvar_Reset_f); + Cmd_SetCommandCompletionFunc("reset", Cvar_CompleteCvarName); + Cmd_AddCommand("unset", Cvar_Unset_f); + Cmd_SetCommandCompletionFunc("unset", Cvar_CompleteCvarName); + + Cmd_AddCommand("cvarlist", Cvar_List_f); + Cmd_AddCommand("cvar_modified", Cvar_ListModified_f); + Cmd_AddCommand("cvar_restart", Cvar_Restart_f); +} diff --git a/src/qcommon/cvar.h b/src/qcommon/cvar.h new file mode 100644 index 0000000..465a99d --- /dev/null +++ b/src/qcommon/cvar.h @@ -0,0 +1,199 @@ +/* + * This file is part of Tremulous. + * Copyright © 2017 Victor Roemer (blowfish) + * Copyright (C) 2015-2019 GrangerHub + * + * 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 3 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, see . + */ + +#ifndef CVAR_H +#define CVAR_H + +#include "q_platform.h" +#include "q_shared.h" + +/* +========================================================== + +CVARS (console variables) + +Many variables can be used for cheating purposes, so when +cheats is zero, force all unspecified variables to their +default values. +========================================================== +*/ + +#define CVAR_ARCHIVE 0x0001 // set to cause it to be saved to vars.rc +// used for system variables, not for player +// specific configurations +#define CVAR_USERINFO 0x0002 // sent to server on connect or change +#define CVAR_SERVERINFO 0x0004 // sent in response to front end requests +#define CVAR_SYSTEMINFO 0x0008 // these cvars will be duplicated on all clients +#define CVAR_INIT 0x0010 // don't allow change from console at all, +// but can be set from the command line +#define CVAR_LATCH 0x0020 // will only change when C code next does +// a Cvar_Get(), so it can't be changed without proper initialization. +// modified will be set, even though the value hasn't changed yet +#define CVAR_ROM 0x0040 // display only, cannot be set by user at all +#define CVAR_USER_CREATED 0x0080 // created by a set command +#define CVAR_TEMP 0x0100 // can be set even when cheats are disabled, but is not archived +#define CVAR_CHEAT 0x0200 // can not be changed if cheats are disabled +#define CVAR_NORESTART 0x0400 // do not clear when a cvar_restart is issued + +#define CVAR_SERVER_CREATED 0x0800 // cvar was created by a server the client connected to. +#define CVAR_VM_CREATED 0x1000 // cvar was created exclusively in one of the VMs. +#define CVAR_PROTECTED 0x2000 // prevent modifying this var from VMs or the server +#define CVAR_ALTERNATE_SYSTEMINFO 0x1000000 +// These flags are only returned by the Cvar_Flags() function +#define CVAR_MODIFIED 0x40000000 // Cvar was modified +#define CVAR_NONEXISTENT 0x80000000 // Cvar doesn't exist. + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s cvar_t; + +struct cvar_s { + char *name; + char *string; + char *resetString; // cvar_restart will reset to this value + char *latchedString; // for CVAR_LATCH vars + int flags; + bool modified; // set each time the cvar is changed + int modificationCount; // incremented each time the cvar is changed + float value; // atof( string ) + int integer; // atoi( string ) + bool validate; + bool integral; + float min; + float max; + char *description; + + cvar_t *next; + cvar_t *prev; + cvar_t *hashNext; + cvar_t *hashPrev; + int hashIndex; +}; + +/* +============================================================== + +CVAR + +============================================================== +*/ + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed +or displayed at the console or prog code as well as accessed directly +in C code. + +The user can access cvars from the console in three ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +set r_draworder 0 as above, but creates the cvar if not present + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +The are also occasionally used to communicated information between different +modules of the program. + +*/ + +cvar_t *Cvar_Get(const char *var_name, const char *value, int flags); +// creates the variable if it doesn't exist, or returns the existing one +// if it exists, the value will not be changed, but flags will be ORed in +// that allows variables to be unarchived without needing bitflags +// if value is "", the value will not override a previously set value. + +void Cvar_Register(vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags); +// basically a slightly modified Cvar_Get for the interpreted modules + +void Cvar_Update(vmCvar_t *vmCvar); +// updates an interpreted modules' version of a cvar + +void Cvar_Set(const char *var_name, const char *value); +// will create the variable with no flags if it doesn't exist + +cvar_t *Cvar_Set2(const char *var_name, const char *value, bool force); +// same as Cvar_Set, but allows more control over setting of cvar + +void Cvar_SetSafe(const char *var_name, const char *value); +// sometimes we set variables from an untrusted source: fail if flags & CVAR_PROTECTED + +void Cvar_SetLatched(const char *var_name, const char *value); +// don't set the cvar immediately + +void Cvar_SetValue(const char *var_name, float value); +void Cvar_SetValueSafe(const char *var_name, float value); +// expands value to a string and calls Cvar_Set/Cvar_SetSafe + +// Validate String used to validate cvar names +bool Cvar_ValidateString(const char *s); +cvar_t *Cvar_FindVar(const char *var_name); +const char *Cvar_Validate(cvar_t *var, const char *value, bool warn); +void Cvar_Print(cvar_t *v); + +float Cvar_VariableValue(const char *var_name); +int Cvar_VariableIntegerValue(const char *var_name); +// returns 0 if not defined or non numeric + +const char *Cvar_VariableString(const char *var_name); +void Cvar_VariableStringBuffer(const char *var_name, char *buffer, int bufsize); +// returns an empty string if not defined + +unsigned int Cvar_Flags(const char *var_name); +// returns CVAR_NONEXISTENT if cvar doesn't exist or the flags of that particular CVAR. + +void Cvar_CommandCompletion(void (*callback)(const char *s)); +// callback with each valid string + +void Cvar_Reset(const char *var_name); +void Cvar_ForceReset(const char *var_name); + +void Cvar_SetCheatState(void); +// reset all testing vars to a safe value + +bool Cvar_Command(void); +// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known +// command. Returns true if the command was a variable reference that +// was handled. (print or change) + +void Cvar_WriteVariables(fileHandle_t f); +// writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +void Cvar_Init(void); + +char *Cvar_InfoString(int bit); +char *Cvar_InfoString_Big(int bit); +// returns an info string containing all the cvars that have the given bit set +// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc ) +void Cvar_InfoStringBuffer(int bit, char *buff, int buffsize); +void Cvar_CheckRange(cvar_t *cv, float minVal, float maxVal, bool shouldBeIntegral); +void Cvar_SetDescription(cvar_t *var, const char *var_description); + +void Cvar_Restart(bool unsetVM); +void Cvar_Restart_f(void); + +void Cvar_CompleteCvarName(char *args, int argNum); + +extern int cvar_modifiedFlags; +// whenever a cvar is modifed, its flags will be OR'd into this, so +// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO, +// etc, variables have been modified since the last check. The bit +// can then be cleared to allow another change detection. + +#endif diff --git a/src/qcommon/files.cpp b/src/qcommon/files.cpp new file mode 100644 index 0000000..68b72b3 --- /dev/null +++ b/src/qcommon/files.cpp @@ -0,0 +1,3986 @@ +/* + Copyright (C) 2016 Victor Roemer (wtfbbqhax), . + Copyright (C) 2000-2013 Darklegion Development + Copyright (C) 1999-2005 Id Software, Inc. + Copyright (C) 2015-2019 GrangerHub + + This file is part of Tremulous. + + Tremulous 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 3 of the License, + or (at your option) any later version. + + Tremulous 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 Tremulous; if not, see + +*/ + +#include "files.h" + +#ifdef _WIN32 +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "cvar.h" +#include "md4.h" +#include "q_platform.h" +#include "q_shared.h" +#include "qcommon.h" +#include "unzip.h" +#include "vm.h" + +#ifndef DEDICATED +#include "client/cl_rest.h" +#endif +#include "sys/sys_shared.h" + +using namespace std; + +#define MAX_ZPATH 256 +#define MAX_SEARCH_PATHS 4096 +#define MAX_FILEHASH_SIZE 1024 + +static bool FS_IsDemoExt(const char *filename); +static bool FS_IsExt(const char *filename, const char *ext, int namelen); + +struct fileInPack_t { + char* name; + unsigned long pos; // file info position in zip + unsigned long len; // uncompressed file size + fileInPack_t* next; +}; + +struct pack_t { + char pakPathname[MAX_OSPATH]; // /tremulous/baseq3 + char pakFilename[MAX_OSPATH]; // /tremulous/base/pak0.pk3 + char pakBasename[MAX_OSPATH]; // pak0 + char pakGamename[MAX_OSPATH]; // base + unzFile handle; // handle to zip file + int checksum; // regular checksum + int pure_checksum; // checksum for pure + int numfiles; // number of files in pk3 + int referenced; // referenced file flags + int hashSize; // hash table size (power of 2) + fileInPack_t **hashTable; // hash table + fileInPack_t *buildBuffer; // buffer with the filenames etc. + // some multiprotocol stuff + bool onlyPrimary; + bool onlyAlternate; + pack_t *primaryVersion; + + // member functions + inline fileInPack_t* find(string filename); + inline bool is_pure(); +}; + +struct directory_t { + char path[MAX_OSPATH]; + char fullpath[MAX_OSPATH]; // /tremulous/base + char gamedir[MAX_OSPATH]; // base +}; + +struct searchpath_t { + pack_t *pack; // only one of pack / dir will be non nullptr + directory_t *dir; + searchpath_t *next; +}; + +static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +static cvar_t *fs_debug; +static cvar_t *fs_homepath; + +static cvar_t *fs_basepath; +static cvar_t *fs_basegame; +#ifdef __APPLE__ +static cvar_t *fs_apppath; // Also search the .app bundle for .pk3 files +#endif +static cvar_t *fs_gamedirvar; + +static searchpath_t *fs_searchpaths; +static int fs_readCount; // total bytes read +static int fs_loadCount; // total files read +static int fs_loadStack; // total files in memory +static int fs_packFiles = 0; // total number of files in packs + +static int fs_checksumFeed; + +union qfile_gut { + FILE *o; + unzFile z; +}; + +struct qfile_ut { + qfile_gut file; + bool unique; +}; + +struct fileHandleData_t { + qfile_ut handleFiles; + bool handleSync; + int fileSize; + int zipFilePos; + int zipFileLen; + bool zipFile; + char name[MAX_ZPATH]; + + void close(); +}; + +static fileHandleData_t fsh[MAX_FILE_HANDLES]; + +// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +// wether we did a reorder on the current search path when joining the server + +static bool fs_reordered; + +// never load anything from pk3 files that are not present at the server when pure + +static int fs_numServerPaks = 0; +static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// only used for autodownload, to make sure the client has at least +// all the pk3 files that are referenced at the server side + +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// last valid game folder used +char lastValidBase[MAX_OSPATH]; +char lastValidGame[MAX_OSPATH]; + +#ifdef FS_MISSING +FILE *missingFiles = nullptr; +#endif + +/* +============== +FS_Initialized +============== +*/ + +bool FS_Initialized(void) { return fs_searchpaths ? true : false; } + +/* +================= +pack_t::is_pure() + +FIXME: also use hashed file names +================= +*/ +inline bool pack_t::is_pure() +{ + if (fs_numServerPaks) + { + for (int i = 0; i < fs_numServerPaks; i++) + if (checksum == fs_serverPaks[i]) + return true; + + return false; + } + return true; +} + +/* +================= +FS_LoadStack +return load stack +================= +*/ +int FS_LoadStack(void) { return fs_loadStack; } + +inline fileInPack_t* pack_t::find(string filename) +{ + long hash = 0; + auto fn = filename.c_str(); + for (long i = 0; fn[i]; i++) + { + long c = tolower(fn[i]); + if (c == '.') + break; // FIXME probably a bad idea + + if (c == '\\') + c = '/'; + hash += c * (i + 119); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (hashSize - 1); + + for (auto file = hashTable[hash]; file; file = file->next) + { + if (FS_FilenameCompare(file->name, fn) == false) + return file; + } + return nullptr; +} +/* +================ +return a hash value for the filename +================ +*/ +static long FS_HashFileName(const char *fname, int hashSize) +{ + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') + { + letter = tolower(fname[i]); + if (letter == '.') break; // don't include extension + if (letter == '\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash += (long)(letter) * (i + 119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (hashSize - 1); + return hash; +} + +static fileHandle_t FS_HandleForFile(void) +{ + for (int i = 1; i < MAX_FILE_HANDLES; i++) + { + if (fsh[i].handleFiles.file.o == nullptr) + { + return i; + } + } + Com_Error(ERR_DROP, "FS_HandleForFile: none free"); + return 0; +} + +static FILE *FS_FileForHandle(fileHandle_t f) +{ + if (f < 1 || f >= MAX_FILE_HANDLES) + { + Com_Error(ERR_DROP, "FS_FileForHandle: out of range"); + } + + if (fsh[f].zipFile == true) + { + Com_Error(ERR_DROP, "FS_FileForHandle: can't get FILE on zip file"); + } + + if (!fsh[f].handleFiles.file.o) + { + Com_Error(ERR_DROP, "FS_FileForHandle: nullptr"); + } + + return fsh[f].handleFiles.file.o; +} + +void FS_ForceFlush(fileHandle_t f) +{ + FILE *file = FS_FileForHandle(f); + setvbuf(file, nullptr, _IONBF, 0); +} + +/* +================ +FS_fplength +================ +*/ + +long FS_fplength(FILE *h) +{ + long pos = ftell(h); + fseek(h, 0, SEEK_END); + + long end = ftell(h); + fseek(h, pos, SEEK_SET); + + return end; +} + +/* +================ +FS_filelength + +If this is called on a non-unique FILE (from a pak file), +it will return the size of the pak file, not the expected +size of the file. +================ +*/ +long FS_filelength(fileHandle_t f) +{ + FILE *h = FS_FileForHandle(f); + if (h == nullptr) return -1; + + return FS_fplength(h); +} + +/* +==================== +FS_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +void FS_ReplaceSeparators(char *path) +{ + bool lastCharWasSep = false; + + for (char *s = path; *s; s++) + { + if (*s == '/' || *s == '\\') + { + if (!lastCharWasSep) + { + *s = PATH_SEP; + lastCharWasSep = true; + } + else + { + memmove(s, s + 1, strlen(s)); + } + } + else + { + lastCharWasSep = false; + } + } +} + +/* +=================== +FS_BuildOSPath + +Qpath may have either forward or backwards slashes +=================== +*/ +char *FS_BuildOSPath(const char *base, const char *game, const char *qpath) +{ + char temp[MAX_OSPATH]; + // "FIXME FS_BuildOSPath() returns static buffer with function scope" + + // This code will alternate between 2 different buffers- + // XXX 3 or more calls to FS_BuildOSPath in a row are not safe. + static char ospath[2][MAX_OSPATH]; + static bool toggle; + + toggle = !toggle; // flip-flop to allow two returns without clash + + if (!game || !game[0]) + { + game = fs_gamedir; + } + + Com_sprintf(temp, sizeof(temp), "/%s/%s", game, qpath); + FS_ReplaceSeparators(temp); + Com_sprintf(ospath[toggle], sizeof(ospath[0]), "%s%s", base, temp); + + Com_DPrintf(S_COLOR_GREEN "%s: returning " S_COLOR_RED "%s\n", + __FUNCTION__, ospath[toggle]); + + return ospath[toggle]; +} + +/* +============ +FS_OpenWithDefault + +Wrapper for Sys_OpenWithDefault() +============ +*/ +static bool FS_OpenWithDefault( const char *path ) +{ + if( Sys_OpenWithDefault( path ) ) + { + // minimize the client's window + Cmd_ExecuteString( "minimize" ); + return true; + } + + return false; +} + +/* +============ +FS_BrowseHomepath + +Opens the homepath in the default file manager +============ +*/ +bool FS_BrowseHomepath( void ) +{ + const char *homePath = Sys_DefaultHomePath( ); + + if (!homePath || !homePath[0]) + { + homePath = fs_basepath->string; + } + + if( FS_OpenWithDefault( homePath ) ) + return true; + + Com_Printf( S_COLOR_RED "FS_BrowseHomepath: failed to open the homepath with the default file manager.\n" S_COLOR_WHITE ); + return false; +} + +/* +============ +FS_OpenBaseGamePath + +Opens the given path for the +base game in the default file manager +============ +*/ +bool FS_OpenBaseGamePath( const char *baseGamePath ) +{ + const char *homePath = Sys_DefaultHomePath( ); + const char *path; + + if (!homePath || !homePath[0]) + { + homePath = fs_basepath->string; + } + + path = FS_BuildOSPath( homePath, fs_basegame->string, baseGamePath); + + if( FS_OpenWithDefault( path ) ) + return true; + + Com_Printf( S_COLOR_RED "FS_BrowseHomepath: failed to open the homepath with the default file manager.\n" S_COLOR_WHITE ); + return false; +} + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +bool FS_CreatePath(const char *OSPath) +{ + // make absolutely sure that it can't back up the path + // FIXME: is c: allowed??? + if (strstr(OSPath, "..") || strstr(OSPath, "::")) + { + Com_Printf("WARNING: refusing to create relative path \"%s\"\n", OSPath); + return true; + } + + char path[MAX_OSPATH]; + Q_strncpyz(path, OSPath, sizeof(path)); + FS_ReplaceSeparators(path); + + // Skip creation of the root directory as it will always be there + char *ofs = strchr(path, PATH_SEP); + if (ofs != nullptr) + { + ofs++; + } + + for (; ofs != nullptr && *ofs; ofs++) + { + if (*ofs == PATH_SEP) + { + // create the directory + *ofs = 0; + if (!Sys_Mkdir(path)) + { + Com_Error(ERR_FATAL, "FS_CreatePath: failed to create path \"%s\"", path); + } + *ofs = PATH_SEP; + } + } + + return false; +} + +/* +================= +FS_CheckFilenameIsMutable + +ERR_FATAL if trying to maniuplate a file with the platform library, QVM, or pk3 extension +================= + */ +static void FS_CheckFilenameIsMutable(const char *filename, const char *function) +{ + // Check if the filename ends with the library, QVM, or pk3 extension + if (Sys_DllExtension(filename) || COM_CompareExtension(filename, ".qvm") || + COM_CompareExtension(filename, ".lua") || COM_CompareExtension(filename, ".pk3")) + { + Com_Error(ERR_FATAL, "%s: Not allowed to manipulate '%s' due to %s extension", + function, filename, COM_GetExtension(filename)); + } +} + +/* +=========== +FS_Remove + +=========== +*/ +void FS_Remove(const char *osPath) +{ + FS_CheckFilenameIsMutable(osPath, __FUNCTION__); +// RB begin +#if defined(_WIN32) + ::DeleteFile(osPath); +#else + remove(osPath); +#endif +} + +/* +=========== +FS_HomeRemove + +=========== +*/ +void FS_HomeRemove(const char *homePath) +{ + FS_CheckFilenameIsMutable(homePath, __FUNCTION__); + FS_Remove(FS_BuildOSPath(fs_homepath->string, fs_gamedir, homePath)); +} + +#if 0 +bool FS_RemoveDir(const char* relativePath) +{ + bool success = true; + success = Sys_Rmdir(FS_BuildOSPath(fs_homepath->string, fs_gamedir, relativePath)); + return success; +} +#endif + +/* +================ +FS_FileInPathExists + +Tests if path and file exists +================ +*/ +bool FS_FileInPathExists(const char *testpath) +{ + FILE *filep; + + filep = Sys_FOpen(testpath, "rb"); + + if (filep) + { + fclose(filep); + return true; + } + + return false; +} + +/* +================ +FS_FileExists + +Tests if the file exists in the current gamedir, this DOES NOT +search the paths. This is to determine if opening a file to write +(which always goes into the current gamedir) will cause any overwrites. +NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards +================ +*/ +bool FS_FileExists(const char *file) +{ + return FS_FileInPathExists(FS_BuildOSPath(fs_homepath->string, fs_gamedir, file)); +} + +/* +================ +FS_SV_FileExists + +Tests if the file exists +================ +*/ +bool FS_SV_FileExists(const char *file) +{ + char *testpath = FS_BuildOSPath(fs_homepath->string, file, ""); + testpath[strlen(testpath) - 1] = '\0'; + + return FS_FileInPathExists(testpath); +} + +/* +=========== +FS_SV_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_SV_FOpenFileWrite(const char *filename) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + char *ospath = FS_BuildOSPath(fs_homepath->string, filename, ""); + ospath[strlen(ospath) - 1] = '\0'; + + fileHandle_t f = FS_HandleForFile(); + fsh[f].zipFile = false; + + Com_DPrintf("FS_SV_FOpenFileWrite: %s\n", ospath); + + FS_CheckFilenameIsMutable(ospath, __FUNCTION__); + + if (FS_CreatePath(ospath)) + { + return 0; + } + + Com_DPrintf("writing to: %s\n", ospath); + fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "wb"); + + Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name)); + + fsh[f].handleSync = false; + if (!fsh[f].handleFiles.file.o) + { + f = 0; + } + + return f; +} + +/* +=========== +FS_SV_FOpenFileRead + +Search for a file somewhere below the home path then base path +in that order +=========== +*/ +long FS_SV_FOpenFileRead(const char *filename, fileHandle_t *fp) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + fileHandle_t f = FS_HandleForFile(); + fsh[f].zipFile = false; + + Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name)); + + // don't let sound stutter + S_ClearSoundBuffer(); + + // search homepath + char *ospath = FS_BuildOSPath(fs_homepath->string, filename, ""); + // remove trailing slash + ospath[strlen(ospath) - 1] = '\0'; + + Com_DPrintf("FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath); + + fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "rb"); + fsh[f].handleSync = false; + if (!fsh[f].handleFiles.file.o) + { + // If fs_homepath == fs_basepath, don't bother + if (Q_stricmp(fs_homepath->string, fs_basepath->string)) + { + // search basepath + ospath = FS_BuildOSPath(fs_basepath->string, filename, ""); + ospath[strlen(ospath) - 1] = '\0'; + + Com_DPrintf("FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath); + + fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "rb"); + fsh[f].handleSync = false; + } + + if (!fsh[f].handleFiles.file.o) + { + f = 0; + } + } + + *fp = f; + if (f) + { + return FS_filelength(f); + } + + return -1; +} + +/* +=========== +FS_SV_Rename + +=========== +*/ +void FS_SV_Rename(const char *from, const char *to, bool safe) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + char *from_ospath = FS_BuildOSPath(fs_homepath->string, from, ""); + char *to_ospath = FS_BuildOSPath(fs_homepath->string, to, ""); + + from_ospath[strlen(from_ospath) - 1] = '\0'; + to_ospath[strlen(to_ospath) - 1] = '\0'; + + Com_DPrintf("FS_SV_Rename: (%s) %s --> %s\n", safe ? "safe" : "unsafe", from_ospath, to_ospath); + + if (safe) + { + FS_CheckFilenameIsMutable(to_ospath, __FUNCTION__); + } + + rename(from_ospath, to_ospath); +} + +/* +=========== +FS_Rename + +=========== +*/ +void FS_Rename(const char *from, const char *to) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + char *from_ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, from); + char *to_ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, to); + + Com_DPrintf("FS_Rename: %s --> %s\n", from_ospath, to_ospath); + + FS_CheckFilenameIsMutable(to_ospath, __FUNCTION__); + + rename(from_ospath, to_ospath); +} + +/* +============== +FS_FCloseFile + +If the FILE pointer is an open pak file, leave it open. + +For some reason, other dll's can't just cal fclose() +on files returned by FS_FOpenFile... +============== +*/ +void fileHandleData_t::close() +{ + if (zipFile == true) + { + unzCloseCurrentFile(handleFiles.file.z); + + if (handleFiles.unique) + unzClose(handleFiles.file.z); + } + // we didn't find it as a pak, so close it as a unique file + else if (handleFiles.file.o) + { + ::fclose(handleFiles.file.o); + } + + ::memset(this, 0, sizeof(*this)); +} + +void FS_FCloseFile(fileHandle_t f) +{ + if (!fs_searchpaths) + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + + fsh[f].close(); + + ::memset(&fsh[f], 0, sizeof(fsh[f])); +} + +/* +=========== +FS_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_FOpenFileWrite(const char *filename) +{ + + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + fileHandle_t f = FS_HandleForFile(); + fsh[f].zipFile = false; + + char *ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, filename); + + Com_DPrintf("FS_FOpenFileWrite: %s\n", ospath); + + FS_CheckFilenameIsMutable(ospath, __FUNCTION__); + + if (FS_CreatePath(ospath)) + { + return 0; + } + + // enabling the following line causes a recursive function call loop + // when running with +set logfile 1 +set developer 1 + // Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "wb"); + + Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name)); + + fsh[f].handleSync = false; + if (!fsh[f].handleFiles.file.o) + { + f = 0; + } + return f; +} + +/* +=========== +FS_FOpenFileAppend + +=========== +*/ +fileHandle_t FS_FOpenFileAppend(const char *filename) +{ + char *ospath; + fileHandle_t f; + + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = false; + + Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name)); + + // don't let sound stutter + S_ClearSoundBuffer(); + + ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, filename); + + Com_DPrintf("FS_FOpenFileAppend: %s\n", ospath); + + FS_CheckFilenameIsMutable(ospath, __FUNCTION__); + + if (FS_CreatePath(ospath)) + { + return 0; + } + + fsh[f].handleFiles.file.o = Sys_FOpen(ospath, "ab"); + fsh[f].handleSync = false; + if (!fsh[f].handleFiles.file.o) + { + f = 0; + } + return f; +} + +/* +=========== +FS_FCreateOpenPipeFile + +=========== +*/ +fileHandle_t FS_FCreateOpenPipeFile(const char *filename) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + fileHandle_t f = FS_HandleForFile(); + fsh[f].zipFile = false; + + Q_strncpyz(fsh[f].name, filename, sizeof(fsh[f].name)); + + // don't let sound stutter + S_ClearSoundBuffer(); + + char *ospath = FS_BuildOSPath(fs_homepath->string, fs_gamedir, filename); + + Com_DPrintf("FS_FCreateOpenPipeFile: %s\n", ospath); + + FS_CheckFilenameIsMutable(ospath, __FUNCTION__); + + FILE *fifo = Sys_Mkfifo(ospath); + if (fifo) + { + fsh[f].handleFiles.file.o = fifo; + fsh[f].handleSync = false; + } + else + { + Com_Printf(S_COLOR_YELLOW + "WARNING: Could not create new com_pipefile at %s. " + "com_pipefile will not be used.\n", + ospath); + f = 0; + } + + return f; +} + +/* +=========== +FS_FilenameCompare + +Ignore case and seprator char distinctions +=========== +*/ +bool FS_FilenameCompare(const char *s1, const char *s2) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (c1 >= 'a' && c1 <= 'z') + { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') + { + c2 -= ('a' - 'A'); + } + + if (c1 == '\\' || c1 == ':') + { + c1 = '/'; + } + if (c2 == '\\' || c2 == ':') + { + c2 = '/'; + } + + if (c1 != c2) + { + return true; // strings not equal + } + } while (c1); + + return false; // strings are equal +} + +/* +=========== +FS_IsExt + +Return true if ext matches file extension filename +=========== +*/ +static bool FS_IsExt(const char *filename, const char *ext, int namelen) +{ + int extlen = strlen(ext); + + if (extlen > namelen) return false; + + filename += namelen - extlen; + + return !Q_stricmp(filename, ext); +} + +/* +=========== +FS_IsDemoExt + +Return true if filename has a demo extension +=========== +*/ + +static bool FS_IsDemoExt(const char *filename) +{ + const char *ext_test = strrchr(filename, '.'); + if (ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) + { + int protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + if (protocol == PROTOCOL_VERSION) return true; + + for (int i = 0; demo_protocols[i]; i++) + if (demo_protocols[i] == protocol) return true; + } + + return false; +} + +/* +=========== +FS_FOpenFileReadDir + +Tries opening file "filename" in searchpath "search" +Returns filesize and an open FILE pointer. +=========== +*/ +long FS_FOpenFileReadDir( + const char *filename, void *_search, fileHandle_t *file, bool uniqueFILE, bool unpure) +{ + pack_t *pak; + directory_t *dir; + char *netpath; + FILE *filep; + int len; + + searchpath_t *search = static_cast(_search); + + if (filename == nullptr) + Com_Error(ERR_FATAL, "FS_FOpenFileRead: nullptr 'filename' parameter passed"); + + // qpaths are not supposed to have a leading slash + if (filename[0] == '/' || filename[0] == '\\') filename++; + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if (strstr(filename, "..") || strstr(filename, "::")) + { + if (file == nullptr) return false; + + *file = 0; + return -1; + } + + if (file == nullptr) + { + // just wants to see if file is there + + if ( fs_debug->integer ) + { + Com_Printf(S_COLOR_GREEN "Searching for: " S_COLOR_RED "%s\n", filename); + } + + // is the element a pak file? + if (search->pack) + { + auto pakfile = search->pack->find(filename); + if (pakfile) + { + // found it! + if (!pakfile->len) + { + // FIXME: It's not nice, but legacy code depends on + // positive value if file exists no matter what size + return 1; + } + + return pakfile->len; + } + } + else if (search->dir) + { + dir = search->dir; + + netpath = FS_BuildOSPath(dir->path, dir->gamedir, filename); + filep = Sys_FOpen(netpath, "rb"); + + if (filep) + { + len = FS_fplength(filep); + fclose(filep); + + if (len) + return len; + else + return 1; + } + } + + return 0; + } + + *file = FS_HandleForFile(); + fsh[*file].handleFiles.unique = uniqueFILE; + + // is the element a pak file? + if (search->pack) + { + pak = search->pack; + auto pakfile = pak->find(filename); + if (pakfile ) + { + if ( fs_debug->integer == 2 ) + Com_Printf(S_COLOR_GREEN "#2 Searching for: " S_COLOR_RED "%s\n", filename); + + // disregard if it doesn't match one of the allowed pure pak files + if (!unpure && !pak->is_pure()) + { + if ( fs_debug->integer == 2 ) + Com_Printf(S_COLOR_GREEN "Ugh-oh %s found in unpure pk3\n", filename); + + *file = 0; + return -1; + } + + len = strlen(filename); + + if (!(pak->referenced & FS_GENERAL_REF)) + { + if ( !FS_IsExt(filename, ".shader", len) + && !FS_IsExt(filename, ".mtr", len) + && !FS_IsExt(filename, ".txt", len) + && !FS_IsExt(filename, ".cfg", len) + && !FS_IsExt(filename, ".config", len) + && !FS_IsExt(filename, ".arena", len) + && !FS_IsExt(filename, ".menu", len) + && !strstr(filename, "levelshots") ) + { + pak->referenced |= FS_GENERAL_REF; + } + } + + if (strstr(filename, "cgame.qvm")) + pak->referenced |= FS_CGAME_REF; + + if (strstr(filename, "ui.qvm")) + pak->referenced |= FS_UI_REF; + + if (uniqueFILE) + { + fsh[*file].handleFiles.file.z = unzOpen(pak->pakFilename); + if ( !fsh[*file].handleFiles.file.z ) + Com_Error(ERR_FATAL, "Couldn't open %s", pak->pakFilename); + } + else + { + fsh[*file].handleFiles.file.z = pak->handle; + } + + Q_strncpyz(fsh[*file].name, filename, sizeof(fsh[*file].name)); + fsh[*file].zipFile = true; + + // set the file position in the zip file (also sets the current file info) + unzSetOffset(fsh[*file].handleFiles.file.z, pakfile->pos); + + // open the file in the zip + unzOpenCurrentFile(fsh[*file].handleFiles.file.z); + fsh[*file].zipFilePos = pakfile->pos; + fsh[*file].zipFileLen = pakfile->len; + + if ( fs_debug->integer ) + { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", + filename, pak->pakFilename); + } + + return pakfile->len; + } + } + else if (search->dir) + { + // check a file in the directory tree + + // if we are running restricted, the only files we + // will allow to come from the directory are .cfg files + len = strlen(filename); + // FIXME TTimo I'm not sure about the fs_numServerPaks test + // if you are using FS_ReadFile to find out if a file exists, + // this test can make the search fail although the file is in the directory + // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 + // turned out I used FS_FileExists instead + if (!unpure && fs_numServerPaks) + { + if (!FS_IsExt(filename, ".cfg", len) && // for config files + !FS_IsExt(filename, ".lua", len) && // lua + !FS_IsExt(filename, ".menu", len) && // menu files + !FS_IsExt(filename, ".game", len) && // menu files + !FS_IsExt(filename, ".dat", len) && // for journal files + !FS_IsDemoExt(filename)) // demos + { + *file = 0; + return -1; + } + } + + dir = search->dir; + + netpath = FS_BuildOSPath(dir->path, dir->gamedir, filename); + filep = Sys_FOpen(netpath, "rb"); + + if (filep == nullptr) + { + *file = 0; + return -1; + } + + Q_strncpyz(fsh[*file].name, filename, sizeof(fsh[*file].name)); + fsh[*file].zipFile = false; + + if (fs_debug->integer) + { + Com_Printf("FS_FOpenFileRead: %s (found in '%s%c%s')\n", + filename, dir->path, PATH_SEP, dir->gamedir); + } + + fsh[*file].handleFiles.file.o = filep; + return FS_fplength(filep); + } + + *file = 0; + return -1; +} + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +long FS_FOpenFileRead(const char *filename, fileHandle_t *file, bool uniqueFILE) +{ + searchpath_t *search; + long len; + + if (!fs_searchpaths) Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + + bool isLocalConfig = !strcmp(filename, "autoexec.cfg") || !strcmp(filename, Q3CONFIG_CFG); + for (search = fs_searchpaths; search; search = search->next) + { + // autoexec.cfg and q3config.cfg can only be loaded outside of pk3 files. + if (isLocalConfig && search->pack) continue; + + len = FS_FOpenFileReadDir(filename, search, file, uniqueFILE, false); + + if (file == nullptr) + { + if (len > 0) return len; + } + else + { + if (len >= 0 && *file) return len; + } + } + +#ifdef FS_MISSING + if (missingFiles) fprintf(missingFiles, "%s\n", filename); +#endif + + if (file) + { + *file = 0; + return -1; + } + else + { + // When file is nullptr, we're querying the existance of the file + // If we've got here, it doesn't exist + return 0; + } +} + +/* +================= +FS_FindVM + +Find a suitable VM file in search path order. + +In each searchpath try: + - open DLL file if DLL loading enabled + - open QVM file + +Enable search for DLL by setting enableDll to FSVM_ENABLEDLL + +write found DLL or QVM to "found" and return VMI_NATIVE if DLL, VMI_COMPILED if QVM +Return the searchpath in "startSearch". +================= +*/ + +int FS_FindVM(void **startSearch, char *found, int foundlen, const char *name, int enableDll) +{ + searchpath_t *search, *lastSearch; + directory_t *dir; + pack_t *pack; + char dllName[MAX_OSPATH], qvmName[MAX_OSPATH]; + char *netpath; + + if (!fs_searchpaths) Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + + if (enableDll) Com_sprintf(dllName, sizeof(dllName), "%s" DLL_EXT, name); + + Com_sprintf(qvmName, sizeof(qvmName), "vm/%s.qvm", name); + + lastSearch = static_cast(*startSearch); + if (*startSearch == nullptr) + search = fs_searchpaths; + else + search = lastSearch->next; + + while (search) + { + if (search->dir && (!fs_numServerPaks || !strcmp(name, "game"))) + { + dir = search->dir; + + if (enableDll) + { + netpath = FS_BuildOSPath(dir->path, dir->gamedir, dllName); + + if (FS_FileInPathExists(netpath)) + { + Q_strncpyz(found, netpath, foundlen); + *startSearch = search; + + return VMI_NATIVE; + } + } + + if (FS_FOpenFileReadDir(qvmName, search, nullptr, false, false) > 0) + { + *startSearch = search; + return VMI_COMPILED; + } + } + else if (search->pack) + { + pack = search->pack; + + if (lastSearch && lastSearch->pack) + { + // make sure we only try loading one VM file per game dir + // i.e. if VM from pak7.pk3 fails we won't try one from pak6.pk3 + + if (!FS_FilenameCompare(lastSearch->pack->pakPathname, pack->pakPathname)) + { + search = search->next; + continue; + } + } + + if (FS_FOpenFileReadDir(qvmName, search, nullptr, false, false) > 0) + { + *startSearch = search; + + return VMI_COMPILED; + } + } + + search = search->next; + } + + return -1; +} + +int FS_Read(void *buffer, int len, fileHandle_t f) +{ + int block, remaining; + int read; + byte *buf; + int tries; + + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + if (!f) + { + return 0; + } + + buf = (byte *)buffer; + fs_readCount += len; + + if (fsh[f].zipFile == false) + { + remaining = len; + tries = 0; + while (remaining) + { + block = remaining; + read = fread(buf, 1, block, fsh[f].handleFiles.file.o); + if (read == 0) + { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if (!tries) + { + tries = 1; + } + else + { + return len - remaining; // Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); + } + } + + if (read == -1) + { + Com_Error(ERR_FATAL, "FS_Read: -1 bytes read"); + } + + remaining -= read; + buf += read; + } + return len; + } + else + { + return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len); + } +} + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write(const void *buffer, int len, fileHandle_t h) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + if (!h) + { + return 0; + } + + FILE *f = FS_FileForHandle(h); + byte *buf = (byte *)buffer; + + int remaining = len; + int tries = 0; + while (remaining) + { + int block = remaining; + int written = fwrite(buf, 1, block, f); + if (written == 0) + { + if (!tries) + { + tries = 1; + } + else + { + Com_Printf("FS_Write: 0 bytes written\n"); + return 0; + } + } + + if (written == -1) + { + Com_Printf("FS_Write: -1 bytes written\n"); + return 0; + } + + remaining -= written; + buf += written; + } + + if (fsh[h].handleSync) + { + fflush(f); + } + return len; +} + +void QDECL FS_Printf(fileHandle_t h, const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start(argptr, fmt); + Q_vsnprintf(msg, sizeof(msg), fmt, argptr); + va_end(argptr); + + FS_Write(msg, strlen(msg), h); +} + +#define PK3_SEEK_BUFFER_SIZE 65536 + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek(fileHandle_t f, long offset, enum FS_Origin origin) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + return -1; + } + + if (fsh[f].zipFile == true) + { + // FIXME: this is really, really crappy + //(but better than what was here before) + byte buffer[PK3_SEEK_BUFFER_SIZE]; + int remainder; + int currentPosition = FS_FTell(f); + + // change negative offsets into FS_SEEK_SET + if (offset < 0) + { + switch (origin) + { + case FS_SEEK_END: + remainder = fsh[f].zipFileLen + offset; + break; + + case FS_SEEK_CUR: + remainder = currentPosition + offset; + break; + + case FS_SEEK_SET: + default: + remainder = 0; + break; + } + + if (remainder < 0) + { + remainder = 0; + } + + origin = FS_SEEK_SET; + } + else + { + if (origin == FS_SEEK_END) + { + remainder = fsh[f].zipFileLen - currentPosition + offset; + } + else + { + remainder = offset; + } + } + + switch (origin) + { + case FS_SEEK_SET: + if (remainder == currentPosition) + { + return offset; + } + unzSetOffset(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); + unzOpenCurrentFile(fsh[f].handleFiles.file.z); + // fallthrough + + case FS_SEEK_END: // fall through + case FS_SEEK_CUR: + while (remainder > PK3_SEEK_BUFFER_SIZE) + { + FS_Read(buffer, PK3_SEEK_BUFFER_SIZE, f); + remainder -= PK3_SEEK_BUFFER_SIZE; + } + FS_Read(buffer, remainder, f); + return offset; + + default: + Com_Error(ERR_FATAL, "Bad origin in FS_Seek"); + return -1; + } + } + else + { + FILE *file; + file = FS_FileForHandle(f); + int _origin; + switch (origin) + { + case FS_SEEK_CUR: + _origin = SEEK_CUR; + break; + case FS_SEEK_END: + _origin = SEEK_END; + break; + case FS_SEEK_SET: + _origin = SEEK_SET; + break; + default: + Com_Error(ERR_FATAL, "Bad origin in FS_Seek"); + break; + } + + return fseek(file, offset, _origin); + } +} + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +int FS_FileIsInPAK_A(bool alternate, const char *filename, int *pChecksum) +{ + if (!fs_searchpaths) + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + + if (!filename) + Com_Error(ERR_FATAL, "FS_FOpenFileRead: nullptr 'filename' parameter passed"); + + // qpaths are not supposed to have a leading slash + if (filename[0] == '/' || filename[0] == '\\') + filename++; + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if (strstr(filename, "..") || strstr(filename, "::")) + return -1; + + // + // search through the path, one element at a time + // + for (auto search = fs_searchpaths; search; search = search->next) + { + if (!search->pack) + continue; + + // disregard if it doesn't match one of the allowed pure pak files + if (!search->pack->is_pure()) + continue; + + if ((alternate && search->pack->onlyPrimary) || + (!alternate && search->pack->onlyAlternate)) + continue; + + auto found = search->pack->find(filename); + if (found) + { + if (pChecksum) + *pChecksum = search->pack->pure_checksum; + + return 1; + } + } + return -1; +} + +int FS_FileIsInPAK(const char *filename, int *pChecksum) +{ + return FS_FileIsInPAK_A(false, filename, pChecksum); +} +/* +============ +FS_ReadFileDir + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +If searchPath is non-nullptr search only in that specific search path +============ +*/ +long FS_ReadFileDir(const char *qpath, void *searchPath, bool unpure, void **buffer) +{ + fileHandle_t h; + byte *buf; + bool isConfig; + long len; + + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + if (!qpath || !qpath[0]) + { + Com_Error(ERR_FATAL, "FS_ReadFile with empty name"); + } + + buf = nullptr; // quiet compiler warning + + // if this is a .cfg file and we are playing back a journal, read + // it from the journal file + if (strstr(qpath, ".cfg")) + { + isConfig = true; + if (com_journal && com_journal->integer == 2) + { + Com_DPrintf("Loading %s from journal file.\n", qpath); + + int r = FS_Read(&len, sizeof(len), com_journalDataFile); + if (r != sizeof(len)) + { + if (buffer != nullptr) *buffer = nullptr; + return -1; + } + + // if the file didn't exist when the journal was created + if (!len) + { + if (buffer == nullptr) + { + return 1; // hack for old journal files + } + *buffer = nullptr; + return -1; + } + + if (buffer == nullptr) + { + return len; + } + + buf = static_cast(Hunk_AllocateTempMemory(len + 1)); + *buffer = buf; + + r = FS_Read(buf, len, com_journalDataFile); + if (r != len) + { + Com_Error(ERR_FATAL, "Read from journalDataFile failed"); + } + + fs_loadCount++; + fs_loadStack++; + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + + return len; + } + } + else + { + isConfig = false; + } + + searchpath_t *search = static_cast(searchPath); + if (search == nullptr) + { + // look for it in the filesystem or pack files + len = FS_FOpenFileRead(qpath, &h, false); + } + else + { + // look for it in a specific search path only + len = FS_FOpenFileReadDir(qpath, search, &h, false, unpure); + } + + if (h == 0) + { + if (buffer) + { + *buffer = nullptr; + } + // if we are journalling and it is a config file, write a zero to the journal file + if (isConfig && com_journal && com_journal->integer == 1) + { + Com_DPrintf("Writing zero for %s to journal file.\n", qpath); + len = 0; + FS_Write(&len, sizeof(len), com_journalDataFile); + FS_Flush(com_journalDataFile); + } + return -1; + } + + if (!buffer) + { + if (isConfig && com_journal && com_journal->integer == 1) + { + Com_DPrintf("Writing len for %s to journal file.\n", qpath); + FS_Write(&len, sizeof(len), com_journalDataFile); + FS_Flush(com_journalDataFile); + } + FS_FCloseFile(h); + return len; + } + + fs_loadCount++; + fs_loadStack++; + + buf = static_cast(Hunk_AllocateTempMemory(len + 1)); + *buffer = buf; + + FS_Read(buf, len, h); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile(h); + + // if we are journalling and it is a config file, write it to the journal file + if (isConfig && com_journal && com_journal->integer == 1) + { + Com_DPrintf("Writing %s to journal file.\n", qpath); + FS_Write(&len, sizeof(len), com_journalDataFile); + FS_Write(buf, len, com_journalDataFile); + FS_Flush(com_journalDataFile); + } + return len; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +long FS_ReadFile(const char *qpath, void **buffer) +{ + return FS_ReadFileDir(qpath, nullptr, false, buffer); +} +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile(void *buffer) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + if (!buffer) + { + Com_Error(ERR_FATAL, "FS_FreeFile( nullptr )"); + } + + fs_loadStack--; + Hunk_FreeTempMemory(buffer); + + // if all of our temp files are free, clear all of our space + if (fs_loadStack == 0) + { + Hunk_ClearTempMemory(); + } +} + +/* +============ +FS_WriteFile + +Filename are relative to the quake search path +============ +*/ +void FS_WriteFile(const char *qpath, const void *buffer, int size) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + if (!qpath || !buffer) + { + Com_Error(ERR_FATAL, "FS_WriteFile: nullptr parameter"); + } + + fileHandle_t f = FS_FOpenFileWrite(qpath); + if (!f) + { + Com_Printf("Failed to open %s\n", qpath); + return; + } + + FS_Write(buffer, size, f); + FS_FCloseFile(f); +} + +/* +========================================================================== + +ZIP FILE LOADING + +========================================================================== +*/ + +/* +================= +FS_LoadZipFile + +Creates a new pack_t in the search chain for the contents of a zip file. +================= +*/ +static pack_t *FS_LoadZipFile(const char *zipfile, const char *basename) +{ + int fs_numHeaderLongs = 0; + unsigned long len = 0; + char filename[MAX_ZPATH]; + + auto z = unzOpen(zipfile); + + unz_global_info gi; + int err = unzGetGlobalInfo(z, &gi); + if (err) return nullptr; + + err = unzGoToFirstFile(z); + if (err) return nullptr; + + for (uLong i = 0; i < gi.number_entry; i++) + { + unz_file_info fi; + err = unzGetCurrentFileInfo( + z, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0); + + if (err) break; + + len += strlen(filename) + 1; + unzGoToNextFile(z); + } + + fileInPack_t *buildBuffer = + static_cast(Z_Malloc((gi.number_entry * sizeof(fileInPack_t)) + len)); + + char *namePtr = ((char *)buildBuffer) + gi.number_entry * sizeof(fileInPack_t); + + int *fs_headerLongs = static_cast(Z_Malloc((gi.number_entry + 1) * sizeof(int))); + + fs_headerLongs[fs_numHeaderLongs] = LittleLong(fs_checksumFeed); + fs_numHeaderLongs++; + + // get the hash table size from the number of files in the zip + // because lots of custom pk3 files have less than 32 or 64 files + uLong hashsiz; + for (hashsiz = 1; hashsiz <= MAX_FILEHASH_SIZE; hashsiz <<= 1) + { + if (hashsiz > gi.number_entry) break; + } + + pack_t *pack = static_cast(Z_Malloc(sizeof(pack_t) + hashsiz * sizeof(fileInPack_t *))); + + pack->hashSize = hashsiz; + pack->hashTable = (fileInPack_t **)(((char *)pack) + sizeof(pack_t)); + + for (int i = 0; i < pack->hashSize; i++) + { + pack->hashTable[i] = nullptr; + } + + Q_strncpyz(pack->pakFilename, zipfile, sizeof(pack->pakFilename)); + Q_strncpyz(pack->pakBasename, basename, sizeof(pack->pakBasename)); + + // strip .pk3 if needed + if ( strlen(pack->pakBasename) > 4 && + !Q_stricmp(pack->pakBasename + strlen(pack->pakBasename) - 4, ".pk3")) + { + pack->pakBasename[strlen(pack->pakBasename) - 4] = '\0'; + } + + pack->handle = z; + pack->numfiles = gi.number_entry; + unzGoToFirstFile(z); + for (uLong i = 0; i < gi.number_entry; i++) + { + unz_file_info fi; + err = unzGetCurrentFileInfo( + z, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0); + + if (err) break; + + if (fi.uncompressed_size) + { + fs_headerLongs[fs_numHeaderLongs] = LittleLong(fi.crc); + fs_numHeaderLongs++; + } + + Q_strlwr(filename); + long hash = FS_HashFileName(filename, pack->hashSize); + + buildBuffer[i].name = namePtr; + strcpy(buildBuffer[i].name, filename); + namePtr += strlen(filename) + 1; + + // store the file position in the zip + buildBuffer[i].pos = unzGetOffset(z); + buildBuffer[i].len = fi.uncompressed_size; + buildBuffer[i].next = pack->hashTable[hash]; + + pack->hashTable[hash] = &buildBuffer[i]; + unzGoToNextFile(z); + } + + pack->checksum = + Com_BlockChecksum(&fs_headerLongs[1], sizeof(*fs_headerLongs) * (fs_numHeaderLongs - 1)); + pack->pure_checksum = + Com_BlockChecksum(fs_headerLongs, sizeof(*fs_headerLongs) * fs_numHeaderLongs); + pack->checksum = LittleLong(pack->checksum); + pack->pure_checksum = LittleLong(pack->pure_checksum); + + Z_Free(fs_headerLongs); + + pack->buildBuffer = buildBuffer; + return pack; +} + +/* +================= +FS_FreePak + +Frees a pak structure and releases all associated resources +================= +*/ + +static void FS_FreePak(pack_t *thepak) +{ + unzClose(thepak->handle); + Z_Free(thepak->buildBuffer); + Z_Free(thepak); +} + +/* +================= +FS_GetZipChecksum + +Compares whether the given pak file matches a referenced checksum +================= +*/ +bool FS_CompareZipChecksum(const char *zipfile) +{ + pack_t *thepak = FS_LoadZipFile(zipfile, ""); + + if (!thepak) return false; + + int checksum = thepak->checksum; + FS_FreePak(thepak); + + for (int i = 0; i < fs_numServerReferencedPaks; i++) + { + if (checksum == fs_serverReferencedPaks[i]) return true; + } + + return false; +} + +/* +================================================================================= + +DIRECTORY SCANNING FUNCTIONS + +================================================================================= +*/ + +#define MAX_FOUND_FILES 0x1000 + +static int FS_ReturnPath(const char *zname, char *zpath, int *depth) +{ + int newdep = 0; + int len = 0; + int at = 0; + zpath[0] = 0; + + while (zname[at] != 0) + { + if (zname[at] == '/' || zname[at] == '\\') + { + len = at; + newdep++; + } + at++; + } + strcpy(zpath, zname); + zpath[len] = 0; + *depth = newdep; + + return len; +} + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList(char *name, char *list[MAX_FOUND_FILES], int nfiles) +{ + if (nfiles == MAX_FOUND_FILES - 1) + { + return nfiles; + } + + for (int i = 0; i < nfiles; i++) + { + if (!Q_stricmp(name, list[i])) + { + return nfiles; // allready in list + } + } + list[nfiles] = CopyString(name); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFilteredFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFilteredFiles(const char *path, const char *extension, const char *filter, + int *numfiles, bool allowNonPureFilesOnDisk) +{ + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + searchpath_t *search; + int i; + int pathLength; + int extensionLength; + int length, pathDepth, temp; + pack_t *pak; + fileInPack_t *buildBuffer; + char zpath[MAX_ZPATH]; + + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + if (!path) + { + *numfiles = 0; + return nullptr; + } + if (!extension) + { + extension = ""; + } + + pathLength = strlen(path); + if (path[pathLength - 1] == '\\' || path[pathLength - 1] == '/') + { + pathLength--; + } + extensionLength = strlen(extension); + nfiles = 0; + FS_ReturnPath(path, zpath, &pathDepth); + + // + // search through the path, one element at a time, adding to list + // + for (search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file? + if (search->pack) + { + // ZOID: If we are pure, don't search for files on paks that + // aren't on the pure list + if (!search->pack->is_pure()) + { + continue; + } + + // look through all the pak file elements + pak = search->pack; + buildBuffer = pak->buildBuffer; + for (i = 0; i < pak->numfiles; i++) + { + char *name; + int zpathLen, depth; + + // check for directory match + name = buildBuffer[i].name; + // + if (filter) + { + // case insensitive + if (!Com_FilterPath(filter, name, false)) continue; + // unique the match + nfiles = FS_AddFileToList(name, list, nfiles); + } + else + { + zpathLen = FS_ReturnPath(name, zpath, &depth); + + if ((depth - pathDepth) > 2 || pathLength > zpathLen || + Q_stricmpn(name, path, pathLength)) + { + continue; + } + + // check for extension match + length = strlen(name); + if (length < extensionLength) + { + continue; + } + + if (Q_stricmp(name + length - extensionLength, extension)) + { + continue; + } + // unique the match + + temp = pathLength; + if (pathLength) + { + temp++; // include the '/' + } + nfiles = FS_AddFileToList(name + temp, list, nfiles); + } + } + } + else if (search->dir) + { // scan for files in the filesystem + + // don't scan directories for files if we are pure or restricted + if (fs_numServerPaks && !allowNonPureFilesOnDisk) + { + continue; + } + else + { + int numSysFiles; + char *netpath = FS_BuildOSPath(search->dir->path, search->dir->gamedir, path); + char **sysFiles = Sys_ListFiles(netpath, extension, filter, &numSysFiles, false); + for (i = 0; i < numSysFiles; i++) + { + // unique the match + char *name = sysFiles[i]; + nfiles = FS_AddFileToList(name, list, nfiles); + } + Sys_FreeFileList(sysFiles); + } + } + } + + // return a copy of the list + *numfiles = nfiles; + + if (!nfiles) + { + return nullptr; + } + + listCopy = static_cast(Z_Malloc((nfiles + 1) * sizeof(*listCopy))); + for (i = 0; i < nfiles; i++) + { + listCopy[i] = list[i]; + } + listCopy[i] = nullptr; + + return listCopy; +} + +/* +================= +FS_ListFiles +================= +*/ +char **FS_ListFiles(const char *path, const char *extension, int *numfiles) +{ + return FS_ListFilteredFiles(path, extension, nullptr, numfiles, false); +} + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList(char **list) +{ + if (!fs_searchpaths) + { + Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + } + + if (!list) + { + return; + } + + for (int i = 0; list[i]; i++) + { + Z_Free(list[i]); + } + + Z_Free(list); +} + +/* +================ +FS_GetFileList +================ +*/ +int FS_GetFileList(const char *path, const char *extension, char *listbuf, int bufsize) +{ + int nFiles = 0; + int nTotal = 0; + *listbuf = 0; + + if (Q_stricmp(path, "$modlist") == 0) + { + return FS_GetModList(listbuf, bufsize); + } + + char **pFiles = FS_ListFiles(path, extension, &nFiles); + + for (int i = 0; i < nFiles; i++) + { + int nLen = strlen(pFiles[i]) + 1; + if (nTotal + nLen + 1 < bufsize) + { + strcpy(listbuf, pFiles[i]); + listbuf += nLen; + nTotal += nLen; + } + else + { + nFiles = i; + break; + } + } + + FS_FreeFileList(pFiles); + + return nFiles; +} + +/* +================ +FS_GetFilteredFiles +================ +*/ +int FS_GetFilteredFiles( + const char *path, const char *extension, const char *filter, char *listbuf, int bufsize) +{ + int nFiles = 0; + int nTotal = 0; + *listbuf = 0; + + char **pFiles = FS_ListFilteredFiles(path, extension, filter, &nFiles, false); + + for (int i = 0; i < nFiles; i++) + { + int nLen = strlen(pFiles[i]) + 1; + if (nTotal + nLen + 1 < bufsize) + { + strcpy(listbuf, pFiles[i]); + listbuf += nLen; + nTotal += nLen; + } + else + { + nFiles = i; + break; + } + } + + FS_FreeFileList(pFiles); + + return nFiles; +} + +/* +======================= +Sys_ConcatenateFileLists + +mkv: Naive implementation. Concatenates three lists into a + new list, and frees the old lists from the heap. +bk001129 - from cvs1.17 (mkv) + +FIXME TTimo those two should move to common.c next to Sys_ListFiles +======================= + */ +static unsigned int Sys_CountFileList(char **list) +{ + unsigned int i = 0; + if (list) + { + while (*list) + { + list++; + i++; + } + } + return i; +} + +static char **Sys_ConcatenateFileLists(char **list0, char **list1) +{ + char **cat, **dst; + + int totalLength = 0; + totalLength += Sys_CountFileList(list0); + totalLength += Sys_CountFileList(list1); + + /* Create new list. */ + dst = cat = static_cast(Z_Malloc((totalLength + 1) * sizeof(char *))); + + /* Copy over lists. */ + if (list0) + { + for (char **src = list0; *src; src++, dst++) *dst = *src; + } + + if (list1) + { + for (char **src = list1; *src; src++, dst++) *dst = *src; + } + + // Terminate the list + *dst = nullptr; + + // Free our old lists. + // NOTE: not freeing their content, it's been merged in dst and still being used + if (list0) Z_Free(list0); + if (list1) Z_Free(list1); + + return cat; +} + +/* +================ +FS_GetModList + +Returns a list of mod directory names +A mod directory is a peer to baseq3 with a pk3 or pk3dir in it +================ +*/ +int FS_GetModList( char *listbuf, int bufsize ) +{ + char * start = listbuf; + *listbuf = '\0'; + + // paths to search for mods + const char * const paths[] = { + fs_basepath->string, + fs_homepath->string + }; + + char **pFiles = nullptr; + for (int i = 0; i < ARRAY_LEN(paths); i++) + { + int dummy; + char **pFiles0 = Sys_ListFiles(paths[i], NULL, NULL, &dummy, true); + pFiles = Sys_ConcatenateFileLists(pFiles, pFiles0); + } + + int nMods = 0; + int nTotal = 0; + for (int i = 0; i < Sys_CountFileList(pFiles); i++) + { + const char* name = pFiles[i]; + + if ( name[0] == '.' ) + continue; + + // In order to be a valid mod the directory must contain at least one + // .pk3 or .pk3dir we didn't keep the information when we merged the + // directory names, as to what OS Path it was found under so we will + // try each of them here. + int nPaks = 0; + int nPakDirs = 0; + for (int j = 0; j < ARRAY_LEN(paths); j++) + { + const char* path = FS_BuildOSPath(paths[j], name, ""); + int nDirs = 0; + + char **pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, false); + char **pDirs = Sys_ListFiles(path, "/", NULL, &nDirs, false); + for (int k = 0; k < nDirs; k++) + { + // we only want to count directories ending with ".pk3dir" + if (FS_IsExt(pDirs[k], ".pk3dir", strlen(pDirs[k]))) + nPakDirs++; + } + + // we only use Sys_ListFiles to check whether files are present + Sys_FreeFileList(pPaks); + Sys_FreeFileList(pDirs); + + if (nPaks > 0 || nPakDirs > 0) + break; + } + + if (nPaks > 0 || nPakDirs > 0) + { + size_t nLen = strlen(name) + 1; + + if (nTotal + nLen + 1 < bufsize) + { + strcpy(listbuf, name); + listbuf += nLen; + nTotal += nLen; + nMods++; + } + else + { + Com_Printf(S_COLOR_RED "Warning: Too many mods!\n"); + break; + } + } + } + + Sys_FreeFileList( pFiles ); + return nMods; +} + +//============================================================================ + +/* +================ +FS_Dir_f +================ +*/ +void FS_Dir_f(void) +{ + const char *path; + const char *extension; + + if (Cmd_Argc() < 2 || Cmd_Argc() > 3) + { + Com_Printf("usage: dir [extension]\n"); + return; + } + + if (Cmd_Argc() == 2) + { + path = Cmd_Argv(1); + extension = ""; + } + else + { + path = Cmd_Argv(1); + extension = Cmd_Argv(2); + } + + Com_Printf("Directory of %s %s\n", path, extension); + Com_Printf("---------------\n"); + + int ndirs; + char **dirnames = FS_ListFiles(path, extension, &ndirs); + + for (int i = 0; i < ndirs; i++) + { + Com_Printf("%s\n", dirnames[i]); + } + FS_FreeFileList(dirnames); +} + +/* +=========== +FS_ConvertPath +=========== +*/ +void FS_ConvertPath(char *s) +{ + while (*s) + { + if (*s == '\\' || *s == ':') + { + *s = '/'; + } + s++; + } +} + +/* +=========== +FS_PathCmp + +Ignore case and seprator char distinctions +=========== +*/ +int FS_PathCmp(const char *s1, const char *s2) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (c1 >= 'a' && c1 <= 'z') + { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') + { + c2 -= ('a' - 'A'); + } + + if (c1 == '\\' || c1 == ':') + { + c1 = '/'; + } + if (c2 == '\\' || c2 == ':') + { + c2 = '/'; + } + + if (c1 < c2) + { + return -1; // strings not equal + } + else if (c1 > c2) + { + return 1; + } + } while (c1); + + return 0; // strings are equal +} + +/* +================ +FS_SortFileList +================ +*/ +void FS_SortFileList(char **filelist, int numfiles) +{ + char **sortedlist = static_cast(Z_Malloc((numfiles + 1) * sizeof(*sortedlist))); + sortedlist[0] = nullptr; + + int numsortedfiles = 0; + for (int i = 0; i < numfiles; i++) + { + int j; + for (j = 0; j < numsortedfiles; j++) + { + if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) break; + } + + int k; + for (k = numsortedfiles; k > j; k--) + { + sortedlist[k] = sortedlist[k - 1]; + } + + sortedlist[j] = filelist[i]; + numsortedfiles++; + } + ::memcpy(filelist, sortedlist, numfiles * sizeof(*filelist)); + Z_Free(sortedlist); +} + +/* +================ +FS_NewDir_f +================ +*/ +void FS_NewDir_f(void) +{ + if (Cmd_Argc() < 2) + { + Com_Printf("usage: fdir \n"); + Com_Printf("example: fdir *q3dm*.bsp\n"); + return; + } + + Com_Printf("---------------\n"); + + const char *filter = Cmd_Argv(1); + + int ndirs; + char **dirnames = FS_ListFilteredFiles("", "", filter, &ndirs, false); + + FS_SortFileList(dirnames, ndirs); + for (int i = 0; i < ndirs; i++) + { + FS_ConvertPath(dirnames[i]); + Com_Printf("%s\n", dirnames[i]); + } + Com_Printf("%d files listed\n", ndirs); + FS_FreeFileList(dirnames); +} + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f(void) +{ + Com_Printf("We are looking in the current search path:\n"); + + for (auto s = fs_searchpaths; s; s = s->next) + { + if (s->pack) + { + Com_Printf("%s (%i files%s)\n", + s->pack->pakFilename, + s->pack->numfiles, + s->pack->onlyPrimary ? ", not for 1.1" + : s->pack->onlyAlternate ? ", only for 1.1" : ""); + + if (s->pack->primaryVersion) + Com_Printf(" (the 1.1 version of %s)\n", + s->pack->primaryVersion->pakFilename); + + if (fs_numServerPaks) + { + if (!s->pack->is_pure()) + { + Com_Printf(" not on the pure list\n"); + } + else + { + Com_Printf(" on the pure list\n"); + } + } + } + else + { + Com_Printf("%s%c%s\n", s->dir->path, PATH_SEP, s->dir->gamedir); + } + } + + Com_Printf("\n"); + for (int i = 1; i < MAX_FILE_HANDLES; i++) + { + if (fsh[i].handleFiles.file.o) + { + Com_Printf("handle %i: %s\n", i, fsh[i].name); + } + } +} + +/* +============ +FS_TouchFile_f +============ +*/ +void FS_TouchFile_f(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf("Usage: touchFile \n"); + return; + } + + fileHandle_t f; + FS_FOpenFileRead(Cmd_Argv(1), &f, false); + if (f) + { + FS_FCloseFile(f); + } +} + +/* +============ +FS_Which +============ +*/ + +bool FS_Which(const char *filename, void *searchPath) +{ + searchpath_t *search = static_cast(searchPath); + + if (FS_FOpenFileReadDir(filename, search, nullptr, false, false) > 0) + { + if (search->pack) + { + Com_Printf("File \"%s\" found in \"%s\"\n", filename, search->pack->pakFilename); + return true; + } + else if (search->dir) + { + Com_Printf("File \"%s\" found at \"%s\"\n", filename, search->dir->fullpath); + return true; + } + } + + return false; +} + +/* +============ +FS_Which_f +============ +*/ +void FS_Which_f(void) +{ + const char *filename = Cmd_Argv(1); + + if (!filename[0]) + { + Com_Printf("Usage: which \n"); + return; + } + + // qpaths are not supposed to have a leading slash + if (filename[0] == '/' || filename[0] == '\\') filename++; + + // just wants to see if file is there + for (searchpath_t *search = fs_searchpaths; search; search = search->next) + if (FS_Which(filename, search)) return; + + Com_Printf("File not found: \"%s\"\n", filename); +} + +//=========================================================================== + +static int QDECL paksort(const void *a, const void *b) +{ + char *aa = *(char **)a; + char *bb = *(char **)b; + + return FS_PathCmp(aa, bb); +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads the zip headers +================ +*/ +void FS_AddGameDirectory(const char *path, const char *dir) +{ + pack_t *pak; + char curpath[MAX_OSPATH + 1]; + int numfiles; + char **pakfiles; + int pakfilesi; + char **pakfilestmp; + int numdirs; + char **pakdirs; + int pakdirsi; + char **pakdirstmp; + + int lengths[10][2]; + + // Unique + for (searchpath_t *sp = fs_searchpaths; sp; sp = sp->next) + { + if (sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) + return; // we've already got this one + } + + Q_strncpyz(fs_gamedir, dir, sizeof(fs_gamedir)); + + // find all pak files in this directory + Q_strncpyz(curpath, FS_BuildOSPath(path, dir, ""), sizeof(curpath)); + curpath[strlen(curpath) - 1] = '\0'; // strip the trailing slash + + // Get .pk3 files + pakfiles = Sys_ListFiles(curpath, ".pk3", nullptr, &numfiles, false); + + qsort(pakfiles, numfiles, sizeof(char *), paksort); + + if (fs_numServerPaks) + { + numdirs = 0; + pakdirs = nullptr; + } + else + { + // Get top level directories (we'll filter them later since the Sys_ListFiles filtering is + // terrible) + pakdirs = Sys_ListFiles(curpath, "/", nullptr, &numdirs, false); + qsort(pakdirs, numdirs, sizeof(char *), paksort); + } + + char prefixBuf[MAX_STRING_CHARS]; + Q_strncpyz(prefixBuf, Cvar_VariableString("fs_pk3PrefixPairs"), sizeof(prefixBuf)); + int numPairs = 0; + + char *p = prefixBuf; + if (!p[0]) p = nullptr; + + const char *prefixes[10][2]; + while (p) + { + prefixes[numPairs][0] = p; + p = strchr(p, '&'); + if (!p) + { + Com_Printf(S_COLOR_YELLOW "WARNING: fs_pk3PrefixPairs ends with an incomplete pair\n"); + break; + } + lengths[numPairs][0] = (int)(p - prefixes[numPairs][0]); + *p++ = '\0'; + prefixes[numPairs][1] = p; + p = strchr(p, '|'); + if (p) + { + lengths[numPairs][1] = (int)(p - prefixes[numPairs][1]); + *p++ = '\0'; + } + else + { + lengths[numPairs][1] = (int)strlen(prefixes[numPairs][1]); + } + if (lengths[numPairs][0] == 0 && lengths[numPairs][1] == 0) + { + Com_Printf(S_COLOR_YELLOW + "WARNING: fs_pk3PrefixPairs contains a null-null pair, " + "skipping this pair\n"); + continue; + } + if (lengths[numPairs][0] != 0 && lengths[numPairs][1] != 0 && + !Q_stricmpn(prefixes[numPairs][0], prefixes[numPairs][1], + MIN(lengths[numPairs][0], lengths[numPairs][1]))) + { + Com_Printf(S_COLOR_YELLOW + "WARNING: in fs_pk3PrefixPairs, one of '%s' and '%s' is a real prefix " + "of the other, skipping this pair\n", + prefixes[numPairs][0], prefixes[numPairs][1]); + + continue; + } + ++numPairs; + } + searchpath_t *otherSearchpaths = fs_searchpaths; + + pakfilesi = 0; + pakdirsi = 0; + + while ((pakfilesi < numfiles) || (pakdirsi < numdirs)) + { + bool pakwhich; + // Check if a pakfile or pakdir comes next + if (pakfilesi >= numfiles) + { + pakwhich = false; // We've used all the pak files, it must be a pak directory. + } + else if (pakdirsi >= numdirs) + { + pakwhich = true; // We've used all the pak directories, it must be a pak file. + } + else + { + // Could be either, compare to see which name comes first + // Need tmp variables for appropriate indirection for paksort() + pakfilestmp = &pakfiles[pakfilesi]; + pakdirstmp = &pakdirs[pakdirsi]; + pakwhich = (paksort(pakfilestmp, pakdirstmp) < 0); + } + + if (pakwhich) + { + // The next .pk3 file is before the next .pk3dir + char *pakfile = FS_BuildOSPath(path, dir, pakfiles[pakfilesi]); + if ((pak = FS_LoadZipFile(pakfile, pakfiles[pakfilesi])) == 0) + { + // This isn't a .pk3! Next! + pakfilesi++; + continue; + } + + Q_strncpyz(pak->pakPathname, curpath, sizeof(pak->pakPathname)); + // store the game name for downloading + Q_strncpyz(pak->pakGamename, dir, sizeof(pak->pakGamename)); + + fs_packFiles += pak->numfiles; + + searchpath_t *search = static_cast(Z_Malloc(sizeof(searchpath_t))); + search->pack = pak; + search->next = fs_searchpaths; + fs_searchpaths = search; + + pak->onlyPrimary = false; + pak->onlyAlternate = false; + for (int i = 0; i < numPairs; ++i) + { + if (lengths[i][0] && !Q_stricmpn(pak->pakBasename, prefixes[i][0], lengths[i][0])) + { + pak->onlyPrimary = true; + break; + } + else if (lengths[i][1] && + !Q_stricmpn(pak->pakBasename, prefixes[i][1], lengths[i][1])) + { + pak->onlyAlternate = true; + break; + } + } + + pak->primaryVersion = nullptr; + pakfilesi++; + } + else + { + // The next .pk3dir is before the next .pk3 file + // But wait, this could be any directory, we're filtering to only ending with ".pk3dir" + // here. + int len = strlen(pakdirs[pakdirsi]); + if (!FS_IsExt(pakdirs[pakdirsi], ".pk3dir", len)) + { + // This isn't a .pk3dir! Next! + pakdirsi++; + continue; + } + + char *pakfile = FS_BuildOSPath(path, dir, pakdirs[pakdirsi]); + + // add the directory to the search path + searchpath_t *search = static_cast(Z_Malloc(sizeof(searchpath_t))); + search->dir = static_cast(Z_Malloc(sizeof(*search->dir))); + + Q_strncpyz(search->dir->path, curpath, sizeof(search->dir->path)); // c:\quake3\baseq3 + Q_strncpyz(search->dir->fullpath, pakfile, + sizeof(search->dir->fullpath)); // c:\quake3\baseq3\mypak.pk3dir + Q_strncpyz(search->dir->gamedir, pakdirs[pakdirsi], + sizeof(search->dir->gamedir)); // mypak.pk3dir + + search->next = fs_searchpaths; + fs_searchpaths = search; + + pakdirsi++; + } + } + + // done + Sys_FreeFileList(pakfiles); + Sys_FreeFileList(pakdirs); + + if (numPairs > 0) + { + int bnlengths[2]; + for (searchpath_t *search = fs_searchpaths; search != otherSearchpaths; + search = search->next) + { + if (!(search->pack && search->pack->onlyPrimary)) + { + continue; + } + + bnlengths[0] = (int)strlen(search->pack->pakBasename); + for (searchpath_t *srch = fs_searchpaths; srch != otherSearchpaths; srch = srch->next) + { + if (!(srch->pack && srch->pack->onlyAlternate)) + { + continue; + } + + bnlengths[1] = (int)strlen(srch->pack->pakBasename); + for (int i = 0; i < numPairs; ++i) + { + if (lengths[i][0] && lengths[i][1] && bnlengths[0] >= lengths[i][0] && + bnlengths[1] >= lengths[i][1] && + !Q_stricmp(search->pack->pakBasename + lengths[i][0], + srch->pack->pakBasename + lengths[i][1])) + { + srch->pack->primaryVersion = search->pack; + break; + } + } + } + } + } + + // + // add the directory to the search path + // + searchpath_t *search = static_cast(Z_Malloc(sizeof(searchpath_t))); + search->dir = static_cast(Z_Malloc(sizeof(*search->dir))); + + Q_strncpyz(search->dir->path, path, sizeof(search->dir->path)); + Q_strncpyz(search->dir->fullpath, curpath, sizeof(search->dir->fullpath)); + Q_strncpyz(search->dir->gamedir, dir, sizeof(search->dir->gamedir)); + + search->next = fs_searchpaths; + fs_searchpaths = search; +} + +/* +================ +FS_CheckDirTraversal + +Check whether the string contains stuff like "../" to prevent directory traversal bugs +and return true if it does. +================ +*/ + +bool FS_CheckDirTraversal(const char *checkdir) +{ + if (strstr(checkdir, "../") || strstr(checkdir, "..\\")) return true; + + return false; +} + +/* +================ +FS_ComparePaks + +---------------- +dlstring == true + +Returns a list of pak files that we should download from the server. They all get stored +in the current gamedir and an FS_Restart will be fired up after we download them all. + +The string is the format: + +@remotename@localname [repeat] + +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; + +---------------- +dlstring == false + +we are not interested in a download string format, we want something human-readable +(this is used for diagnostics while connecting to a pure server) + +================ +*/ +bool FS_ComparePaks(char *neededpaks, int len, bool dlstring) +{ + if (!fs_numServerReferencedPaks) + { + return false; // Server didn't send any pack information along + } + + char *origpos = neededpaks; + *neededpaks = '\0'; + + bool havepak = false; + for (int i = 0; i < fs_numServerReferencedPaks; i++) + { + // Ok, see if we have this pak file + havepak = false; + + // Make sure the server cannot make us write to non-quake3 directories. + if (FS_CheckDirTraversal(fs_serverReferencedPakNames[i])) + { + Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]); + continue; + } + + for (searchpath_t *sp = fs_searchpaths; sp; sp = sp->next) + { + if (sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i]) + { + havepak = true; // This is it! + break; + } + } + + if (!havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i]) + { + // Don't got it + if (dlstring) + { + // We need this to make sure we won't hit the end of the buffer or the server could + // overwrite non-pk3 files on clients by writing so much crap into neededpaks that + // Q_strcat cuts off the .pk3 extension. + + origpos += strlen(origpos); + + // Remote name + Q_strcat(neededpaks, len, "@"); + Q_strcat(neededpaks, len, fs_serverReferencedPakNames[i]); + Q_strcat(neededpaks, len, ".pk3"); + + // Local name + Q_strcat(neededpaks, len, "@"); + // Do we have one with the same name? + if (FS_SV_FileExists(va("%s.pk3", fs_serverReferencedPakNames[i]))) + { + char st[MAX_ZPATH]; + // We already have one called this, we need to download it to another name + // Make something up with the checksum in it + Com_sprintf(st, sizeof(st), "%s.%08x.pk3", fs_serverReferencedPakNames[i], + fs_serverReferencedPaks[i]); + Q_strcat(neededpaks, len, st); + } + else + { + Q_strcat(neededpaks, len, fs_serverReferencedPakNames[i]); + Q_strcat(neededpaks, len, ".pk3"); + } + + // Find out whether it might have overflowed the buffer and don't add this file to + // the + // list if that is the case. + if (strlen(origpos) + (origpos - neededpaks) >= (len - 1)) + { + *origpos = '\0'; + break; + } + } + else + { + Q_strcat(neededpaks, len, fs_serverReferencedPakNames[i]); + Q_strcat(neededpaks, len, ".pk3"); + // Do we have one with the same name? + if (FS_SV_FileExists(va("%s.pk3", fs_serverReferencedPakNames[i]))) + { + Q_strcat(neededpaks, len, " (local file exists with wrong checksum)"); + } + Q_strcat(neededpaks, len, "\n"); + } + } + } + + if (*neededpaks) + { + return true; + } + + return false; // We have them all +} + +/* +================ +FS_Shutdown + +Frees all resources. +================ +*/ + +void FS_Shutdown(bool closemfp) +{ + for (int i = 0; i < MAX_FILE_HANDLES; i++) + { + if (fsh[i].fileSize) FS_FCloseFile(i); + } + + searchpath_t *next; + // free everything + for (auto p = fs_searchpaths; p; p = next) + { + next = p->next; + if (p->pack) FS_FreePak(p->pack); + if (p->dir) Z_Free(p->dir); + Z_Free(p); + } + + // Any FS_ calls will now be an error until reinitialized + fs_searchpaths = nullptr; + + Cmd_RemoveCommand("path"); + Cmd_RemoveCommand("dir"); + Cmd_RemoveCommand("fdir"); + Cmd_RemoveCommand("touchFile"); + Cmd_RemoveCommand("which"); + +#ifdef FS_MISSING + if (closemfp) + { + fclose(missingFiles); + } +#endif +} + +/* +================ +FS_ReorderPurePaks +NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*) + this can lead to misleading situations, see +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +================ +*/ +static void FS_ReorderPurePaks(void) +{ + // do this before fs_numServerPaks check? + fs_reordered = false; + + // only relevant when connected to pure server + if (!fs_numServerPaks) return; + + // we insert in order at the beginning of the list + auto p_insert_index = &fs_searchpaths; + for (int i = 0; i < fs_numServerPaks; i++) + { + // track the pointer-to-current-item + auto p_previous = p_insert_index; + for (auto s = *p_insert_index; s; s = s->next) + { + // the part of the list before p_insert_index has been sorted already + if (s->pack && fs_serverPaks[i] == s->pack->checksum) + { + fs_reordered = true; + + // move this element to the insert list + *p_previous = s->next; + s->next = *p_insert_index; + *p_insert_index = s; + + // increment insert list + p_insert_index = &s->next; + + // iterate to next server pack + break; + } + p_previous = &s->next; + } + } +} + +/* +================ +FS_Startup +================ +*/ +static void FS_Startup(const char *gameName) +{ + Com_Printf("----- FS_Startup -----\n"); + fs_packFiles = 0; + + fs_debug = Cvar_Get("fs_debug", "0", 0); + fs_basepath = Cvar_Get("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT | CVAR_PROTECTED); + fs_basegame = Cvar_Get("fs_basegame", BASEGAME, CVAR_INIT); + + const char *homePath = Sys_DefaultHomePath(); + if (!homePath || !homePath[0]) + { + homePath = fs_basepath->string; + } + + fs_homepath = Cvar_Get("fs_homepath", homePath, CVAR_INIT | CVAR_PROTECTED); + fs_gamedirvar = Cvar_Get("fs_game", BASEGAME, CVAR_INIT | CVAR_SYSTEMINFO); + +#ifdef DEDICATED + // add search path elements in reverse priority order + if (fs_basepath->string[0]) + FS_AddGameDirectory(fs_basepath->string, gameName); + + // NOTE: same filtering below for mods and basegame + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) + { + FS_CreatePath(fs_homepath->string); + FS_AddGameDirectory(fs_homepath->string, gameName); + } + + // check for additional base game so mods can be based upon other mods + if ( fs_basegame->string[0] && Q_stricmp( fs_basegame->string, gameName ) ) + { + if (fs_basepath->string[0]) + FS_AddGameDirectory(fs_basepath->string, fs_basegame->string); + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) + FS_AddGameDirectory(fs_homepath->string, fs_basegame->string); + } + + // check for additional game folder for mods + if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, gameName ) ) + { + if (fs_basepath->string[0]) + FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) + FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); + } + +#else + + // add search path elements in reverse priority order + if (fs_basepath->string[0]) + { + FS_AddGameDirectory(fs_basepath->string, "base"); + } + +#ifdef __APPLE__ + // Make MacOSX also include the base path included with the .app bundle + fs_apppath = Cvar_Get("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT | CVAR_PROTECTED); + if (fs_apppath->string[0]) + { + FS_AddGameDirectory(fs_apppath->string, "base"); + } +#endif + + // NOTE: same filtering below for mods and basegame + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string)) + { + FS_CreatePath(fs_homepath->string); + FS_AddGameDirectory(fs_homepath->string, "base"); + } + + // check for additional base game so mods can be based upon other mods + if (fs_basegame->string[0] && Q_stricmp(fs_basegame->string, gameName)) + { + if (fs_basepath->string[0]) + FS_AddGameDirectory(fs_basepath->string, fs_basegame->string); + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string)) + FS_AddGameDirectory(fs_homepath->string, fs_basegame->string); + } + + // check for additional game folder for mods + if (fs_gamedirvar->string[0] && Q_stricmp(fs_gamedirvar->string, gameName)) + { + if (fs_basepath->string[0]) + FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string)) + FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); + } + + // NOTE: same filtering below for mods and basegame + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string, fs_basepath->string)) + { + FS_CreatePath(fs_homepath->string); + FS_AddGameDirectory(fs_homepath->string, gameName); + } + + // add search path elements in reverse priority order + if (fs_basepath->string[0]) + { + FS_AddGameDirectory(fs_basepath->string, gameName); + } + +#ifdef __APPLE__ + // Make MacOSX also include the base path included with the .app bundle + fs_apppath = Cvar_Get("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT | CVAR_PROTECTED); + if (fs_apppath->string[0]) + { + FS_AddGameDirectory(fs_apppath->string, gameName); + } +#endif +#endif + + // add our commands + Cmd_AddCommand("path", FS_Path_f); + Cmd_AddCommand("dir", FS_Dir_f); + Cmd_AddCommand("fdir", FS_NewDir_f); + Cmd_AddCommand("touchFile", FS_TouchFile_f); + Cmd_AddCommand("which", FS_Which_f); + + // reorder the pure pk3 files according to server order + FS_ReorderPurePaks(); + + // print the current search paths + FS_Path_f(); + + // We just loaded, it's not modified + fs_gamedirvar->modified = false; + + Com_Printf("----------------------\n"); + +#ifdef FS_MISSING + if (missingFiles == nullptr) + { + missingFiles = Sys_FOpen("\\missing.txt", "ab"); + } +#endif + + Com_Printf("%d files in pk3 files\n", fs_packFiles); +} + +/* +===================== +FS_LoadedPakChecksums + +Returns a space separated string containing the checksums of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakChecksums(bool alternate) +{ + static char info[BIG_INFO_STRING]; + info[0] = 0; + + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file? + if (!search->pack) continue; + if ((alternate && search->pack->onlyPrimary) || (!alternate && search->pack->onlyAlternate)) + continue; + Q_strcat(info, sizeof(info), va("%i ", search->pack->checksum)); + } + + return info; +} + +/* +===================== +FS_LoadedPakNames + +Returns a space separated string containing the names of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakNames(bool alternate) +{ + static char info[BIG_INFO_STRING]; + info[0] = 0; + + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file? + if (!search->pack) continue; + if ((alternate && search->pack->onlyPrimary) || (!alternate && search->pack->onlyAlternate)) + continue; + if (info[0]) Q_strcat(info, sizeof(info), " "); + Q_strcat(info, sizeof(info), search->pack->pakBasename); + } + + return info; +} + +/* +===================== +FS_LoadedPakPureChecksums + +Returns a space separated string containing the pure checksums of all loaded pk3 files. +Servers with sv_pure use these checksums to compare with the checksums the clients send +back to the server. +===================== +*/ +const char *FS_LoadedPakPureChecksums(bool alternate) +{ + static char info[BIG_INFO_STRING]; + info[0] = 0; + + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file? + if (!search->pack) continue; + if ((alternate && search->pack->onlyPrimary) || (!alternate && search->pack->onlyAlternate)) + continue; + Q_strcat(info, sizeof(info), va("%i ", search->pack->pure_checksum)); + } + + return info; +} + +/* +===================== +FS_ReferencedPakChecksums + +Returns a space separated string containing the checksums of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakChecksums(bool alternate) +{ + static char info[BIG_INFO_STRING]; + info[0] = 0; + + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file? + if (search->pack) + { + if ((alternate and search->pack->onlyPrimary) or + (!alternate and search->pack->onlyAlternate)) + continue; + + if (search->pack->referenced or + (search->pack->primaryVersion and search->pack->primaryVersion->referenced) or + (*fs_gamedirvar->string and Q_stricmp(fs_gamedirvar->string, BASEGAME) and + Q_stricmp(search->pack->pakGamename, fs_gamedirvar->string) == 0)) + { + Q_strcat(info, sizeof(info), va("%i ", search->pack->checksum)); + } + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakPureChecksums + +Returns a space separated string containing the pure checksums of all referenced pk3 files. +Servers with sv_pure set will get this string back from clients for pure validation + +The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." +===================== +*/ +const char *FS_ReferencedPakPureChecksums(void) +{ + static char info[BIG_INFO_STRING]; + info[0] = 0; + + int checksum = fs_checksumFeed; + int numPaks = 0; + for (int nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) + { + if (nFlags & FS_GENERAL_REF) + { + // add a delimter between must haves and general refs + // Q_strcat(info, sizeof(info), "@ "); + info[strlen(info) + 1] = '\0'; + info[strlen(info) + 2] = '\0'; + info[strlen(info)] = '@'; + info[strlen(info)] = ' '; + } + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file and has it been referenced based on flag? + if (search->pack && (search->pack->referenced & nFlags)) + { + Q_strcat(info, sizeof(info), va("%i ", search->pack->pure_checksum)); + if (nFlags & (FS_CGAME_REF | FS_UI_REF)) break; + + checksum ^= search->pack->pure_checksum; + numPaks++; + } + } + } + // last checksum is the encoded number of referenced pk3s + checksum ^= numPaks; + Q_strcat(info, sizeof(info), va("%i ", checksum)); + + return info; +} + +/* +===================== +FS_ReferencedPakNames + +Returns a space separated string containing the names of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakNames(bool alternate) +{ + static char info[BIG_INFO_STRING]; + info[0] = 0; + + // we want to return ALL pk3's from the fs_game path + // and referenced one's from base + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file? + if (search->pack) + { + if ((alternate && search->pack->onlyPrimary) || + (!alternate && search->pack->onlyAlternate)) + continue; + + if (search->pack->referenced || + (search->pack->primaryVersion && search->pack->primaryVersion->referenced) || + (fs_gamedirvar->string[0] && Q_stricmp(fs_gamedirvar->string, BASEGAME) && + !Q_stricmp(search->pack->pakGamename, fs_gamedirvar->string))) + { + if (*info) Q_strcat(info, sizeof(info), " "); + + Q_strcat(info, sizeof(info), search->pack->pakGamename); + Q_strcat(info, sizeof(info), "/"); + Q_strcat(info, sizeof(info), search->pack->pakBasename); + } + } + } + + return info; +} + +/* +===================== +FS_ClearPakReferences +===================== +*/ +void FS_ClearPakReferences(int flags) +{ + if (!flags) flags = -1; + + for (auto search = fs_searchpaths; search; search = search->next) + { + // is the element a pak file and has it been referenced? + if (search->pack) search->pack->referenced &= ~flags; + } +} + +/* +===================== +FS_PureServerSetLoadedPaks + +If the string is empty, all data sources will be allowed. +If not empty, only pk3 files that match one of the space +separated checksums will be checked for files, with the +exception of .cfg and .dat files. +===================== +*/ +void FS_PureServerSetLoadedPaks(const char *pakSums, const char *pakNames) +{ + Cmd_TokenizeString(pakSums); + + int c = Cmd_Argc(); + if (c > MAX_SEARCH_PATHS) c = MAX_SEARCH_PATHS; + + fs_numServerPaks = c; + + for (int i = 0; i < c; i++) fs_serverPaks[i] = atoi(Cmd_Argv(i)); + + if (fs_numServerPaks) + { + Com_DPrintf("Connected to a pure server.\n"); + } + else if (fs_reordered) + { + // force a restart to make sure the search order will be correct + Com_DPrintf("FS search reorder is required\n"); + FS_Restart(fs_checksumFeed); + return; + } + + for (int i = 0; i < c; i++) + { + if (fs_serverPakNames[i]) Z_Free(fs_serverPakNames[i]); + fs_serverPakNames[i] = nullptr; + } + + if (pakNames && pakNames[0]) + { + Cmd_TokenizeString(pakNames); + + int d = Cmd_Argc(); + if (d > MAX_SEARCH_PATHS) d = MAX_SEARCH_PATHS; + + for (int i = 0; i < d; i++) fs_serverPakNames[i] = CopyString(Cmd_Argv(i)); + } +} + +/* +===================== +FS_PureServerSetReferencedPaks + +The checksums and names of the pk3 files referenced at the server +are sent to the client and stored here. The client will use these +checksums to see if any pk3 files need to be auto-downloaded. +===================== +*/ +void FS_PureServerSetReferencedPaks(const char *pakSums, const char *pakNames) +{ + Cmd_TokenizeString(pakSums); + + unsigned c = Cmd_Argc(); + if (c > MAX_SEARCH_PATHS) c = MAX_SEARCH_PATHS; + + for (unsigned i = 0; i < c; i++) fs_serverReferencedPaks[i] = atoi(Cmd_Argv(i)); + + for (unsigned i = 0; i < ARRAY_LEN(fs_serverReferencedPakNames); i++) + { + if (fs_serverReferencedPakNames[i]) Z_Free(fs_serverReferencedPakNames[i]); + fs_serverReferencedPakNames[i] = nullptr; + } + + unsigned d = 0; + if (pakNames && *pakNames) + { + Cmd_TokenizeString(pakNames); + + d = Cmd_Argc(); + if (d > c) d = c; + + for (unsigned i = 0; i < d; i++) fs_serverReferencedPakNames[i] = CopyString(Cmd_Argv(i)); + } + + // ensure that there are as many checksums as there are pak names. + if (d < c) c = d; + + fs_numServerReferencedPaks = c; +} + +/* +================ +FS_InitFilesystem + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void FS_InitFilesystem(void) +{ + // allow command line parms to override our defaults + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + Com_StartupVariable("fs_basepath"); + Com_StartupVariable("fs_homepath"); + Com_StartupVariable("fs_game"); + Com_StartupVariable("fs_pk3PrefixPairs"); + + if (!FS_FilenameCompare(Cvar_VariableString("fs_game"), BASEGAME)) Cvar_Set("fs_game", ""); + + // try to start up normally + FS_Startup(BASEGAME); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if (FS_ReadFile("default.cfg", nullptr) <= 0) + { + Com_Error(ERR_FATAL, "Couldn't load default.cfg"); + } + + Q_strncpyz(lastValidBase, fs_basegame->string, sizeof(lastValidBase)); + Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); +} + +/* +================ +FS_Restart +================ +*/ +void FS_Restart(int checksumFeed) +{ + // free anything we currently have loaded + FS_Shutdown(false); + + // set the checksum feed + fs_checksumFeed = checksumFeed; + + // clear pak references + FS_ClearPakReferences(0); + + // try to start up normally + FS_Startup(BASEGAME); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if (FS_ReadFile("default.cfg", nullptr) <= 0) + { + // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3 + // (for instance a TA demo server) + if (lastValidBase[0]) + { + FS_PureServerSetLoadedPaks("", ""); + Cvar_Set("fs_basegame", lastValidBase); + Cvar_Set("fs_game", lastValidGame); + lastValidBase[0] = lastValidGame[0] = '\0'; + FS_Restart(checksumFeed); + Com_Error(ERR_DROP, "Invalid game folder"); + return; + } + Com_Error(ERR_FATAL, "Couldn't load default.cfg"); + } + + if (Q_stricmp(fs_gamedirvar->string, lastValidGame)) + { + // skip the autogen.cfg if "safe" is on the command line + if (!Com_SafeMode()) + { + Cbuf_AddText("exec " Q3CONFIG_CFG "\n"); + } + } + + Q_strncpyz(lastValidBase, fs_basegame->string, sizeof(lastValidBase)); + Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); +} + +/* +================= +FS_ConditionalRestart + +Restart if necessary +Return true if restarting due to game directory changed, false otherwise +================= +*/ +bool FS_ConditionalRestart(int checksumFeed, bool disconnect) +{ + if (fs_gamedirvar->modified) + { + if (FS_FilenameCompare(lastValidGame, fs_gamedirvar->string) && + (*lastValidGame || FS_FilenameCompare(fs_gamedirvar->string, BASEGAME)) && + (*fs_gamedirvar->string || FS_FilenameCompare(lastValidGame, BASEGAME))) + { + Com_GameRestart(checksumFeed, disconnect); + return true; + } + fs_gamedirvar->modified = false; + } + + if (checksumFeed != fs_checksumFeed) + FS_Restart(checksumFeed); + + else if (fs_numServerPaks && !fs_reordered) + FS_ReorderPurePaks(); + + return false; +} + +/* +======================================================================================== + +Handle based file calls for virtual machines + +======================================================================================== +*/ + +int FS_FOpenFileByMode(const char *qpath, fileHandle_t *f, enum FS_Mode mode) +{ + int r; + bool sync = false; + + switch (mode) + { + case FS_READ: + r = FS_FOpenFileRead(qpath, f, true); + break; + + case FS_WRITE: + *f = FS_FOpenFileWrite(qpath); + r = 0; + if (*f == 0) r = -1; + break; + + case FS_APPEND_SYNC: + sync = true; + // fall through + + case FS_APPEND: + *f = FS_FOpenFileAppend(qpath); + r = 0; + if (*f == 0) r = -1; + break; + + default: + Com_Error(ERR_FATAL, "FS_FOpenFileByMode: bad mode"); + return -1; + } + + if (!f) return r; + + if (*f) + { + fsh[*f].fileSize = r; + } + fsh[*f].handleSync = sync; + + return r; +} + +int FS_FTell(fileHandle_t f) +{ + if (fsh[f].zipFile == true) return unztell(fsh[f].handleFiles.file.z); + return ftell(fsh[f].handleFiles.file.o); +} + +void FS_Flush(fileHandle_t f) +{ + fflush(fsh[f].handleFiles.file.o); +} + +void FS_FilenameCompletion(const char *dir, const char *ext, bool stripExt, + void (*callback)(const char *s), bool allowNonPureFilesOnDisk) +{ + int nfiles; + char filename[MAX_STRING_CHARS]; + char **filenames = FS_ListFilteredFiles(dir, ext, nullptr, &nfiles, allowNonPureFilesOnDisk); + + FS_SortFileList(filenames, nfiles); + + for (int i = 0; i < nfiles; i++) + { + FS_ConvertPath(filenames[i]); + Q_strncpyz(filename, filenames[i], MAX_STRING_CHARS); + if (stripExt) COM_StripExtension(filename, filename, sizeof(filename)); + callback(filename); + } + FS_FreeFileList(filenames); +} + +const char *FS_GetCurrentGameDir(void) +{ + if (fs_gamedirvar->string[0]) return fs_gamedirvar->string; + return BASEGAME; +} diff --git a/src/qcommon/files.h b/src/qcommon/files.h new file mode 100644 index 0000000..d54bdf4 --- /dev/null +++ b/src/qcommon/files.h @@ -0,0 +1,286 @@ +/* + * This file is part of Tremulous. + * Copyright © 2016 Victor Roemer (blowfish) + * Copyright (C) 2015-2019 GrangerHub + * + * 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 3 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, see . + */ + +#ifndef QC_FILES_H +#define QC_FILES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "q_platform.h" +#include "q_shared.h" + +// referenced flags +// these are in loop specific order so don't change the order +#define FS_GENERAL_REF 0x01 +#define FS_UI_REF 0x02 +#define FS_CGAME_REF 0x04 + +#define MAX_FILE_HANDLES 64 + +#define BASEGAME "gpp" + +#ifdef DEDICATED +#define Q3CONFIG_CFG "autogen_server.cfg" +#else +#define Q3CONFIG_CFG "autogen.cfg" +#endif + +/* + ============================================================= + + QUAKE3 FILESYSTEM + + All of Quake's data access is through a hierarchical file system, but the contents of + the file system can be transparently merged from several sources. + + A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include + a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any + references outside the quake directory system. + + The "base path" is the path to the directory holding all the game directories and usually + the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3" + command line to allow code debugging in a different directory. Basepath cannot + be modified at all after startup. Any files that are created (demos, screenshots, + etc) will be created relative to the base path, so base path should usually be writable. + + The "home path" is the path used for all write access. On win32 systems we have "base path" + == "home path", but on *nix systems the base installation is usually readonly, and + "home path" points to ~/.q3a or similar + + The user can also install custom mods and content in "home path", so it should be searched + along with "home path" and "cd path" for game content. + + + The "base game" is the directory under the paths where data comes from by default, and + can be "base". + + The "current game" may be the same as the base game, or it may be the name of another + directory under the paths that should be searched for files before looking in the base game. + This is the basis for addons. + + Clients automatically set the game directory after receiving a gamestate from a server, + so only servers need to worry about +set fs_game. + + No other directories outside of the base game and current game will ever be referenced by + filesystem functions. + + To save disk space and speed loading, directory trees can be collapsed into zip files. + The files use a ".pk3" extension to prevent users from unzipping them accidentally, but + otherwise the are simply normal uncompressed zip files. A game directory can have multiple + zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order + from the highest number to the lowest, and will always take precedence over the filesystem. + This allows a pk3 distributed as a patch to override all existing data. + + Because we will have updated executables freely available online, there is no point to + trying to restrict demo / oem versions of the game with code changes. Demo / oem versions + should be exactly the same executables as release versions, but with different data that + automatically restricts where game media can come from to prevent add-ons from working. + + File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths + structure and stop on the first successful hit. fs_searchpaths is built with successive + calls to FS_AddGameDirectory + + Additionaly, we search in several subdirectories: + current game is the current mode + base game is a variable to allow mods based on other mods + (such as base + missionpack content combination in a mod for instance) + BASEGAME is the hardcoded base game ("base") + + e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places: + + home path + current game's zip files + home path + current game's directory + base path + current game's zip files + base path + current game's directory + cd path + current game's zip files + cd path + current game's directory + + home path + base game's zip file + home path + base game's directory + base path + base game's zip file + base path + base game's directory + cd path + base game's zip file + cd path + base game's directory + + home path + BASEGAME's zip file + home path + BASEGAME's directory + base path + BASEGAME's zip file + base path + BASEGAME's directory + cd path + BASEGAME's zip file + cd path + BASEGAME's directory + + server download, to be written to home path + current game's directory + + + The filesystem can be safely shutdown and reinitialized with different + basedir / cddir / game combinations, but all other subsystems that rely on it + (sound, video) must also be forced to restart. + + Because the same files are loaded by both the clip model (CM_) and renderer (TR_) + subsystems, a simple single-file caching scheme is used. The CM_ subsystems will + load the file with a request to cache. Only one file will be kept cached at a time, + so any models that are going to be referenced by both subsystems should alternate + between the CM_ load function and the ref load function. + + TODO: A qpath that starts with a leading slash will always refer to the base game, even if another + game is currently active. This allows character models, skins, and sounds to be downloaded + to a common directory no matter which game is active. + + How to prevent downloading zip files? + Pass pk3 file names in systeminfo, and download before FS_Restart (void)? + + Aborting a download disconnects the client from the server. + + How to mark files as downloadable? Commercial add-ons won't be downloadable. + + Non-commercial downloads will want to download the entire zip file. + the game would have to be reset to actually read the zip in + + Auto-update information + + Path separators + + Casing + + separate server gamedir and client gamedir, so if the user starts + a local game after having connected to a network game, it won't stick + with the network game. + + allow menu options for game selection? + + Read / write config to floppy option. + + Different version coexistance? + + When building a pak file, make sure a autogen.cfg isn't present in it, + or configs will never get loaded from disk! + + todo: + + downloading (outside fs?) + game directory passing and restarting + + ============================================================================= +*/ + +//enum FS_Mode { +// FS_READ, +// FS_WRITE, +// FS_APPEND, +// FS_APPEND_SYNC +//}; +// +//enum FS_Origin { +// FS_SEEK_CUR, +// FS_SEEK_END, +// FS_SEEK_SET +//}; + +const char* FS_GetCurrentGameDir (void); +void FS_FilenameCompletion (const char* dir, const char* ext, bool stripExt, void (* callback)(const char* s), bool allowNonPureFilesOnDisk); +int FS_FOpenFileByMode (const char* qpath, fileHandle_t* f, enum FS_Mode mode); +bool FS_ConditionalRestart (int checksumFeed, bool disconnect); +void FS_InitFilesystem (void); +void FS_PureServerSetReferencedPaks (const char* pakSums, const char* pakNames); +void FS_Restart (int checksumFeed); +void FS_PureServerSetLoadedPaks (const char* pakSums, const char* pakNames); +void FS_ClearPakReferences (int flags); +const char* FS_ReferencedPakNames (bool alternate); +const char* FS_ReferencedPakPureChecksums (void); +const char* FS_ReferencedPakChecksums (bool alternate); +const char* FS_LoadedPakPureChecksums (bool alternate); +const char* FS_LoadedPakNames (bool alternate); +const char* FS_LoadedPakChecksums (bool alternate); +void FS_Shutdown (bool closemfp); +bool FS_ComparePaks (char* neededpaks, int len, bool dlstring); +bool FS_CheckDirTraversal (const char* checkdir); +void FS_AddGameDirectory (const char* path, const char* dir); +bool FS_Which (const char* filename, void* searchPath); +void FS_SortFileList (char** filelist, int numfiles); +int FS_PathCmp (const char* s1, const char* s2); +void FS_ConvertPath (char* s); +int FS_GetModList (char* listbuf, int bufsize); +int FS_GetFileList (const char* path, const char* extension, char* listbuf, int bufsize); +void FS_FreeFileList (char** list); +int FS_GetFilteredFiles (const char *path, const char *extension, const char *filter, char *listbuf, int bufsize); +char** FS_ListFiles (const char* path, const char* extension, int* numfiles); +char** FS_ListFilteredFiles (const char* path, const char* extension, const char* filter, int* numfiles, bool allowNonPureFilesOnDisk); +bool FS_CompareZipChecksum (const char* zipfile); +void FS_WriteFile (const char* qpath, const void* buffer, int size); +void FS_FreeFile (void* buffer); +long FS_ReadFile (const char* qpath, void** buffer); +void FS_Flush (fileHandle_t f); +long FS_ReadFileDir (const char* qpath, void* searchPath, bool unpure, void** buffer); +int FS_FileIsInPAK_A(bool alternate, const char *filename, int *pChecksum); +int FS_FileIsInPAK (const char* filename, int* pChecksum); +int FS_FTell (fileHandle_t f); +int FS_Seek (fileHandle_t f, long offset, enum FS_Origin origin); +void QDECL FS_Printf (fileHandle_t h, const char* fmt, ...); +int FS_Write (const void* buffer, int len, fileHandle_t h); +int FS_Read (void* buffer, int len, fileHandle_t f); +int FS_Read (void* buffer, int len, fileHandle_t f); +int FS_FindVM (void** startSearch, char* found, int foundlen, const char* name, int enableDll); +long FS_FOpenFileRead (const char* filename, fileHandle_t* file, bool uniqueFILE); +long FS_FOpenFileReadDir (const char* filename, void* search, fileHandle_t* file, bool uniqueFILE, bool unpure); +bool FS_FilenameCompare (const char* s1, const char* s2); +fileHandle_t FS_FCreateOpenPipeFile (const char* filename); +fileHandle_t FS_FOpenFileAppend (const char* filename); +fileHandle_t FS_FOpenFileWrite (const char* filename); +void FS_FCloseFile (fileHandle_t f); +void FS_Rename (const char* from, const char* to); +void FS_SV_Rename (const char* from, const char* to, bool safe); +long FS_SV_FOpenFileRead (const char* filename, fileHandle_t* fp); +fileHandle_t FS_SV_FOpenFileWrite (const char* filename); +bool FS_SV_FileExists (const char* file); +bool FS_FileExists (const char* file); +bool FS_FileInPathExists (const char* testpath); +void FS_HomeRemove (const char* homePath); +void FS_Remove (const char* osPath); +bool FS_BrowseHomepath ( void ); +bool FS_OpenBaseGamePath( const char *baseGamePath ); +bool FS_CreatePath (const char* OSPath); +char* FS_BuildOSPath (const char* base, const char* game, const char* qpath); +long FS_filelength (fileHandle_t f); +void FS_ReplaceSeparators (char *path); +void FS_ForceFlush (fileHandle_t f); +int FS_LoadStack (void); +bool FS_Initialized (void); + +void FS_Which_f (void); +void FS_TouchFile_f (void); +void FS_Path_f (void); +void FS_NewDir_f (void); +void FS_Dir_f (void); + + +// XXX Delete me. +#if defined (FS_MISSING) +extern FILE *missingFiles; +#endif + +extern char lastValidGame[MAX_OSPATH]; +extern char lastValidBase[MAX_OSPATH]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/qcommon/huffman.cpp b/src/qcommon/huffman.cpp new file mode 100644 index 0000000..4cb0d8b --- /dev/null +++ b/src/qcommon/huffman.cpp @@ -0,0 +1,558 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#include "huffman.h" + +#include "alternatePlayerstate.h" +#include "cvar.h" +#include "msg.h" +#include "q_shared.h" +#include "qcommon.h" + +static int bloc = 0; + +void Huff_putBit(int bit, uint8_t *fout, int *offset) +{ + bloc = *offset; + if ((bloc & 7) == 0) + { + fout[(bloc >> 3)] = 0; + } + fout[(bloc >> 3)] |= bit << (bloc & 7); + bloc++; + *offset = bloc; +} + +int Huff_getBloc(void) { return bloc; } +void Huff_setBloc(int _bloc) { bloc = _bloc; } +int Huff_getBit(uint8_t *fin, int *offset) +{ + int t; + bloc = *offset; + t = (fin[(bloc >> 3)] >> (bloc & 7)) & 0x1; + bloc++; + *offset = bloc; + return t; +} + +/* Add a bit to the output file (buffered) */ +static void add_bit(char bit, uint8_t *fout) +{ + if ((bloc & 7) == 0) + { + fout[(bloc >> 3)] = 0; + } + fout[(bloc >> 3)] |= bit << (bloc & 7); + bloc++; +} + +/* Receive one bit from the input file (buffered) */ +static int get_bit(uint8_t *fin) +{ + int t; + t = (fin[(bloc >> 3)] >> (bloc & 7)) & 0x1; + bloc++; + return t; +} + +static node_t **get_ppnode(huff_t *huff) +{ + node_t **tppnode; + if (!huff->freelist) + { + return &(huff->nodePtrs[huff->blocPtrs++]); + } + else + { + tppnode = huff->freelist; + huff->freelist = (node_t **)*tppnode; + return tppnode; + } +} + +static void free_ppnode(huff_t *huff, node_t **ppnode) +{ + *ppnode = (node_t *)huff->freelist; + huff->freelist = ppnode; +} + +/* Swap the location of these two nodes in the tree */ +static void swap(huff_t *huff, node_t *node1, node_t *node2) +{ + node_t *par1, *par2; + + par1 = node1->parent; + par2 = node2->parent; + + if (par1) + { + if (par1->left == node1) + { + par1->left = node2; + } + else + { + par1->right = node2; + } + } + else + { + huff->tree = node2; + } + + if (par2) + { + if (par2->left == node2) + { + par2->left = node1; + } + else + { + par2->right = node1; + } + } + else + { + huff->tree = node1; + } + + node1->parent = par2; + node2->parent = par1; +} + +/* Swap these two nodes in the linked list (update ranks) */ +static void swaplist(node_t *node1, node_t *node2) +{ + node_t *par1; + + par1 = node1->next; + node1->next = node2->next; + node2->next = par1; + + par1 = node1->prev; + node1->prev = node2->prev; + node2->prev = par1; + + if (node1->next == node1) + { + node1->next = node2; + } + if (node2->next == node2) + { + node2->next = node1; + } + if (node1->next) + { + node1->next->prev = node1; + } + if (node2->next) + { + node2->next->prev = node2; + } + if (node1->prev) + { + node1->prev->next = node1; + } + if (node2->prev) + { + node2->prev->next = node2; + } +} + +/* Do the increments */ +static void increment(huff_t *huff, node_t *node) +{ + node_t *lnode; + + if (!node) + { + return; + } + + if (node->next != NULL && node->next->weight == node->weight) + { + lnode = *node->head; + if (lnode != node->parent) + { + swap(huff, lnode, node); + } + swaplist(lnode, node); + } + if (node->prev && node->prev->weight == node->weight) + { + *node->head = node->prev; + } + else + { + *node->head = NULL; + free_ppnode(huff, node->head); + } + node->weight++; + if (node->next && node->next->weight == node->weight) + { + node->head = node->next->head; + } + else + { + node->head = get_ppnode(huff); + *node->head = node; + } + if (node->parent) + { + increment(huff, node->parent); + if (node->prev == node->parent) + { + swaplist(node, node->parent); + if (*node->head == node) + { + *node->head = node->parent; + } + } + } +} + +void Huff_addRef(huff_t *huff, uint8_t ch) +{ + node_t *tnode, *tnode2; + if (huff->loc[ch] == NULL) + { /* if this is the first transmission of this node */ + tnode = &(huff->nodeList[huff->blocNode++]); + tnode2 = &(huff->nodeList[huff->blocNode++]); + + tnode2->symbol = INTERNAL_NODE; + tnode2->weight = 1; + tnode2->next = huff->lhead->next; + if (huff->lhead->next) + { + huff->lhead->next->prev = tnode2; + if (huff->lhead->next->weight == 1) + { + tnode2->head = huff->lhead->next->head; + } + else + { + tnode2->head = get_ppnode(huff); + *tnode2->head = tnode2; + } + } + else + { + tnode2->head = get_ppnode(huff); + *tnode2->head = tnode2; + } + huff->lhead->next = tnode2; + tnode2->prev = huff->lhead; + + tnode->symbol = ch; + tnode->weight = 1; + tnode->next = huff->lhead->next; + if (huff->lhead->next) + { + huff->lhead->next->prev = tnode; + if (huff->lhead->next->weight == 1) + { + tnode->head = huff->lhead->next->head; + } + else + { + /* this should never happen */ + tnode->head = get_ppnode(huff); + *tnode->head = tnode2; + } + } + else + { + /* this should never happen */ + tnode->head = get_ppnode(huff); + *tnode->head = tnode; + } + huff->lhead->next = tnode; + tnode->prev = huff->lhead; + tnode->left = tnode->right = NULL; + + if (huff->lhead->parent) + { + if (huff->lhead->parent->left == huff->lhead) + { /* lhead is guaranteed to by the NYT */ + huff->lhead->parent->left = tnode2; + } + else + { + huff->lhead->parent->right = tnode2; + } + } + else + { + huff->tree = tnode2; + } + + tnode2->right = tnode; + tnode2->left = huff->lhead; + + tnode2->parent = huff->lhead->parent; + huff->lhead->parent = tnode->parent = tnode2; + + huff->loc[ch] = tnode; + + increment(huff, tnode2->parent); + } + else + { + increment(huff, huff->loc[ch]); + } +} + +/* Get a symbol */ +int Huff_Receive(node_t *node, int *ch, uint8_t *fin) +{ + while (node && node->symbol == INTERNAL_NODE) + { + if (get_bit(fin)) + { + node = node->right; + } + else + { + node = node->left; + } + } + if (!node) + { + return 0; + // Com_Error(ERR_DROP, "Illegal tree!"); + } + return (*ch = node->symbol); +} + +/* Get a symbol */ +void Huff_offsetReceive(node_t *node, int *ch, uint8_t *fin, int *offset, int maxoffset) +{ + bloc = *offset; + while (node && node->symbol == INTERNAL_NODE) + { + if ( bloc >= maxoffset ) + { + *ch = 0; + *offset = maxoffset + 1; + return; + } + + if (get_bit(fin)) + { + node = node->right; + } + else + { + node = node->left; + } + } + if (!node) + { + *ch = 0; + return; + // Com_Error(ERR_DROP, "Illegal tree!"); + } + *ch = node->symbol; + *offset = bloc; +} + +/* Send the prefix code for this node */ +static void send(node_t *node, node_t *child, uint8_t *fout, int maxoffset) +{ + if (node->parent) + { + send(node->parent, node, fout, maxoffset); + } + if (child) + { + if (bloc >= maxoffset) + { + bloc = maxoffset + 1; + return; + } + + if (node->right == child) + { + add_bit(1, fout); + } + else + { + add_bit(0, fout); + } + } +} + +/* Send a symbol */ +void Huff_transmit(huff_t *huff, int ch, uint8_t *fout, int maxoffset) +{ + int i; + if (huff->loc[ch] == NULL) + { + /* node_t hasn't been transmitted, send a NYT, then the symbol */ + Huff_transmit(huff, NYT, fout, maxoffset); + for (i = 7; i >= 0; i--) + { + add_bit((char)((ch >> i) & 0x1), fout); + } + } + else + { + send(huff->loc[ch], NULL, fout, maxoffset); + } +} + +void Huff_offsetTransmit(huff_t *huff, int ch, uint8_t *fout, int *offset, int maxoffset) +{ + bloc = *offset; + send(huff->loc[ch], NULL, fout, maxoffset); + *offset = bloc; +} + +void Huff_Decompress(struct msg_t *mbuf, int offset) +{ + int ch, cch, i, j, size; + uint8_t seq[65536]; + uint8_t *buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data + offset; + + if (size <= 0) + { + return; + } + + memset(&huff, 0, sizeof(huff_t)); + // Initialize the tree & list with the NYT node + huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + + cch = buffer[0] * 256 + buffer[1]; + // don't overflow with bad messages + if (cch > mbuf->maxsize - offset) + { + cch = mbuf->maxsize - offset; + } + bloc = 16; + + for (j = 0; j < cch; j++) + { + ch = 0; + // don't overflow reading from the messages + // FIXME: would it be better to have an overflow check in get_bit ? + if ((bloc >> 3) > size) + { + seq[j] = 0; + break; + } + Huff_Receive(huff.tree, &ch, buffer); /* Get a character */ + if (ch == NYT) + { /* We got a NYT, get the symbol associated with it */ + ch = 0; + for (i = 0; i < 8; i++) + { + ch = (ch << 1) + get_bit(buffer); + } + } + + seq[j] = ch; /* Write symbol */ + + Huff_addRef(&huff, (uint8_t)ch); /* Increment node */ + } + mbuf->cursize = cch + offset; + memcpy(mbuf->data + offset, seq, cch); +} + +extern int oldsize; + +void Huff_Compress(struct msg_t *mbuf, int offset) +{ + int i, ch, size; + uint8_t seq[65536]; + uint8_t *buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data + +offset; + + if (size <= 0) + { + return; + } + + memset(&huff, 0, sizeof(huff_t)); + // Add the NYT (not yet transmitted) node into the tree/list */ + huff.tree = huff.lhead = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + + seq[0] = (size >> 8); + seq[1] = size & 0xff; + + bloc = 16; + + for (i = 0; i < size; i++) + { + ch = buffer[i]; + Huff_transmit(&huff, ch, seq, size << 3); /* Transmit symbol */ + Huff_addRef(&huff, (uint8_t)ch); /* Do update */ + } + + bloc += 8; // next uint8_t + + mbuf->cursize = (bloc >> 3) + offset; + memcpy(mbuf->data + offset, seq, (bloc >> 3)); +} + +void Huff_Init(huffman_t *huff) +{ + memset(&huff->compressor, 0, sizeof(huff_t)); + memset(&huff->decompressor, 0, sizeof(huff_t)); + + // Initialize the tree & list with the NYT node + huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = + &(huff->decompressor.nodeList[huff->decompressor.blocNode++]); + huff->decompressor.tree->symbol = NYT; + huff->decompressor.tree->weight = 0; + huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL; + huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL; + + // Add the NYT (not yet transmitted) node into the tree/list */ + huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] = + &(huff->compressor.nodeList[huff->compressor.blocNode++]); + huff->compressor.tree->symbol = NYT; + huff->compressor.tree->weight = 0; + huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL; + huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL; +} diff --git a/src/qcommon/huffman.h b/src/qcommon/huffman.h new file mode 100644 index 0000000..217fb9f --- /dev/null +++ b/src/qcommon/huffman.h @@ -0,0 +1,59 @@ +#ifndef QCOMMON_HUFFMAN_H +#define QCOMMON_HUFFMAN_H 1 + +#include + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#define NYT HMAX /* NYT = Not Yet Transmitted */ +#define INTERNAL_NODE (HMAX + 1) + +typedef struct nodetype { + struct nodetype *left, *right, *parent; /* tree structure */ + struct nodetype *next, *prev; /* doubly-linked list */ + struct nodetype **head; /* highest ranked node in block */ + int weight; + int symbol; +} node_t; + +#define HMAX 256 /* Maximum symbol */ + +typedef struct { + int blocNode; + int blocPtrs; + + node_t *tree; + node_t *lhead; + node_t *ltail; + node_t *loc[HMAX + 1]; + node_t **freelist; + + node_t nodeList[768]; + node_t *nodePtrs[768]; +} huff_t; + +typedef struct { + huff_t compressor; + huff_t decompressor; +} huffman_t; + +void Huff_Compress(struct msg_t *buf, int offset); +void Huff_Decompress(struct msg_t *buf, int offset); +void Huff_Init(huffman_t *huff); +void Huff_addRef(huff_t *huff, uint8_t ch); +int Huff_Receive(node_t *node, int *ch, uint8_t *fin); +void Huff_transmit(huff_t *huff, int ch, uint8_t *fout, int maxoffset); +void Huff_offsetReceive(node_t *node, int *ch, uint8_t *fin, int *offset, int maxoffset); +void Huff_offsetTransmit(huff_t *huff, int ch, uint8_t *fout, int *offset, int maxoffset); +void Huff_putBit(int bit, uint8_t *fout, int *offset); +int Huff_getBit(uint8_t *fout, int *offset); + +// don't use if you don't know what you're doing. +int Huff_getBloc(void); +void Huff_setBloc(int _bloc); + +extern huffman_t clientHuffTables; + +#endif diff --git a/src/qcommon/ioapi.cpp b/src/qcommon/ioapi.cpp new file mode 100644 index 0000000..0d22f9f --- /dev/null +++ b/src/qcommon/ioapi.cpp @@ -0,0 +1,373 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project + + Copyright (C) 1998-2010 Gilles Vollant + http://www.winimage.com/zLibDll/minizip.html + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson + http://result42.com + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + + +#include "ioapi.h" + +#include +#include +#include + +#include "zconf.h" + +#if defined(_WIN32) +# define snprintf _snprintf +#endif + +#ifdef __APPLE__ +/* In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions */ +# define FOPEN_FUNC(filename, mode) fopen(filename, mode) +# define FTELLO_FUNC(stream) ftello(stream) +# define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +# define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +# define FTELLO_FUNC(stream) ftello64(stream) +# define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ +#ifndef SEEK_CUR +# define SEEK_CUR 1 +#endif +#ifndef SEEK_END +# define SEEK_END 2 +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif + +voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode) +{ + if (pfilefunc->zfile_func64.zopen64_file != NULL) + return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode); + return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode); +} + +voidpf call_zopendisk64 OF((const zlib_filefunc64_32_def* pfilefunc, voidpf filestream, unsigned long number_disk, int mode)) +{ + if (pfilefunc->zfile_func64.zopendisk64_file != NULL) + return (*(pfilefunc->zfile_func64.zopendisk64_file)) (pfilefunc->zfile_func64.opaque,filestream,number_disk,mode); + return (*(pfilefunc->zopendisk32_file))(pfilefunc->zfile_func64.opaque,filestream,number_disk,mode); +} + +long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin) +{ + uLong offsetTruncated; + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); + offsetTruncated = (uLong)offset; + if (offsetTruncated != offset) + return -1; + return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin); +} + +ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream) +{ + uLong tell_uLong; + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream); + tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream); + if ((tell_uLong) == 0xffffffff) + return (ZPOS64_T)-1; + return tell_uLong; +} + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32) +{ + p_filefunc64_32->zfile_func64.zopen64_file = NULL; + p_filefunc64_32->zfile_func64.zopendisk64_file = NULL; + p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file; + p_filefunc64_32->zopendisk32_file = p_filefunc32->zopendisk_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file; + p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file; + p_filefunc64_32->zfile_func64.ztell64_file = NULL; + p_filefunc64_32->zfile_func64.zseek64_file = NULL; + p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque; + p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file; + p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; +} + +static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode)); +static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size)); +static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream)); +static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream)); +static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream)); + +typedef struct +{ + FILE *file; + int filenameLength; + void *filename; +} FILE_IOPOSIX; + +static voidpf file_build_ioposix(FILE *file, const char *filename) +{ + FILE_IOPOSIX *ioposix = NULL; + if (file == NULL) + return NULL; + ioposix = (FILE_IOPOSIX*)malloc(sizeof(FILE_IOPOSIX)); + ioposix->file = file; + ioposix->filenameLength = (int)strlen(filename) + 1; + ioposix->filename = (char*)malloc(ioposix->filenameLength * sizeof(char)); + strncpy((char*)ioposix->filename, filename, ioposix->filenameLength); + return (voidpf)ioposix; +} + +static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename != NULL) && (mode_fopen != NULL)) + { + file = fopen(filename, mode_fopen); + return file_build_ioposix(file, filename); + } + return file; +} + +static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename != NULL) && (mode_fopen != NULL)) + { + file = FOPEN_FUNC((const char*)filename, mode_fopen); + return file_build_ioposix(file, (const char*)filename); + } + return file; +} + +static voidpf ZCALLBACK fopendisk64_file_func (voidpf opaque, voidpf stream, unsigned long number_disk, int mode) +{ + FILE_IOPOSIX *ioposix = NULL; + char *diskFilename = NULL; + voidpf ret = NULL; + int i = 0; + + if (stream == NULL) + return NULL; + ioposix = (FILE_IOPOSIX*)stream; + diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char)); + strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength); + for (i = ioposix->filenameLength - 1; i >= 0; i -= 1) + { + if (diskFilename[i] != '.') + continue; + snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02lu", number_disk + 1); + break; + } + if (i >= 0) + ret = fopen64_file_func(opaque, diskFilename, mode); + free(diskFilename); + return ret; +} + +static voidpf ZCALLBACK fopendisk_file_func (voidpf opaque, voidpf stream, unsigned long number_disk, int mode) +{ + FILE_IOPOSIX *ioposix = NULL; + char *diskFilename = NULL; + voidpf ret = NULL; + int i = 0; + + if (stream == NULL) + return NULL; + ioposix = (FILE_IOPOSIX*)stream; + diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char)); + strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength); + for (i = ioposix->filenameLength - 1; i >= 0; i -= 1) + { + if (diskFilename[i] != '.') + continue; + snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02lu", number_disk + 1); + break; + } + if (i >= 0) + ret = fopen_file_func(opaque, diskFilename, mode); + free(diskFilename); + return ret; +} + +static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) +{ + FILE_IOPOSIX *ioposix = NULL; + uLong ret; + if (stream == NULL) + return -1; + ioposix = (FILE_IOPOSIX*)stream; + ret = (uLong)fread(buf, 1, (size_t)size, ioposix->file); + return ret; +} + +static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) +{ + FILE_IOPOSIX *ioposix = NULL; + uLong ret; + if (stream == NULL) + return -1; + ioposix = (FILE_IOPOSIX*)stream; + ret = (uLong)fwrite(buf, 1, (size_t)size, ioposix->file); + return ret; +} + +static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) +{ + FILE_IOPOSIX *ioposix = NULL; + long ret = -1; + if (stream == NULL) + return ret; + ioposix = (FILE_IOPOSIX*)stream; + ret = ftell(ioposix->file); + return ret; +} + +static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) +{ + FILE_IOPOSIX *ioposix = NULL; + ZPOS64_T ret = -1; + if (stream == NULL) + return ret; + ioposix = (FILE_IOPOSIX*)stream; + ret = FTELLO_FUNC(ioposix->file); + return ret; +} + +static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin) +{ + FILE_IOPOSIX *ioposix = NULL; + int fseek_origin = 0; + long ret = 0; + + if (stream == NULL) + return -1; + ioposix = (FILE_IOPOSIX*)stream; + + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR: + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END: + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET: + fseek_origin = SEEK_SET; + break; + default: + return -1; + } + if (fseek(ioposix->file, offset, fseek_origin) != 0) + ret = -1; + return ret; +} + +static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + FILE_IOPOSIX *ioposix = NULL; + int fseek_origin = 0; + long ret = 0; + + if (stream == NULL) + return -1; + ioposix = (FILE_IOPOSIX*)stream; + + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR: + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END: + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET: + fseek_origin = SEEK_SET; + break; + default: + return -1; + } + + if(FSEEKO_FUNC(ioposix->file, offset, fseek_origin) != 0) + ret = -1; + + return ret; +} + + +static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) +{ + FILE_IOPOSIX *ioposix = NULL; + int ret = -1; + if (stream == NULL) + return ret; + ioposix = (FILE_IOPOSIX*)stream; + if (ioposix->filename != NULL) + free(ioposix->filename); + ret = fclose(ioposix->file); + free(ioposix); + return ret; +} + +static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) +{ + FILE_IOPOSIX *ioposix = NULL; + int ret = -1; + if (stream == NULL) + return ret; + ioposix = (FILE_IOPOSIX*)stream; + ret = ferror(ioposix->file); + return ret; +} + +void fill_fopen_filefunc (zlib_filefunc_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zopendisk_file = fopendisk_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} + +void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = fopen64_file_func; + pzlib_filefunc_def->zopendisk64_file = fopendisk64_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell64_file = ftell64_file_func; + pzlib_filefunc_def->zseek64_file = fseek64_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/src/qcommon/ioapi.h b/src/qcommon/ioapi.h new file mode 100644 index 0000000..4b3c297 --- /dev/null +++ b/src/qcommon/ioapi.h @@ -0,0 +1,173 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project + + Copyright (C) 1998-2010 Gilles Vollant + http://www.winimage.com/zLibDll/minizip.html + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson + http://result42.com + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef _ZLIBIOAPI64_H +#define _ZLIBIOAPI64_H + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) +# ifndef __USE_FILE_OFFSET64 +# define __USE_FILE_OFFSET64 +# endif +# ifndef __USE_LARGEFILE64 +# define __USE_LARGEFILE64 +# endif +# ifndef _LARGEFILE64_SOURCE +# define _LARGEFILE64_SOURCE +# endif +# ifndef _FILE_OFFSET_BIT +# define _FILE_OFFSET_BIT 64 +# endif +#endif + +#include "zconf.h" + +#if defined(USE_FILE32API) +# define fopen64 fopen +# define ftello64 ftell +# define fseeko64 fseek +#else +# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) +# define fopen64 fopen +# define ftello64 ftello +# define fseeko64 fseeko +# endif +# ifdef _MSC_VER +# define fopen64 fopen +# if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) +# define ftello64 _ftelli64 +# define fseeko64 _fseeki64 +# else /* old MSC */ +# define ftello64 ftell +# define fseeko64 fseek +# endif +# endif +#endif + +/* a type choosen by DEFINE */ +#ifdef HAVE_64BIT_INT_CUSTOM +typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; +#else +# ifdef HAVE_STDINT_H +# include "stdint.h" + typedef uint64_t ZPOS64_T; +# else +# if defined(_MSC_VER) || defined(__BORLANDC__) + typedef unsigned __int64 ZPOS64_T; +# else + typedef unsigned long long int ZPOS64_T; +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + +#ifndef ZCALLBACK +# if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) \ + && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +# define ZCALLBACK CALLBACK +# else +# define ZCALLBACK +# endif +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef voidpf (ZCALLBACK *opendisk_file_func) OF((voidpf opaque, voidpf stream, unsigned long number_disk, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); + +/* here is the "old" 32 bits structure structure */ +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + opendisk_file_func zopendisk_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + +typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode)); +typedef voidpf (ZCALLBACK *opendisk64_file_func) OF((voidpf opaque, voidpf stream, unsigned long number_disk, int mode)); +typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); + +typedef struct zlib_filefunc64_def_s +{ + open64_file_func zopen64_file; + opendisk64_file_func zopendisk64_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell64_file_func ztell64_file; + seek64_file_func zseek64_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc64_def; + +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); +void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def)); + +/* now internal definition, only for zip.c and unzip.h */ +typedef struct zlib_filefunc64_32_def_s +{ + zlib_filefunc64_def zfile_func64; + open_file_func zopen32_file; + opendisk_file_func zopendisk32_file; + tell_file_func ztell32_file; + seek_file_func zseek32_file; +} zlib_filefunc64_32_def; + +#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +/*#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))*/ +/*#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))*/ +#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) +#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) + +voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)); +voidpf call_zopendisk64 OF((const zlib_filefunc64_32_def* pfilefunc, voidpf filestream, unsigned long number_disk, int mode)); +long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)); +ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)); + +void fill_zlib_filefunc64_32_def_from_filefunc32 OF((zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)); + +#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) +#define ZOPENDISK64(filefunc,filestream,diskn,mode) (call_zopendisk64((&(filefunc)),(filestream),(diskn),(mode))) +#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) +#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/qcommon/json.h b/src/qcommon/json.h new file mode 100644 index 0000000..956ae84 --- /dev/null +++ b/src/qcommon/json.h @@ -0,0 +1,353 @@ +/* +=========================================================================== +Copyright (C) 2016 James Canete +Copyright (C) 2015-2019 GrangerHub + +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 3 +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, see . +=========================================================================== +*/ + +#ifndef JSON_H +#define JSON_H + +enum +{ + JSONTYPE_STRING, // string + JSONTYPE_OBJECT, // object + JSONTYPE_ARRAY, // array + JSONTYPE_VALUE, // number, true, false, or null + JSONTYPE_ERROR // out of data +}; + +// -------------------------------------------------------------------------- +// Array Functions +// -------------------------------------------------------------------------- + +// Get pointer to first value in array +// When given pointer to an array, returns pointer to the first +// returns NULL if array is empty or not an array. +const char *JSON_ArrayGetFirstValue(const char *json, const char *jsonEnd); + +// Get pointer to next value in array +// When given pointer to a value, returns pointer to the next value +// returns NULL when no next value. +const char *JSON_ArrayGetNextValue(const char *json, const char *jsonEnd); + +// Get pointers to values in an array +// returns 0 if not an array, array is empty, or out of data +// returns number of values in the array and copies into index if successful +unsigned int JSON_ArrayGetIndex(const char *json, const char *jsonEnd, const char **indexes, unsigned int numIndexes); + +// Get pointer to indexed value from array +// returns NULL if not an array, no index, or out of data +const char *JSON_ArrayGetValue(const char *json, const char *jsonEnd, unsigned int index); + +// -------------------------------------------------------------------------- +// Object Functions +// -------------------------------------------------------------------------- + +// Get pointer to named value from object +// returns NULL if not an object, name not found, or out of data +const char *JSON_ObjectGetNamedValue(const char *json, const char *jsonEnd, const char *name); + +// -------------------------------------------------------------------------- +// Value Functions +// -------------------------------------------------------------------------- + +// Get type of value +// returns JSONTYPE_ERROR if out of data +unsigned int JSON_ValueGetType(const char *json, const char *jsonEnd); + +// Get value as string +// returns 0 if out of data +// returns length and copies into string if successful, including terminating nul. +// string values are stripped of enclosing quotes but not escaped +unsigned int JSON_ValueGetString(const char *json, const char *jsonEnd, char *outString, unsigned int stringLen); + +// Get value as appropriate type +// returns 0 if value is false, value is null, or out of data +// returns 1 if value is true +// returns value otherwise +double JSON_ValueGetDouble(const char *json, const char *jsonEnd); +float JSON_ValueGetFloat(const char *json, const char *jsonEnd); +int JSON_ValueGetInt(const char *json, const char *jsonEnd); + +#endif + +#ifdef JSON_IMPLEMENTATION +#include + +// -------------------------------------------------------------------------- +// Internal Functions +// -------------------------------------------------------------------------- + +static const char *JSON_SkipSeparators(const char *json, const char *jsonEnd); +static const char *JSON_SkipString(const char *json, const char *jsonEnd); +static const char *JSON_SkipStruct(const char *json, const char *jsonEnd); +static const char *JSON_SkipValue(const char *json, const char *jsonEnd); +static const char *JSON_SkipValueAndSeparators(const char *json, const char *jsonEnd); + +#define IS_SEPARATOR(x) ((x) == ' ' || (x) == '\t' || (x) == '\n' || (x) == '\r' || (x) == ',' || (x) == ':') +#define IS_STRUCT_OPEN(x) ((x) == '{' || (x) == '[') +#define IS_STRUCT_CLOSE(x) ((x) == '}' || (x) == ']') + +static const char *JSON_SkipSeparators(const char *json, const char *jsonEnd) +{ + while (json < jsonEnd && IS_SEPARATOR(*json)) + json++; + + return json; +} + +static const char *JSON_SkipString(const char *json, const char *jsonEnd) +{ + for (json++; json < jsonEnd && *json != '"'; json++) + if (*json == '\\') + json++; + + return (json + 1 > jsonEnd) ? jsonEnd : json + 1; +} + +static const char *JSON_SkipStruct(const char *json, const char *jsonEnd) +{ + json = JSON_SkipSeparators(json + 1, jsonEnd); + while (json < jsonEnd && !IS_STRUCT_CLOSE(*json)) + json = JSON_SkipValueAndSeparators(json, jsonEnd); + + return (json + 1 > jsonEnd) ? jsonEnd : json + 1; +} + +static const char *JSON_SkipValue(const char *json, const char *jsonEnd) +{ + if (json >= jsonEnd) + return jsonEnd; + else if (*json == '"') + json = JSON_SkipString(json, jsonEnd); + else if (IS_STRUCT_OPEN(*json)) + json = JSON_SkipStruct(json, jsonEnd); + else + { + while (json < jsonEnd && !IS_SEPARATOR(*json) && !IS_STRUCT_CLOSE(*json)) + json++; + } + + return json; +} + +static const char *JSON_SkipValueAndSeparators(const char *json, const char *jsonEnd) +{ + json = JSON_SkipValue(json, jsonEnd); + return JSON_SkipSeparators(json, jsonEnd); +} + +// returns 0 if value requires more parsing, 1 if no more data/false/null, 2 if true +static unsigned int JSON_NoParse(const char *json, const char *jsonEnd) +{ + if (!json || json >= jsonEnd || *json == 'f' || *json == 'n') + return 1; + + if (*json == 't') + return 2; + + return 0; +} + +// -------------------------------------------------------------------------- +// Array Functions +// -------------------------------------------------------------------------- + +const char *JSON_ArrayGetFirstValue(const char *json, const char *jsonEnd) +{ + if (!json || json >= jsonEnd || !IS_STRUCT_OPEN(*json)) + return NULL; + + json = JSON_SkipSeparators(json + 1, jsonEnd); + + return (json >= jsonEnd || IS_STRUCT_CLOSE(*json)) ? NULL : json; +} + +const char *JSON_ArrayGetNextValue(const char *json, const char *jsonEnd) +{ + if (!json || json >= jsonEnd || IS_STRUCT_CLOSE(*json)) + return NULL; + + json = JSON_SkipValueAndSeparators(json, jsonEnd); + + return (json >= jsonEnd || IS_STRUCT_CLOSE(*json)) ? NULL : json; +} + +unsigned int JSON_ArrayGetIndex(const char *json, const char *jsonEnd, const char **indexes, unsigned int numIndexes) +{ + unsigned int length = 0; + + for (json = JSON_ArrayGetFirstValue(json, jsonEnd); json; json = JSON_ArrayGetNextValue(json, jsonEnd)) + { + if (indexes && numIndexes) + { + *indexes++ = json; + numIndexes--; + } + length++; + } + + return length; +} + +const char *JSON_ArrayGetValue(const char *json, const char *jsonEnd, unsigned int index) +{ + for (json = JSON_ArrayGetFirstValue(json, jsonEnd); json && index; json = JSON_ArrayGetNextValue(json, jsonEnd)) + index--; + + return json; +} + +// -------------------------------------------------------------------------- +// Object Functions +// -------------------------------------------------------------------------- + +const char *JSON_ObjectGetNamedValue(const char *json, const char *jsonEnd, const char *name) +{ + unsigned int nameLen = strlen(name); + + for (json = JSON_ArrayGetFirstValue(json, jsonEnd); json; json = JSON_ArrayGetNextValue(json, jsonEnd)) + { + if (*json == '"') + { + const char *thisNameStart, *thisNameEnd; + + thisNameStart = json + 1; + json = JSON_SkipString(json, jsonEnd); + thisNameEnd = json - 1; + json = JSON_SkipSeparators(json, jsonEnd); + + if ((unsigned int)(thisNameEnd - thisNameStart) == nameLen) + if (strncmp(thisNameStart, name, nameLen) == 0) + return json; + } + } + + return NULL; +} + +// -------------------------------------------------------------------------- +// Value Functions +// -------------------------------------------------------------------------- + +unsigned int JSON_ValueGetType(const char *json, const char *jsonEnd) +{ + if (!json || json >= jsonEnd) + return JSONTYPE_ERROR; + else if (*json == '"') + return JSONTYPE_STRING; + else if (*json == '{') + return JSONTYPE_OBJECT; + else if (*json == '[') + return JSONTYPE_ARRAY; + + return JSONTYPE_VALUE; +} + +unsigned int JSON_ValueGetString(const char *json, const char *jsonEnd, char *outString, unsigned int stringLen) +{ + const char *stringEnd, *stringStart; + + if (!json) + { + *outString = '\0'; + return 0; + } + + stringStart = json; + stringEnd = JSON_SkipValue(stringStart, jsonEnd); + if (stringEnd >= jsonEnd) + { + *outString = '\0'; + return 0; + } + + // skip enclosing quotes if they exist + if (*stringStart == '"') + stringStart++; + + if (*(stringEnd - 1) == '"') + stringEnd--; + + stringLen--; + if (stringLen > stringEnd - stringStart) + stringLen = stringEnd - stringStart; + + json = stringStart; + while (stringLen--) + *outString++ = *json++; + *outString = '\0'; + + return stringEnd - stringStart; +} + +double JSON_ValueGetDouble(const char *json, const char *jsonEnd) +{ + char cValue[256]; + double dValue = 0.0; + unsigned int np = JSON_NoParse(json, jsonEnd); + + if (np) + return (double)(np - 1); + + if (!JSON_ValueGetString(json, jsonEnd, cValue, 256)) + return 0.0; + + sscanf(cValue, "%lf", &dValue); + + return dValue; +} + +float JSON_ValueGetFloat(const char *json, const char *jsonEnd) +{ + char cValue[256]; + float fValue = 0.0f; + unsigned int np = JSON_NoParse(json, jsonEnd); + + if (np) + return (float)(np - 1); + + if (!JSON_ValueGetString(json, jsonEnd, cValue, 256)) + return 0.0f; + + sscanf(cValue, "%f", &fValue); + + return fValue; +} + +int JSON_ValueGetInt(const char *json, const char *jsonEnd) +{ + char cValue[256]; + int iValue = 0; + unsigned int np = JSON_NoParse(json, jsonEnd); + + if (np) + return np - 1; + + if (!JSON_ValueGetString(json, jsonEnd, cValue, 256)) + return 0; + + sscanf(cValue, "%d", &iValue); + + return iValue; +} + +#undef IS_SEPARATOR +#undef IS_STRUCT_OPEN +#undef IS_STRUCT_CLOSE + +#endif diff --git a/src/qcommon/md4.cpp b/src/qcommon/md4.cpp new file mode 100644 index 0000000..1b212f3 --- /dev/null +++ b/src/qcommon/md4.cpp @@ -0,0 +1,202 @@ +/* + mdfour.c + + An implementation of MD4 designed for use in the samba SMB + authentication protocol + + Copyright (C) 1997-1998 Andrew Tridgell + + 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 3 + 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, see . + + $Id: mdfour.c,v 1.1 2002/08/23 22:03:27 abster Exp $ +*/ + +#include "cvar.h" +#include "q_shared.h" +#include "qcommon.h" + +struct mdfour { + uint32_t A, B, C, D; + uint32_t totalN; +}; + + +/* NOTE: This code makes no attempt to be fast! + + It assumes that an int is at least 32 bits long + */ + +static struct mdfour *m; + +#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z))) +#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z))) +#define H(X,Y,Z) ((X)^(Y)^(Z)) +#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s)))) + +#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s) +#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s) +#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64(uint32_t *M) +{ + int j; + uint32_t AA, BB, CC, DD; + uint32_t X[16]; + uint32_t A,B,C,D; + + for (j=0;j<16;j++) + X[j] = M[j]; + + A = m->A; B = m->B; C = m->C; D = m->D; + AA = A; BB = B; CC = C; DD = D; + + ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7); + ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19); + ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7); + ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19); + ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7); + ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19); + ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7); + ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19); + + ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5); + ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13); + ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5); + ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13); + ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5); + ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13); + ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5); + ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13); + + ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9); + ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15); + ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9); + ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15); + ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9); + ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15); + ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9); + ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15); + + A += AA; B += BB; C += CC; D += DD; + + for (j=0;j<16;j++) + X[j] = 0; + + m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64(uint32_t *M, byte *in) +{ + int i; + + for (i=0;i<16;i++) + M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | (in[i*4+1]<<8) | (in[i*4+0]<<0); +} + +static void copy4(byte *out,uint32_t x) +{ + out[0] = x&0xFF; + out[1] = (x>>8)&0xFF; + out[2] = (x>>16)&0xFF; + out[3] = (x>>24)&0xFF; +} + +void mdfour_begin(struct mdfour *md) +{ + md->A = 0x67452301; + md->B = 0xefcdab89; + md->C = 0x98badcfe; + md->D = 0x10325476; + md->totalN = 0; +} + + +static void mdfour_tail(byte *in, int n) +{ + byte buf[128]; + uint32_t M[16]; + uint32_t b; + + m->totalN += n; + + b = m->totalN * 8; + + memset(buf, 0, 128); + if (n) memcpy(buf, in, n); + buf[n] = 0x80; + + if (n <= 55) { + copy4(buf+56, b); + copy64(M, buf); + mdfour64(M); + } else { + copy4(buf+120, b); + copy64(M, buf); + mdfour64(M); + copy64(M, buf+64); + mdfour64(M); + } +} + +static void mdfour_update(struct mdfour *md, byte *in, int n) +{ + uint32_t M[16]; + + m = md; + + if (n == 0) mdfour_tail(in, n); + + while (n >= 64) { + copy64(M, in); + mdfour64(M); + in += 64; + n -= 64; + m->totalN += 64; + } + + mdfour_tail(in, n); +} + + +static void mdfour_result(struct mdfour *md, byte *out) +{ + copy4(out, md->A); + copy4(out+4, md->B); + copy4(out+8, md->C); + copy4(out+12, md->D); +} + +static void mdfour(byte *out, byte *in, int n) +{ + struct mdfour md; + mdfour_begin(&md); + mdfour_update(&md, in, n); + mdfour_result(&md, out); +} + +//=================================================================== + +unsigned Com_BlockChecksum (const void *buffer, int length) +{ + int digest[4]; + unsigned val; + + mdfour( (byte *)digest, (byte *)buffer, length ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/src/qcommon/md4.h b/src/qcommon/md4.h new file mode 100644 index 0000000..cbb8166 --- /dev/null +++ b/src/qcommon/md4.h @@ -0,0 +1,6 @@ +#ifndef QCOMMON_MD4_H +#define QCOMMON_MD4_H + +unsigned Com_BlockChecksum( const void *buffer, int length ); + +#endif diff --git a/src/qcommon/md5.cpp b/src/qcommon/md5.cpp new file mode 100644 index 0000000..a8e8c58 --- /dev/null +++ b/src/qcommon/md5.cpp @@ -0,0 +1,312 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "files.h" +#include "q_shared.h" +#include "qcommon.h" + +typedef struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} MD5_CTX; + +#ifndef Q3_BIG_ENDIAN + #define byteReverse(buf, len) /* Nothing */ +#else + static void byteReverse(unsigned char *buf, unsigned longs); + + /* + * Note: this code is harmless on little-endian machines. + */ + static void byteReverse(unsigned char *buf, unsigned longs) + { + uint32_t t; + do { + t = (uint32_t) + ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); + } +#endif // Q3_BIG_ENDIAN + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +static void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], + uint32_t const in[16]) +{ + uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +static void MD5Update(struct MD5Context *ctx, unsigned char const *buf, + unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +static void MD5Final(struct MD5Context *ctx, unsigned char *digest) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + + if (digest!=NULL) + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + + +char *Com_MD5File( const char *fn, int length, const char *prefix, int prefix_len ) +{ + static char final[33] = {""}; + unsigned char digest[16] = {""}; + fileHandle_t f; + MD5_CTX md5; + byte buffer[2048]; + int i; + int filelen = 0; + int r = 0; + int total = 0; + + Q_strncpyz( final, "", sizeof( final ) ); + + filelen = FS_SV_FOpenFileRead( fn, &f ); + + if( !f ) { + return final; + } + if( filelen < 1 ) { + FS_FCloseFile( f ); + return final; + } + if(filelen < length || !length) { + length = filelen; + } + + MD5Init(&md5); + + if( prefix_len && *prefix ) + MD5Update(&md5 , (unsigned char *)prefix, prefix_len); + + for(;;) { + r = FS_Read(buffer, sizeof(buffer), f); + if(r < 1) + break; + if(r + total > length) + r = length - total; + total += r; + MD5Update(&md5 , buffer, r); + if(r < sizeof(buffer) || total >= length) + break; + } + FS_FCloseFile(f); + MD5Final(&md5, digest); + final[0] = '\0'; + for(i = 0; i < 16; i++) { + Q_strcat(final, sizeof(final), va("%02X", digest[i])); + } + return final; +} diff --git a/src/qcommon/msg.cpp b/src/qcommon/msg.cpp new file mode 100644 index 0000000..fc4a62a --- /dev/null +++ b/src/qcommon/msg.cpp @@ -0,0 +1,2248 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "msg.h" + +#include "alternatePlayerstate.h" +#include "cvar.h" +#include "huffman.h" +#include "q_shared.h" +#include "qcommon.h" + +static huffman_t msgHuff; + +static bool msgInit = false; + +int pcount[256]; + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles uint8_t ordering and avoids alignment errors +============================================================================== +*/ + +int oldsize = 0; + +void MSG_initHuffman(void); + +void MSG_Init(msg_t *buf, uint8_t *data, int length) +{ + if (!msgInit) + { + MSG_initHuffman(); + } + ::memset(buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; +} + +void MSG_InitOOB(msg_t *buf, uint8_t *data, int length) +{ + if (!msgInit) + { + MSG_initHuffman(); + } + ::memset(buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; + buf->oob = true; +} + +void MSG_Clear(msg_t *buf) +{ + buf->cursize = 0; + buf->overflowed = false; + buf->bit = 0; //<- in bits +} + +void MSG_Bitstream(msg_t *buf) { buf->oob = false; } +void MSG_BeginReading(msg_t *msg) +{ + msg->readcount = 0; + msg->bit = 0; + msg->oob = false; +} + +void MSG_BeginReadingOOB(msg_t *msg) +{ + msg->readcount = 0; + msg->bit = 0; + msg->oob = true; +} + +void MSG_Copy(msg_t *buf, uint8_t *data, int length, msg_t *src) +{ + if (length < src->cursize) + { + Com_Error(ERR_DROP, "MSG_Copy: can't copy into a smaller msg_t buffer"); + } + ::memcpy(buf, src, sizeof(msg_t)); + buf->data = data; + ::memcpy(buf->data, src->data, src->cursize); +} + +/* +============================================================================= + +bit functions + +============================================================================= +*/ + +int overflows; + +// negative bit values include signs +void MSG_WriteBits(msg_t *msg, int value, int bits) +{ + int i; + //FILE* fp; + + oldsize += bits; + + if ( msg->overflowed ) + { + return; + } + + if (bits == 0 || bits < -31 || bits > 32) + { + Com_Error(ERR_DROP, "MSG_WriteBits: bad bits %i", bits); + } + + // check for overflows + if (bits != 32) + { + if (bits > 0) + { + if (value > ((1 << bits) - 1) || value < 0) + { + overflows++; + } + } + else + { + int r; + + r = 1 << (bits - 1); + + if (value > r - 1 || value < -r) + { + overflows++; + } + } + } + if (bits < 0) + { + bits = -bits; + } + if (msg->oob) + { + if (msg->cursize + (bits >> 3) > msg->maxsize) + { + msg->overflowed = true; + return; + } + if (bits == 8) + { + msg->data[msg->cursize] = value; + msg->cursize += 1; + msg->bit += 8; + } + else if (bits == 16) + { + short temp = value; + + CopyLittleShort(&msg->data[msg->cursize], &temp); + msg->cursize += 2; + msg->bit += 16; + } + else if (bits == 32) + { + CopyLittleLong(&msg->data[msg->cursize], &value); + msg->cursize += 4; + msg->bit += 32; + } + else + Com_Error(ERR_DROP, "can't write %d bits", bits); + } + else + { + value &= (0xffffffff >> (32 - bits)); + if (bits & 7) + { + int nbits; + nbits = bits & 7; + + if ( msg->bit + nbits > msg->maxsize << 3 ) + { + msg->overflowed = true; + return; + } + + for (i = 0; i < nbits; i++) + { + Huff_putBit((value & 1), msg->data, &msg->bit); + value = (value >> 1); + } + bits = bits - nbits; + } + if (bits) + { + for (i = 0; i < bits; i += 8) + { + Huff_offsetTransmit(&msgHuff.compressor, (value & 0xff), msg->data, &msg->bit, msg->maxsize << 3); + value = (value >> 8); + + if (msg->bit > msg->maxsize << 3) + { + msg->overflowed = true; + return; + } + } + } + msg->cursize = (msg->bit >> 3) + 1; + } +} + +int MSG_ReadBits(msg_t *msg, int bits) +{ + int value; + int get; + bool sgn; + + + if (msg->readcount > msg->cursize) + { + return 0; + } + + value = 0; + + if (bits < 0) + { + bits = -bits; + sgn = true; + } + else + { + sgn = false; + } + + if (msg->oob) + { + if (msg->readcount + (bits >> 3) > msg->cursize) + { + msg->readcount = msg->cursize + 1; + return 0; + } + + if (bits == 8) + { + value = msg->data[msg->readcount]; + msg->readcount += 1; + msg->bit += 8; + } + else if (bits == 16) + { + short temp; + + CopyLittleShort(&temp, &msg->data[msg->readcount]); + value = temp; + msg->readcount += 2; + msg->bit += 16; + } + else if (bits == 32) + { + CopyLittleLong(&value, &msg->data[msg->readcount]); + msg->readcount += 4; + msg->bit += 32; + } + else + Com_Error(ERR_DROP, "can't read %d bits", bits); + } + else + { + int nbits = 0; + if (bits & 7) + { + nbits = bits & 7; + if (msg->bit + nbits > msg->cursize << 3) + { + msg->readcount = msg->cursize + 1; + return 0; + } + for (int i = 0; i < nbits; i++) + { + value |= (Huff_getBit(msg->data, &msg->bit) << i); + } + bits = bits - nbits; + } + if (bits) + { + for (int i = 0; i < bits; i += 8) + { + Huff_offsetReceive(msgHuff.decompressor.tree, &get, msg->data, &msg->bit, msg->cursize << 3); + value |= (get << (i + nbits)); + + if (msg->bit > msg->cursize << 3) + { + msg->readcount = msg->cursize + 1; + return 0; + } + } + } + msg->readcount = (msg->bit >> 3) + 1; + } + if (sgn && bits > 0 && bits < 32) + { + if (value & (1 << (bits - 1))) + { + value |= -1 ^ ((1 << bits) - 1); + } + } + + return value; +} + +//================================================================================ + +// +// writing functions +// + +void MSG_WriteChar(msg_t *sb, int c) +{ +#ifdef PARANOID + if (c < -128 || c > 127) Com_Error(ERR_FATAL, "MSG_WriteChar: range error"); +#endif + + MSG_WriteBits(sb, c, 8); +} + +void MSG_WriteByte(msg_t *sb, int c) +{ +#ifdef PARANOID + if (c < 0 || c > 255) Com_Error(ERR_FATAL, "MSG_WriteByte: range error"); +#endif + + MSG_WriteBits(sb, c, 8); +} + +void MSG_WriteData(msg_t *buf, const void *data, int length) +{ + int i; + for (i = 0; i < length; i++) + { + MSG_WriteByte(buf, ((uint8_t *)data)[i]); + } +} + +void MSG_WriteShort(msg_t *sb, int c) +{ +#ifdef PARANOID + if (c < ((short)0x8000) || c > (short)0x7fff) Com_Error(ERR_FATAL, "MSG_WriteShort: range error"); +#endif + + MSG_WriteBits(sb, c, 16); +} + +void MSG_WriteLong(msg_t *sb, int c) { MSG_WriteBits(sb, c, 32); } +void MSG_WriteFloat(msg_t *sb, float f) +{ + floatint_t dat; + dat.f = f; + MSG_WriteBits(sb, dat.i, 32); +} + +static void MSG_WriteString2(msg_t *sb, const char *s, int maxsize) +{ + int size = strlen(s) + 1; + + if (size > maxsize) + { + Com_Printf("MSG_WriteString2: %i > %i\n", size, maxsize); + MSG_WriteData(sb, "", 1); + return; + } + + MSG_WriteData(sb, s, size); +} + +void MSG_WriteString(msg_t *sb, const char *s) { MSG_WriteString2(sb, s, MAX_STRING_CHARS); } +void MSG_WriteBigString(msg_t *sb, const char *s) { MSG_WriteString2(sb, s, BIG_INFO_STRING); } +void MSG_WriteAngle(msg_t *sb, float f) { MSG_WriteByte(sb, (int)(f * 256 / 360) & 255); } +void MSG_WriteAngle16(msg_t *sb, float f) { MSG_WriteShort(sb, ANGLE2SHORT(f)); } +//============================================================ + +// +// reading functions +// + +// returns -1 if no more characters are available +int MSG_ReadChar(msg_t *msg) +{ + int c; + + c = (signed char)MSG_ReadBits(msg, 8); + if (msg->readcount > msg->cursize) + { + c = -1; + } + + return c; +} + +int MSG_ReadByte(msg_t *msg) +{ + int c; + + c = (unsigned char)MSG_ReadBits(msg, 8); + if (msg->readcount > msg->cursize) + { + c = -1; + } + return c; +} + +int MSG_LookaheadByte(msg_t *msg) +{ + const int bloc = Huff_getBloc(); + const int readcount = msg->readcount; + const int bit = msg->bit; + int c = MSG_ReadByte(msg); + Huff_setBloc(bloc); + msg->readcount = readcount; + msg->bit = bit; + return c; +} + +int MSG_ReadShort(msg_t *msg) +{ + int c; + + c = (short)MSG_ReadBits(msg, 16); + if (msg->readcount > msg->cursize) + { + c = -1; + } + + return c; +} + +int MSG_ReadLong(msg_t *msg) +{ + int c; + + c = MSG_ReadBits(msg, 32); + if (msg->readcount > msg->cursize) + { + c = -1; + } + + return c; +} + +float MSG_ReadFloat(msg_t *msg) +{ + floatint_t dat; + + dat.i = MSG_ReadBits(msg, 32); + if (msg->readcount > msg->cursize) + { + dat.f = -1; + } + + return dat.f; +} + +char *MSG_ReadString(msg_t *msg) +{ + static char string[MAX_STRING_CHARS]; + + size_t l = 0; + do + { + int c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0) + { + break; + } + + string[l] = c; + l++; + } while (l < sizeof(string) - 1); + + string[l] = 0; + + return string; +} + +char *MSG_ReadBigString(msg_t *msg) +{ + static char string[BIG_INFO_STRING]; + + size_t l = 0; + do + { + int c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0) + { + break; + } + + string[l] = c; + l++; + } while (l < sizeof(string) - 1); + + string[l] = 0; + + return string; +} + +char *MSG_ReadStringLine(msg_t *msg) +{ + static char string[MAX_STRING_CHARS]; + + size_t l = 0; + do + { + int c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0 || c == '\n') + { + break; + } + + string[l] = c; + l++; + } while (l < sizeof(string) - 1); + + string[l] = 0; + + return string; +} + +float MSG_ReadAngle16(msg_t *msg) { return SHORT2ANGLE(MSG_ReadShort(msg)); } +void MSG_ReadData(msg_t *msg, void *data, int len) +{ + int i; + + for (i = 0; i < len; i++) + { + ((uint8_t *)data)[i] = MSG_ReadByte(msg); + } +} + +// a string hasher which gives the same hash value even if the +// string is later modified via the legacy MSG read/write code +int MSG_HashKey(int alternateProtocol, const char *string, int maxlen) +{ + int hash, i; + + hash = 0; + for (i = 0; i < maxlen && string[i] != '\0'; i++) + { + if (string[i] & 0x80 || (alternateProtocol == 2 && string[i] == '%')) + hash += '.' * (119 + i); + else + hash += string[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + return hash; +} + +/* +============================================================================= + +delta functions + +============================================================================= +*/ + +extern cvar_t *cl_shownet; + +#define LOG(x) \ + if (cl_shownet && cl_shownet->integer == 4) \ + { \ + Com_Printf("%s ", x); \ + }; + +void MSG_WriteDelta(msg_t *msg, int oldV, int newV, int bits) +{ + if (oldV == newV) + { + MSG_WriteBits(msg, 0, 1); + return; + } + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, newV, bits); +} + +int MSG_ReadDelta(msg_t *msg, int oldV, int bits) +{ + if (MSG_ReadBits(msg, 1)) + { + return MSG_ReadBits(msg, bits); + } + return oldV; +} + +void MSG_WriteDeltaFloat(msg_t *msg, float oldV, float newV) +{ + floatint_t fi; + if (oldV == newV) + { + MSG_WriteBits(msg, 0, 1); + return; + } + fi.f = newV; + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, fi.i, 32); +} + +float MSG_ReadDeltaFloat(msg_t *msg, float oldV) +{ + if (MSG_ReadBits(msg, 1)) + { + floatint_t fi; + + fi.i = MSG_ReadBits(msg, 32); + return fi.f; + } + return oldV; +} + +/* +============================================================================= + +delta functions with keys + +============================================================================= +*/ + +unsigned int kbitmask[32] = { + 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, + 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF, 0x0003FFFF, + 0x0007FFFF, 0x000FFFFF, 0x001FFFFf, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, + 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, +}; + +void MSG_WriteDeltaKey(msg_t *msg, int key, int oldV, int newV, int bits) +{ + if (oldV == newV) + { + MSG_WriteBits(msg, 0, 1); + return; + } + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, newV ^ key, bits); +} + +int MSG_ReadDeltaKey(msg_t *msg, int key, int oldV, int bits) +{ + if (MSG_ReadBits(msg, 1)) + { + return MSG_ReadBits(msg, bits) ^ (key & kbitmask[bits - 1]); + } + return oldV; +} + +void MSG_WriteDeltaKeyFloat(msg_t *msg, int key, float oldV, float newV) +{ + floatint_t fi; + if (oldV == newV) + { + MSG_WriteBits(msg, 0, 1); + return; + } + fi.f = newV; + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, fi.i ^ key, 32); +} + +float MSG_ReadDeltaKeyFloat(msg_t *msg, int key, float oldV) +{ + if (MSG_ReadBits(msg, 1)) + { + floatint_t fi; + + fi.i = MSG_ReadBits(msg, 32) ^ key; + return fi.f; + } + return oldV; +} + +/* +============================================================================ + +usercmd_t communication + +============================================================================ +*/ + +/* +===================== +MSG_WriteDeltaUsercmdKey +===================== +*/ +void MSG_WriteDeltaUsercmdKey(msg_t *msg, int key, usercmd_t *from, usercmd_t *to) +{ + if (to->serverTime - from->serverTime < 256) + { + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, to->serverTime - from->serverTime, 8); + } + else + { + MSG_WriteBits(msg, 0, 1); + MSG_WriteBits(msg, to->serverTime, 32); + } + if ( from->angles[0] == to->angles[0] + && from->angles[1] == to->angles[1] + && from->angles[2] == to->angles[2] + && from->forwardmove == to->forwardmove + && from->rightmove == to->rightmove + && from->upmove == to->upmove + && from->buttons == to->buttons + && from->weapon == to->weapon ) + { + MSG_WriteBits(msg, 0, 1); // no change + oldsize += 7; + return; + } + key ^= to->serverTime; + MSG_WriteBits(msg, 1, 1); + MSG_WriteDeltaKey(msg, key, from->angles[0], to->angles[0], 16); + MSG_WriteDeltaKey(msg, key, from->angles[1], to->angles[1], 16); + MSG_WriteDeltaKey(msg, key, from->angles[2], to->angles[2], 16); + MSG_WriteDeltaKey(msg, key, from->forwardmove, to->forwardmove, 8); + MSG_WriteDeltaKey(msg, key, from->rightmove, to->rightmove, 8); + MSG_WriteDeltaKey(msg, key, from->upmove, to->upmove, 8); + MSG_WriteDeltaKey(msg, key, from->buttons, to->buttons, 16); + MSG_WriteDeltaKey(msg, key, from->weapon, to->weapon, 8); +} + +/* +===================== +MSG_ReadDeltaUsercmdKey +===================== +*/ +void MSG_ReadDeltaUsercmdKey(msg_t *msg, int key, usercmd_t *from, usercmd_t *to) +{ + if (MSG_ReadBits(msg, 1)) + { + to->serverTime = from->serverTime + MSG_ReadBits(msg, 8); + } + else + { + to->serverTime = MSG_ReadBits(msg, 32); + } + if (MSG_ReadBits(msg, 1)) + { + key ^= to->serverTime; + to->angles[0] = MSG_ReadDeltaKey(msg, key, from->angles[0], 16); + to->angles[1] = MSG_ReadDeltaKey(msg, key, from->angles[1], 16); + to->angles[2] = MSG_ReadDeltaKey(msg, key, from->angles[2], 16); + to->forwardmove = MSG_ReadDeltaKey(msg, key, from->forwardmove, 8); + if (to->forwardmove == -128) to->forwardmove = -127; + to->rightmove = MSG_ReadDeltaKey(msg, key, from->rightmove, 8); + if (to->rightmove == -128) to->rightmove = -127; + to->upmove = MSG_ReadDeltaKey(msg, key, from->upmove, 8); + if (to->upmove == -128) to->upmove = -127; + to->buttons = MSG_ReadDeltaKey(msg, key, from->buttons, 16); + to->weapon = MSG_ReadDeltaKey(msg, key, from->weapon, 8); + } + else + { + to->angles[0] = from->angles[0]; + to->angles[1] = from->angles[1]; + to->angles[2] = from->angles[2]; + to->forwardmove = from->forwardmove; + to->rightmove = from->rightmove; + to->upmove = from->upmove; + to->buttons = from->buttons; + to->weapon = from->weapon; + } +} + +/* +============================================================================= + +entityState_t communication + +============================================================================= +*/ + +/* +================= +MSG_ReportChangeVectors_f + +Prints out a table from the current statistics for copying to code +================= +*/ +void MSG_ReportChangeVectors_f(void) +{ + int i; + for (i = 0; i < 256; i++) + { + if (pcount[i]) + { + Com_Printf("%d used %d\n", i, pcount[i]); + } + } +} + +typedef struct { + const char *name; + size_t offset; + int bits; // 0 = float +} netField_t; + +// using the stringizing operator to save typing... +#define NETF(x) #x, (size_t) & ((entityState_t *) 0)->x + +netField_t entityStateFields[] = { + {NETF(pos.trTime), 32}, + {NETF(pos.trBase[0]), 0}, + {NETF(pos.trBase[1]), 0}, + {NETF(pos.trDelta[0]), 0}, + {NETF(pos.trDelta[1]), 0}, + {NETF(pos.trBase[2]), 0}, + {NETF(apos.trBase[1]), 0}, + {NETF(pos.trDelta[2]), 0}, + {NETF(apos.trBase[0]), 0}, + {NETF(event), 10}, + {NETF(angles2[1]), 0}, + {NETF(eType), 8}, + {NETF(torsoAnim), 8}, + {NETF(weaponAnim), 8}, + {NETF(eventParm), 8}, + {NETF(legsAnim), 8}, + {NETF(groundEntityNum), GENTITYNUM_BITS}, + {NETF(pos.trType), 8}, + {NETF(eFlags), 19}, + {NETF(otherEntityNum), GENTITYNUM_BITS}, + {NETF(weapon), 8}, + {NETF(clientNum), 8}, + {NETF(angles[1]), 0}, + {NETF(pos.trDuration), 32}, + {NETF(apos.trType), 8}, + {NETF(origin[0]), 0}, + {NETF(origin[1]), 0}, + {NETF(origin[2]), 0}, + {NETF(solid), 24}, + {NETF(misc), MAX_MISC}, + {NETF(modelindex), 8}, + {NETF(otherEntityNum2), GENTITYNUM_BITS}, + {NETF(loopSound), 8}, + {NETF(generic1), 10}, + {NETF(origin2[2]), 0}, + {NETF(origin2[0]), 0}, + {NETF(origin2[1]), 0}, + {NETF(modelindex2), 8}, + {NETF(angles[0]), 0}, + {NETF(time), 32}, + {NETF(apos.trTime), 32}, + {NETF(apos.trDuration), 32}, + {NETF(apos.trBase[2]), 0}, + {NETF(apos.trDelta[0]), 0}, + {NETF(apos.trDelta[1]), 0}, + {NETF(apos.trDelta[2]), 0}, + {NETF(time2), 32}, + {NETF(angles[2]), 0}, + {NETF(angles2[0]), 0}, + {NETF(angles2[2]), 0}, + {NETF(constantLight), 32}, + {NETF(frame), 16} +}; + +// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS ) +// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent +#define FLOAT_INT_BITS 13 +#define FLOAT_INT_BIAS (1 << (FLOAT_INT_BITS - 1)) + +/* +================== +MSG_WriteDeltaEntity + +Writes part of a packetentities message, including the entity number. +Can delta from either a baseline or a previous packet_entity +If to is NULL, a remove entity update will be sent +If force is not set, then nothing at all will be generated if the entity is +identical, under the assumption that the in-order delta code will catch it. +================== +*/ +void MSG_WriteDeltaEntity(int alternateProtocol, msg_t *msg, struct entityState_s *from, struct entityState_s *to, bool force) +{ + int i, lc; + int numFields; + netField_t *field; + int trunc; + float fullFloat; + int *fromF, *toF; + + numFields = ARRAY_LEN(entityStateFields); + + // all fields should be 32 bits to avoid any compiler packing issues + // the "number" field is not part of the field list + // if this assert fails, someone added a field to the entityState_t + // struct without updating the message fields + assert(numFields + 1 == sizeof(*from) / 4); + + // a NULL to is a delta remove message + if (to == NULL) + { + if (from == NULL) + { + return; + } + MSG_WriteBits(msg, from->number, GENTITYNUM_BITS); + MSG_WriteBits(msg, 1, 1); + return; + } + + if (to->number < 0 || to->number >= MAX_GENTITIES) + { + Com_Error(ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number); + } + + lc = 0; + // build the change vector as bytes so it is endien independent + for (i = 0, field = entityStateFields; i < numFields; i++, field++) + { + if (alternateProtocol == 2 && i == 13) + { + continue; + } + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + if (*fromF != *toF) + { + lc = i + 1; + } + } + + if (lc == 0) + { + // nothing at all changed + if (!force) + { + return; // nothing at all + } + // write two bits for no change + MSG_WriteBits(msg, to->number, GENTITYNUM_BITS); + MSG_WriteBits(msg, 0, 1); // not removed + MSG_WriteBits(msg, 0, 1); // no delta + return; + } + + MSG_WriteBits(msg, to->number, GENTITYNUM_BITS); + MSG_WriteBits(msg, 0, 1); // not removed + MSG_WriteBits(msg, 1, 1); // we have a delta + + if (alternateProtocol == 2 && lc - 1 > 13) + { + MSG_WriteByte(msg, lc - 1); // # of changes + } + else + { + MSG_WriteByte(msg, lc); // # of changes + } + + oldsize += numFields; + + for (i = 0, field = entityStateFields; i < lc; i++, field++) + { + if (alternateProtocol == 2 && i == 13) + { + continue; + } + + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + + if (*fromF == *toF) + { + MSG_WriteBits(msg, 0, 1); // no change + continue; + } + + MSG_WriteBits(msg, 1, 1); // changed + + if (field->bits == 0) + { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if (fullFloat == 0.0f) + { + MSG_WriteBits(msg, 0, 1); + oldsize += FLOAT_INT_BITS; + } + else + { + MSG_WriteBits(msg, 1, 1); + if (trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && trunc + FLOAT_INT_BIAS < (1 << FLOAT_INT_BITS)) + { + // send as small integer + MSG_WriteBits(msg, 0, 1); + MSG_WriteBits(msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS); + } + else + { + // send as full floating point value + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, *toF, 32); + } + } + } + else + { + if (*toF == 0) + { + MSG_WriteBits(msg, 0, 1); + } + else + { + MSG_WriteBits(msg, 1, 1); + // integer + if (alternateProtocol == 2 && i == 33) + { + MSG_WriteBits(msg, *toF, 8); + } + else + { + MSG_WriteBits(msg, *toF, field->bits); + } + } + } + } +} + +/* +================== +MSG_ReadDeltaEntity + +The entity number has already been read from the message, which +is how the from state is identified. + +If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1 + +Can go from either a baseline or a previous packet_entity +================== +*/ +void MSG_ReadDeltaEntity(int alternateProtocol, msg_t *msg, entityState_t *from, entityState_t *to, int number) +{ + int i, lc; + int numFields; + netField_t *field; + int *fromF, *toF; + int print; + int trunc; + int startBit, endBit; + + if (number < 0 || number >= MAX_GENTITIES) + { + Com_Error(ERR_DROP, "Bad delta entity number: %i", number); + } + + if (msg->bit == 0) + { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } + else + { + startBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // check for a remove + if (MSG_ReadBits(msg, 1) == 1) + { + ::memset(to, 0, sizeof(*to)); + to->number = MAX_GENTITIES - 1; + if (cl_shownet && (cl_shownet->integer >= 2 || cl_shownet->integer == -1)) + { + Com_Printf("%3i: #%-3i remove\n", msg->readcount, number); + } + return; + } + + // check for no delta + if (MSG_ReadBits(msg, 1) == 0) + { + *to = *from; + to->number = number; + return; + } + + numFields = ARRAY_LEN(entityStateFields); + lc = MSG_ReadByte(msg); + if (alternateProtocol == 2 && lc - 1 >= 13) + { + ++lc; + } + + if (lc > numFields || lc < 0) + { + Com_Error(ERR_DROP, "invalid entityState field count"); + } + + // shownet 2/3 will interleave with other printed info, -1 will + // just print the delta records` + if (cl_shownet && (cl_shownet->integer >= 2 || cl_shownet->integer == -1)) + { + print = 1; + Com_Printf("%3i: #%-3i ", msg->readcount, to->number); + } + else + { + print = 0; + } + + to->number = number; + + for (i = 0, field = entityStateFields; i < lc; i++, field++) + { + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + if (alternateProtocol == 2 && i == 13) + { + *toF = 0; + continue; + } + + if (!MSG_ReadBits(msg, 1)) + { + // no change + *toF = *fromF; + } + else + { + if (field->bits == 0) + { + // float + if (MSG_ReadBits(msg, 1) == 0) + { + *(float *)toF = 0.0f; + } + else + { + if (MSG_ReadBits(msg, 1) == 0) + { + // integral float + trunc = MSG_ReadBits(msg, FLOAT_INT_BITS); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if (print) + { + Com_Printf("%s:%i ", field->name, trunc); + } + } + else + { + // full floating point value + *toF = MSG_ReadBits(msg, 32); + if (print) + { + Com_Printf("%s:%f ", field->name, *(float *)toF); + } + } + } + } + else + { + if (MSG_ReadBits(msg, 1) == 0) + { + *toF = 0; + } + else + { + // integer + if (alternateProtocol == 2 && i == 33) + { + *toF = MSG_ReadBits(msg, 8); + } + else + { + *toF = MSG_ReadBits(msg, field->bits); + } + if (print) + { + Com_Printf("%s:%i ", field->name, *toF); + } + } + } + // pcount[i]++; + } + } + for (i = lc, field = &entityStateFields[lc]; i < numFields; i++, field++) + { + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + // no change + *toF = *fromF; + } + + if (print) + { + if (msg->bit == 0) + { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } + else + { + endBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf(" (%i bits)\n", endBit - startBit); + } +} + +/* +============================================================================ + +plyer_state_t communication + +============================================================================ +*/ + +// using the stringizing operator to save typing... +#define PSF(x) #x, (size_t) & ((playerState_t *) 0)->x + +netField_t playerStateFields[] = { + {PSF(commandTime), 32}, + {PSF(origin[0]), 0}, + {PSF(origin[1]), 0}, + {PSF(bobCycle), 8}, + {PSF(velocity[0]), 0}, + {PSF(velocity[1]), 0}, + {PSF(viewangles[1]), 0}, + {PSF(viewangles[0]), 0}, + {PSF(weaponTime), -16}, + {PSF(origin[2]), 0}, + {PSF(velocity[2]), 0}, + {PSF(legsTimer), 8}, + {PSF(pm_time), -16}, + {PSF(eventSequence), 16}, + {PSF(torsoAnim), 8}, + {PSF(weaponAnim), 8}, + {PSF(movementDir), 4}, + {PSF(events[0]), 8}, + {PSF(legsAnim), 8}, + {PSF(events[1]), 8}, + {PSF(pm_flags), 24}, + {PSF(groundEntityNum), GENTITYNUM_BITS}, + {PSF(weaponstate), 4}, + {PSF(eFlags), 16}, + {PSF(externalEvent), 10}, + {PSF(gravity), -16}, + {PSF(speed), -16}, + {PSF(delta_angles[1]), 16}, + {PSF(externalEventParm), 8}, + {PSF(viewheight), -8}, + {PSF(damageEvent), 8}, + {PSF(damageYaw), 8}, + {PSF(damagePitch), 8}, + {PSF(damageCount), 8}, + {PSF(ammo), 12}, + {PSF(clips), 4}, + {PSF(generic1), 10}, + {PSF(pm_type), 8}, + {PSF(delta_angles[0]), 16}, + {PSF(delta_angles[2]), 16}, + {PSF(torsoTimer), 12}, + {PSF(tauntTimer), 12}, + {PSF(eventParms[0]), 8}, + {PSF(eventParms[1]), 8}, + {PSF(clientNum), 8}, + {PSF(weapon), 5}, + {PSF(viewangles[2]), 0}, + {PSF(grapplePoint[0]), 0}, + {PSF(grapplePoint[1]), 0}, + {PSF(grapplePoint[2]), 0}, + {PSF(otherEntityNum), GENTITYNUM_BITS}, + {PSF(loopSound), 16} +}; + +#define APSF(x) #x, (size_t) & ((alternatePlayerState_t *) 0)->x + +netField_t alternatePlayerStateFields[] = { + {APSF(commandTime), 32}, + {APSF(origin[0]), 0}, + {APSF(origin[1]), 0}, + {APSF(bobCycle), 8}, + {APSF(velocity[0]), 0}, + {APSF(velocity[1]), 0}, + {APSF(viewangles[1]), 0}, + {APSF(viewangles[0]), 0}, + {APSF(weaponTime), -16}, + {APSF(origin[2]), 0}, + {APSF(velocity[2]), 0}, + {APSF(legsTimer), 8}, + {APSF(pm_time), -16}, + {APSF(eventSequence), 16}, + {APSF(torsoAnim), 8}, + {APSF(movementDir), 4}, + {APSF(events[0]), 8}, + {APSF(legsAnim), 8}, + {APSF(events[1]), 8}, + {APSF(pm_flags), 16}, + {APSF(groundEntityNum), GENTITYNUM_BITS}, + {APSF(weaponstate), 4}, + {APSF(eFlags), 16}, + {APSF(externalEvent), 10}, + {APSF(gravity), -16}, + {APSF(speed), -16}, + {APSF(delta_angles[1]), 16}, + {APSF(externalEventParm), 8}, + {APSF(viewheight), -8}, + {APSF(damageEvent), 8}, + {APSF(damageYaw), 8}, + {APSF(damagePitch), 8}, + {APSF(damageCount), 8}, + {APSF(generic1), 8}, + {APSF(pm_type), 8}, + {APSF(delta_angles[0]), 16}, + {APSF(delta_angles[2]), 16}, + {APSF(torsoTimer), 12}, + {APSF(eventParms[0]), 8}, + {APSF(eventParms[1]), 8}, + {APSF(clientNum), 8}, + {APSF(weapon), 5}, + {APSF(viewangles[2]), 0}, + {APSF(grapplePoint[0]), 0}, + {APSF(grapplePoint[1]), 0}, + {APSF(grapplePoint[2]), 0}, + {APSF(otherEntityNum), GENTITYNUM_BITS}, + {APSF(loopSound), 16} +}; + +/* +============= +MSG_WriteDeltaPlayerstate + +============= +*/ +void MSG_WriteDeltaPlayerstate(int alternateProtocol, msg_t *msg, struct playerState_s *from, struct playerState_s *to) +{ + int i; + playerState_t dummy; + int statsbits; + int persistantbits; + int altFromAmmo[3]; + int altToAmmo[3]; + int ammobits; + int miscbits; + int numFields; + netField_t *field; + int *fromF, *toF; + float fullFloat; + int trunc, lc; + + if (!from) + { + from = &dummy; + ::memset(&dummy, 0, sizeof(dummy)); + } + + numFields = ARRAY_LEN(playerStateFields); + + lc = 0; + for (i = 0, field = playerStateFields; i < numFields; i++, field++) + { + if (alternateProtocol == 2 && (i == 15 || i == 34 || i == 35 || i == 41)) + { + continue; + } + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + if (*fromF != *toF) + { + lc = i + 1; + } + } + + if (alternateProtocol == 2) + { + if (lc - 1 > 41) + { + MSG_WriteByte(msg, lc - 4); // # of changes + } + else if (lc - 1 > 35) + { + MSG_WriteByte(msg, lc - 3); // # of changes + } + else if (lc - 1 > 34) + { + MSG_WriteByte(msg, lc - 2); // # of changes + } + else if (lc - 1 > 15) + { + MSG_WriteByte(msg, lc - 1); // # of changes + } + else + { + MSG_WriteByte(msg, lc); // # of changes + } + } + else + { + MSG_WriteByte(msg, lc); // # of changes + } + + oldsize += numFields - lc; + + for (i = 0, field = playerStateFields; i < lc; i++, field++) + { + if (alternateProtocol == 2 && (i == 15 || i == 34 || i == 35 || i == 41)) + { + continue; + } + + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + + if (*fromF == *toF) + { + MSG_WriteBits(msg, 0, 1); // no change + continue; + } + + MSG_WriteBits(msg, 1, 1); // changed + // pcount[i]++; + + if (field->bits == 0) + { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if (trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && trunc + FLOAT_INT_BIAS < (1 << FLOAT_INT_BITS)) + { + // send as small integer + MSG_WriteBits(msg, 0, 1); + MSG_WriteBits(msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS); + } + else + { + // send as full floating point value + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, *toF, 32); + } + } + else + { + // integer + if (alternateProtocol == 2) + { + if (i == 20) + { + MSG_WriteBits(msg, *toF, 16); + } + else if (i == 36) + { + MSG_WriteBits(msg, *toF, 8); + } + else + { + MSG_WriteBits(msg, *toF, field->bits); + } + } + else + { + MSG_WriteBits(msg, *toF, field->bits); + } + } + } + + // + // send the arrays + // + statsbits = 0; + for (i = 0; i < MAX_STATS; i++) + { + if (to->stats[i] != from->stats[i]) + { + statsbits |= 1 << i; + } + } + persistantbits = 0; + for (i = 0; i < MAX_PERSISTANT; i++) + { + if (to->persistant[i] != from->persistant[i]) + { + persistantbits |= 1 << i; + } + } + if (alternateProtocol == 2) + { + altFromAmmo[0] = (from->weaponAnim & 0xFF) | ((from->pm_flags >> 8) & 0xFF00); + altFromAmmo[1] = (from->ammo & 0xFFF) | ((from->clips << 12) & 0xF000); + altFromAmmo[2] = (from->tauntTimer & 0xFFF) | ((from->generic1 << 4) & 0x3000); + altToAmmo[0] = (to->weaponAnim & 0xFF) | ((to->pm_flags >> 8) & 0xFF00); + altToAmmo[1] = (to->ammo & 0xFFF) | ((to->clips << 12) & 0xF000); + altToAmmo[2] = (to->tauntTimer & 0xFFF) | ((to->generic1 << 4) & 0x3000); + ammobits = 0; + for (i = 0; i < 3; i++) + { + if (altToAmmo[i] != altFromAmmo[i]) + { + ammobits |= 1 << i; + } + } + } + else + { + ammobits = 0; + } + miscbits = 0; + for (i = 0; i < MAX_MISC; i++) + { + if (to->misc[i] != from->misc[i]) + { + miscbits |= 1 << i; + } + } + + if (!statsbits && !persistantbits && !ammobits && !miscbits) + { + MSG_WriteBits(msg, 0, 1); // no change + oldsize += 4; + return; + } + MSG_WriteBits(msg, 1, 1); // changed + + if (statsbits) + { + MSG_WriteBits(msg, 1, 1); // changed + MSG_WriteBits(msg, statsbits, MAX_STATS); + for (i = 0; i < MAX_STATS; i++) + if (statsbits & (1 << i)) MSG_WriteShort(msg, to->stats[i]); + } + else + { + MSG_WriteBits(msg, 0, 1); // no change + } + + if (persistantbits) + { + MSG_WriteBits(msg, 1, 1); // changed + MSG_WriteBits(msg, persistantbits, MAX_PERSISTANT); + for (i = 0; i < MAX_PERSISTANT; i++) + if (persistantbits & (1 << i)) MSG_WriteShort(msg, to->persistant[i]); + } + else + { + MSG_WriteBits(msg, 0, 1); // no change + } + + if (alternateProtocol == 2) + { + if (ammobits) + { + MSG_WriteBits(msg, 1, 1); // changed + MSG_WriteBits(msg, ammobits, 16); + for (i = 0; i < 3; i++) + if (ammobits & (1 << i)) MSG_WriteShort(msg, altToAmmo[i]); + } + else + { + MSG_WriteBits(msg, 0, 1); // no change + } + } + + if (miscbits) + { + MSG_WriteBits(msg, 1, 1); // changed + MSG_WriteBits(msg, miscbits, MAX_MISC); + for (i = 0; i < MAX_MISC; i++) + if (miscbits & (1 << i)) MSG_WriteLong(msg, to->misc[i]); + } + else + { + MSG_WriteBits(msg, 0, 1); // no change + } +} + +/* +=================== +MSG_ReadDeltaPlayerstate +=================== +*/ +void MSG_ReadDeltaPlayerstate(msg_t *msg, playerState_t *from, playerState_t *to) +{ + int i, lc; + int bits; + netField_t *field; + int numFields; + int startBit, endBit; + int print; + int *fromF, *toF; + int trunc; + playerState_t dummy; + + if (!from) + { + from = &dummy; + ::memset(&dummy, 0, sizeof(dummy)); + } + *to = *from; + + if (msg->bit == 0) + { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } + else + { + startBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if (cl_shownet && (cl_shownet->integer >= 2 || cl_shownet->integer == -2)) + { + print = 1; + Com_Printf("%3i: playerstate ", msg->readcount); + } + else + { + print = 0; + } + + numFields = ARRAY_LEN(playerStateFields); + lc = MSG_ReadByte(msg); + + if (lc > numFields || lc < 0) + { + Com_Error(ERR_DROP, "invalid playerState field count"); + } + + for (i = 0, field = playerStateFields; i < lc; i++, field++) + { + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + + if (!MSG_ReadBits(msg, 1)) + { + // no change + *toF = *fromF; + } + else + { + if (field->bits == 0) + { + // float + if (MSG_ReadBits(msg, 1) == 0) + { + // integral float + trunc = MSG_ReadBits(msg, FLOAT_INT_BITS); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if (print) + { + Com_Printf("%s:%i ", field->name, trunc); + } + } + else + { + // full floating point value + *toF = MSG_ReadBits(msg, 32); + if (print) + { + Com_Printf("%s:%f ", field->name, *(float *)toF); + } + } + } + else + { + // integer + *toF = MSG_ReadBits(msg, field->bits); + if (print) + { + Com_Printf("%s:%i ", field->name, *toF); + } + } + } + } + for (i = lc, field = &playerStateFields[lc]; i < numFields; i++, field++) + { + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + // no change + *toF = *fromF; + } + + // read the arrays + if (MSG_ReadBits(msg, 1)) + { + // parse stats + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_STATS"); + bits = MSG_ReadBits(msg, MAX_STATS); + for (i = 0; i < MAX_STATS; i++) + { + if (bits & (1 << i)) + { + to->stats[i] = MSG_ReadShort(msg); + } + } + } + + // parse persistant stats + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_PERSISTANT"); + bits = MSG_ReadBits(msg, MAX_PERSISTANT); + for (i = 0; i < MAX_PERSISTANT; i++) + { + if (bits & (1 << i)) + { + to->persistant[i] = MSG_ReadShort(msg); + } + } + } + + // parse misc data + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_MISC"); + bits = MSG_ReadBits(msg, MAX_MISC); + for (i = 0; i < MAX_MISC; i++) + { + if (bits & (1 << i)) + { + to->misc[i] = MSG_ReadLong(msg); + } + } + } + } + + if (print) + { + if (msg->bit == 0) + { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } + else + { + endBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf(" (%i bits)\n", endBit - startBit); + } +} + +void MSG_ReadDeltaAlternatePlayerstate(msg_t *msg, alternatePlayerState_t *from, alternatePlayerState_t *to) +{ + int i, lc; + int bits; + netField_t *field; + int numFields; + int startBit, endBit; + int print; + int *fromF, *toF; + int trunc; + alternatePlayerState_t dummy; + + if (!from) + { + from = &dummy; + ::memset(&dummy, 0, sizeof(dummy)); + } + *to = *from; + + if (msg->bit == 0) + { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } + else + { + startBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if (cl_shownet && (cl_shownet->integer >= 2 || cl_shownet->integer == -2)) + { + print = 1; + Com_Printf("%3i: playerstate ", msg->readcount); + } + else + { + print = 0; + } + + numFields = ARRAY_LEN(alternatePlayerStateFields); + lc = MSG_ReadByte(msg); + + if (lc > numFields || lc < 0) + { + Com_Error(ERR_DROP, "invalid playerState field count"); + } + + for (i = 0, field = alternatePlayerStateFields; i < lc; i++, field++) + { + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + + if (!MSG_ReadBits(msg, 1)) + { + // no change + *toF = *fromF; + } + else + { + if (field->bits == 0) + { + // float + if (MSG_ReadBits(msg, 1) == 0) + { + // integral float + trunc = MSG_ReadBits(msg, FLOAT_INT_BITS); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if (print) + { + Com_Printf("%s:%i ", field->name, trunc); + } + } + else + { + // full floating point value + *toF = MSG_ReadBits(msg, 32); + if (print) + { + Com_Printf("%s:%f ", field->name, *(float *)toF); + } + } + } + else + { + // integer + *toF = MSG_ReadBits(msg, field->bits); + if (print) + { + Com_Printf("%s:%i ", field->name, *toF); + } + } + } + } + for (i = lc, field = &alternatePlayerStateFields[lc]; i < numFields; i++, field++) + { + fromF = (int *)((uint8_t *)from + field->offset); + toF = (int *)((uint8_t *)to + field->offset); + // no change + *toF = *fromF; + } + + // read the arrays + if (MSG_ReadBits(msg, 1)) + { + // parse stats + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_STATS"); + bits = MSG_ReadBits(msg, MAX_STATS); + for (i = 0; i < MAX_STATS; i++) + { + if (bits & (1 << i)) + { + to->stats[i] = MSG_ReadShort(msg); + } + } + } + + // parse persistant stats + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_PERSISTANT"); + bits = MSG_ReadBits(msg, MAX_PERSISTANT); + for (i = 0; i < MAX_PERSISTANT; i++) + { + if (bits & (1 << i)) + { + to->persistant[i] = MSG_ReadShort(msg); + } + } + } + + // parse ammo + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_AMMO"); + bits = MSG_ReadBits(msg, MAX_WEAPONS); + for (i = 0; i < MAX_WEAPONS; i++) + { + if (bits & (1 << i)) + { + to->ammo[i] = MSG_ReadShort(msg); + } + } + } + + // parse misc data + if (MSG_ReadBits(msg, 1)) + { + LOG("PS_MISC"); + bits = MSG_ReadBits(msg, MAX_MISC); + for (i = 0; i < MAX_MISC; i++) + { + if (bits & (1 << i)) + { + to->misc[i] = MSG_ReadLong(msg); + } + } + } + } + + if (print) + { + if (msg->bit == 0) + { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } + else + { + endBit = (msg->readcount - 1) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf(" (%i bits)\n", endBit - startBit); + } +} + +int msg_hData[256] = { + 250315, // 0 + 41193, // 1 + 6292, // 2 + 7106, // 3 + 3730, // 4 + 3750, // 5 + 6110, // 6 + 23283, // 7 + 33317, // 8 + 6950, // 9 + 7838, // 10 + 9714, // 11 + 9257, // 12 + 17259, // 13 + 3949, // 14 + 1778, // 15 + 8288, // 16 + 1604, // 17 + 1590, // 18 + 1663, // 19 + 1100, // 20 + 1213, // 21 + 1238, // 22 + 1134, // 23 + 1749, // 24 + 1059, // 25 + 1246, // 26 + 1149, // 27 + 1273, // 28 + 4486, // 29 + 2805, // 30 + 3472, // 31 + 21819, // 32 + 1159, // 33 + 1670, // 34 + 1066, // 35 + 1043, // 36 + 1012, // 37 + 1053, // 38 + 1070, // 39 + 1726, // 40 + 888, // 41 + 1180, // 42 + 850, // 43 + 960, // 44 + 780, // 45 + 1752, // 46 + 3296, // 47 + 10630, // 48 + 4514, // 49 + 5881, // 50 + 2685, // 51 + 4650, // 52 + 3837, // 53 + 2093, // 54 + 1867, // 55 + 2584, // 56 + 1949, // 57 + 1972, // 58 + 940, // 59 + 1134, // 60 + 1788, // 61 + 1670, // 62 + 1206, // 63 + 5719, // 64 + 6128, // 65 + 7222, // 66 + 6654, // 67 + 3710, // 68 + 3795, // 69 + 1492, // 70 + 1524, // 71 + 2215, // 72 + 1140, // 73 + 1355, // 74 + 971, // 75 + 2180, // 76 + 1248, // 77 + 1328, // 78 + 1195, // 79 + 1770, // 80 + 1078, // 81 + 1264, // 82 + 1266, // 83 + 1168, // 84 + 965, // 85 + 1155, // 86 + 1186, // 87 + 1347, // 88 + 1228, // 89 + 1529, // 90 + 1600, // 91 + 2617, // 92 + 2048, // 93 + 2546, // 94 + 3275, // 95 + 2410, // 96 + 3585, // 97 + 2504, // 98 + 2800, // 99 + 2675, // 100 + 6146, // 101 + 3663, // 102 + 2840, // 103 + 14253, // 104 + 3164, // 105 + 2221, // 106 + 1687, // 107 + 3208, // 108 + 2739, // 109 + 3512, // 110 + 4796, // 111 + 4091, // 112 + 3515, // 113 + 5288, // 114 + 4016, // 115 + 7937, // 116 + 6031, // 117 + 5360, // 118 + 3924, // 119 + 4892, // 120 + 3743, // 121 + 4566, // 122 + 4807, // 123 + 5852, // 124 + 6400, // 125 + 6225, // 126 + 8291, // 127 + 23243, // 128 + 7838, // 129 + 7073, // 130 + 8935, // 131 + 5437, // 132 + 4483, // 133 + 3641, // 134 + 5256, // 135 + 5312, // 136 + 5328, // 137 + 5370, // 138 + 3492, // 139 + 2458, // 140 + 1694, // 141 + 1821, // 142 + 2121, // 143 + 1916, // 144 + 1149, // 145 + 1516, // 146 + 1367, // 147 + 1236, // 148 + 1029, // 149 + 1258, // 150 + 1104, // 151 + 1245, // 152 + 1006, // 153 + 1149, // 154 + 1025, // 155 + 1241, // 156 + 952, // 157 + 1287, // 158 + 997, // 159 + 1713, // 160 + 1009, // 161 + 1187, // 162 + 879, // 163 + 1099, // 164 + 929, // 165 + 1078, // 166 + 951, // 167 + 1656, // 168 + 930, // 169 + 1153, // 170 + 1030, // 171 + 1262, // 172 + 1062, // 173 + 1214, // 174 + 1060, // 175 + 1621, // 176 + 930, // 177 + 1106, // 178 + 912, // 179 + 1034, // 180 + 892, // 181 + 1158, // 182 + 990, // 183 + 1175, // 184 + 850, // 185 + 1121, // 186 + 903, // 187 + 1087, // 188 + 920, // 189 + 1144, // 190 + 1056, // 191 + 3462, // 192 + 2240, // 193 + 4397, // 194 + 12136, // 195 + 7758, // 196 + 1345, // 197 + 1307, // 198 + 3278, // 199 + 1950, // 200 + 886, // 201 + 1023, // 202 + 1112, // 203 + 1077, // 204 + 1042, // 205 + 1061, // 206 + 1071, // 207 + 1484, // 208 + 1001, // 209 + 1096, // 210 + 915, // 211 + 1052, // 212 + 995, // 213 + 1070, // 214 + 876, // 215 + 1111, // 216 + 851, // 217 + 1059, // 218 + 805, // 219 + 1112, // 220 + 923, // 221 + 1103, // 222 + 817, // 223 + 1899, // 224 + 1872, // 225 + 976, // 226 + 841, // 227 + 1127, // 228 + 956, // 229 + 1159, // 230 + 950, // 231 + 7791, // 232 + 954, // 233 + 1289, // 234 + 933, // 235 + 1127, // 236 + 3207, // 237 + 1020, // 238 + 927, // 239 + 1355, // 240 + 768, // 241 + 1040, // 242 + 745, // 243 + 952, // 244 + 805, // 245 + 1073, // 246 + 740, // 247 + 1013, // 248 + 805, // 249 + 1008, // 250 + 796, // 251 + 996, // 252 + 1057, // 253 + 11457, // 254 + 13504, // 255 +}; + +void MSG_initHuffman(void) +{ + int i, j; + + msgInit = true; + Huff_Init(&msgHuff); + for (i = 0; i < 256; i++) + { + for (j = 0; j < msg_hData[i]; j++) + { + Huff_addRef(&msgHuff.compressor, (uint8_t)i); // Do update + Huff_addRef(&msgHuff.decompressor, (uint8_t)i); // Do update + } + } +} + +/* +void MSG_NUinitHuffman() { + uint8_t *data; + int size, i, ch; + int array[256]; + + msgInit = true; + + Huff_Init(&msgHuff); + // load it in + size = FS_ReadFile( "netchan/netchan.bin", (void **)&data ); + + for(i=0;i<256;i++) { + array[i] = 0; + } + for(i=0;i + +// +// msg.c +// +struct msg_t { + bool allowoverflow; // if false, do a Com_Error + bool overflowed; // set to true if the buffer size failed (with allowoverflow set) + bool oob; // set to true if the buffer size failed (with allowoverflow set) + uint8_t *data; + int maxsize; + int cursize; + int readcount; + int bit; // for bitwise reads and writes +}; + +void MSG_Init(struct msg_t *buf, uint8_t *data, int length); +void MSG_InitOOB(struct msg_t *buf, uint8_t *data, int length); +void MSG_Clear(struct msg_t *buf); +void MSG_WriteData(struct msg_t *buf, const void *data, int length); +void MSG_Bitstream(struct msg_t *buf); + +// TTimo +// copy a struct msg_t in case we need to store it as is for a bit +// (as I needed this to keep an struct msg_t from a static var for later use) +// sets data buffer as MSG_Init does prior to do the copy +void MSG_Copy(struct msg_t *buf, uint8_t *data, int length, struct msg_t *src); + +typedef struct usercmd_s usercmd_t; +typedef struct entityState_s entityState_t; +typedef struct playerState_s playerState_t; + +void MSG_WriteBits(struct msg_t *msg, int value, int bits); + +void MSG_WriteChar(struct msg_t *sb, int c); +void MSG_WriteByte(struct msg_t *sb, int c); +void MSG_WriteShort(struct msg_t *sb, int c); +void MSG_WriteLong(struct msg_t *sb, int c); +void MSG_WriteFloat(struct msg_t *sb, float f); +void MSG_WriteString(struct msg_t *sb, const char *s); +void MSG_WriteBigString(struct msg_t *sb, const char *s); +void MSG_WriteAngle16(struct msg_t *sb, float f); +int MSG_HashKey(int alternateProtocol, const char *string, int maxlen); + +void MSG_BeginReading(struct msg_t *sb); +void MSG_BeginReadingOOB(struct msg_t *sb); + +int MSG_ReadBits(struct msg_t *msg, int bits); + +int MSG_ReadChar(struct msg_t *sb); +int MSG_ReadByte(struct msg_t *sb); +int MSG_ReadShort(struct msg_t *sb); +int MSG_ReadLong(struct msg_t *sb); +float MSG_ReadFloat(struct msg_t *sb); +char *MSG_ReadString(struct msg_t *sb); +char *MSG_ReadBigString(struct msg_t *sb); +char *MSG_ReadStringLine(struct msg_t *sb); +float MSG_ReadAngle16(struct msg_t *sb); +void MSG_ReadData(struct msg_t *sb, void *buffer, int size); +int MSG_LookaheadByte(struct msg_t *msg); + +void MSG_WriteDeltaUsercmdKey(struct msg_t *msg, int key, usercmd_t *from, usercmd_t *to); +void MSG_ReadDeltaUsercmdKey(struct msg_t *msg, int key, usercmd_t *from, usercmd_t *to); + +void MSG_WriteDeltaEntity(int alternateProtocol, struct msg_t *msg, struct entityState_s *from, struct entityState_s *to, bool force); +void MSG_ReadDeltaEntity(int alternateProtocol, struct msg_t *msg, entityState_t *from, entityState_t *to, int number); + +void MSG_WriteDeltaPlayerstate(int alternateProtocol, struct msg_t *msg, struct playerState_s *from, struct playerState_s *to); +void MSG_ReadDeltaPlayerstate(struct msg_t *msg, struct playerState_s *from, struct playerState_s *to); + +struct alternatePlayerState_t; +void MSG_ReadDeltaAlternatePlayerstate(struct msg_t *msg, struct alternatePlayerState_t *from, struct alternatePlayerState_t *to); + +void MSG_ReportChangeVectors_f(void); + +#endif diff --git a/src/qcommon/net.h b/src/qcommon/net.h new file mode 100644 index 0000000..7766d23 --- /dev/null +++ b/src/qcommon/net.h @@ -0,0 +1,145 @@ +#ifndef QCOMMON_NET_H +#define QCOMMON_NET_H 1 + +#include + +/* +============================================================== + +NET + +============================================================== +*/ + +#define NET_ENABLEV4 0x01 +#define NET_ENABLEV6 0x02 +// if this flag is set, always attempt ipv6 connections instead of ipv4 if a v6 address is found. +#define NET_PRIOV6 0x04 +// disables ipv6 multicast support if set. +#define NET_DISABLEMCAST 0x08 + +#define NET_ENABLEALT1PROTO 0x01 +#define NET_ENABLEALT2PROTO 0x02 +#define NET_DISABLEPRIMPROTO 0x04 + +#define PACKET_BACKUP 32 // number of old messages that must be kept on client and + // server for delta compression and ping estimation +#define PACKET_MASK (PACKET_BACKUP - 1) + +#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet + +#define MAX_SNAPSHOT_ENTITIES 256 + +#define PORT_ANY -1 + +#define MAX_RELIABLE_COMMANDS 128 // max string commands buffered for retransmit + +enum netadrtype_t { + NA_BAD = 0, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IP6, + NA_MULTICAST6, + NA_UNSPEC +}; + +typedef enum { NS_CLIENT, NS_SERVER } netsrc_t; + +#define NET_ADDRSTRMAXLEN 48 // maximum length of an IPv6 address string including trailing '\0' +struct netadr_t { + enum netadrtype_t type; + + uint8_t ip[4]; + uint8_t ip6[16]; + + unsigned short port; + unsigned long scope_id; // Needed for IPv6 link-local addresses + + int alternateProtocol; +}; + +void NET_Init(void); +void NET_Shutdown(void); +void NET_Restart_f(void); +void NET_Config(bool enableNetworking); +void NET_FlushPacketQueue(void); +void NET_SendPacket(netsrc_t sock, int length, const void *data, struct netadr_t to); +void NET_OutOfBandPrint(netsrc_t net_socket, struct netadr_t adr, const char *format, ...) + __attribute__((format(printf, 3, 4))); +void NET_OutOfBandData(netsrc_t sock, struct netadr_t adr, uint8_t *format, int len); + +bool NET_CompareAdr(struct netadr_t a, struct netadr_t b); +bool NET_CompareBaseAdrMask(struct netadr_t a, struct netadr_t b, int netmask); +bool NET_CompareBaseAdr(struct netadr_t a, struct netadr_t b); +bool NET_IsLocalAddress(struct netadr_t adr); +const char *NET_AdrToString(struct netadr_t a); +const char *NET_AdrToStringwPort(struct netadr_t a); +int NET_StringToAdr(const char *s, struct netadr_t *a, enum netadrtype_t family); +bool NET_GetLoopPacket(netsrc_t sock, struct netadr_t *net_from, struct msg_t *net_message); +void NET_JoinMulticast6(void); +void NET_LeaveMulticast6(void); +void NET_Sleep(int msec); + +#define MAX_MSGLEN 16384 // max length of a message, which may be fragmented into multiple packets + +#define MAX_DOWNLOAD_WINDOW 48 // ACK window of 48 download chunks. Cannot set this higher, or clients + // will overflow the reliable commands buffer +#define MAX_DOWNLOAD_BLKSIZE 1024 // 896 uint8_t block chunks + +#define NETCHAN_GENCHECKSUM(challenge, sequence) ((challenge) ^ ((sequence) * (challenge))) + +/* +Netchan handles packet fragmentation and out of order / duplicate suppression +*/ + +typedef struct { + netsrc_t sock; + + int dropped; // between last packet and previous + + int alternateProtocol; + struct netadr_t remoteAddress; + int qport; // qport value to write when transmitting + + // sequencing variables + int incomingSequence; + int outgoingSequence; + + // incoming fragment assembly buffer + int fragmentSequence; + int fragmentLength; + uint8_t fragmentBuffer[MAX_MSGLEN]; + + // outgoing fragment buffer + // we need to space out the sending of large fragmented messages + bool unsentFragments; + int unsentFragmentStart; + int unsentLength; + uint8_t unsentBuffer[MAX_MSGLEN]; + + int challenge; + int lastSentTime; + int lastSentSize; +} netchan_t; + +void Netchan_Init(int qport); +void Netchan_Setup( + int alternateProtocol, netsrc_t sock, netchan_t *chan, struct netadr_t adr, int qport, int challenge); + +void Netchan_Transmit(netchan_t *chan, int length, const uint8_t *data); +void Netchan_TransmitNextFragment(netchan_t *chan); + +bool Netchan_Process(netchan_t *chan, struct msg_t *msg); + +void Sys_SendPacket(int length, const void *data, struct netadr_t to); +bool Sys_StringToAdr(const char *s, struct netadr_t *a, enum netadrtype_t family); // Does NOT parse port numbers, only base addresses. +bool Sys_IsLANAddress(struct netadr_t adr); +void Sys_ShowIP(void); + +#define SV_ENCODE_START 4 +#define SV_DECODE_START 12 +#define CL_ENCODE_START 12 +#define CL_DECODE_START 4 + +#endif diff --git a/src/qcommon/net_chan.cpp b/src/qcommon/net_chan.cpp new file mode 100644 index 0000000..1dab821 --- /dev/null +++ b/src/qcommon/net_chan.cpp @@ -0,0 +1,697 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "net.h" + +#include "sys/sys_shared.h" + +#include "cvar.h" +#include "huffman.h" +#include "msg.h" +#include "q_shared.h" +#include "qcommon.h" + +/* + +packet header +------------- +4 outgoing sequence. high bit will be set if this is a fragmented message +[2 qport (only for client to server)] +[2 fragment start byte] +[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment] + +if the sequence number is -1, the packet should be handled as an out-of-band +message instead of as part of a netcon. + +All fragments will have the same sequence numbers. + +The qport field is a workaround for bad address translating routers that +sometimes remap the client's source port on a packet during game play. + +If the base part of the net address matches and the qport matches, then the +channel matches even if the IP port differs. The IP port should be updated +to the new value before sending out any replies. + +*/ + +#define MAX_PACKETLEN 1400 // max size of a network packet + +#define FRAGMENT_SIZE (MAX_PACKETLEN - 100) +#define PACKET_HEADER 10 // two ints and a short + +#define FRAGMENT_BIT (1 << 31) + +cvar_t *showpackets; +cvar_t *showdrop; +cvar_t *qport; + +static const char *netsrcString[2] = {"client", "server"}; + +/* +=============== +Netchan_Init + +=============== +*/ +void Netchan_Init(int port) +{ + port &= 0xffff; + showpackets = Cvar_Get("showpackets", "0", CVAR_TEMP); + showdrop = Cvar_Get("showdrop", "0", CVAR_TEMP); + qport = Cvar_Get("net_qport", va("%i", port), CVAR_INIT); +} + +/* +============== +Netchan_Setup + +called to open a channel to a remote system +============== +*/ +void Netchan_Setup(int alternateProtocol, netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge) +{ + ::memset(chan, 0, sizeof(*chan)); + + chan->sock = sock; + chan->remoteAddress = adr; + chan->qport = qport; + chan->incomingSequence = 0; + chan->outgoingSequence = 1; + chan->challenge = challenge; + chan->alternateProtocol = alternateProtocol; +} + +/* +================= +Netchan_TransmitNextFragment + +Send one fragment of the current message +================= +*/ +void Netchan_TransmitNextFragment(netchan_t *chan) +{ + msg_t send; + byte send_buf[MAX_PACKETLEN]; + int fragmentLength; + int outgoingSequence; + + // write the packet header + MSG_InitOOB(&send, send_buf, sizeof(send_buf)); // <-- only do the oob here + + outgoingSequence = chan->outgoingSequence | FRAGMENT_BIT; + MSG_WriteLong(&send, outgoingSequence); + + // send the qport if we are a client + if (chan->sock == NS_CLIENT) + { + MSG_WriteShort(&send, qport->integer); + } + + if (chan->alternateProtocol == 0) + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + + // copy the reliable message to the packet first + fragmentLength = FRAGMENT_SIZE; + if (chan->unsentFragmentStart + fragmentLength > chan->unsentLength) + { + fragmentLength = chan->unsentLength - chan->unsentFragmentStart; + } + + MSG_WriteShort(&send, chan->unsentFragmentStart); + MSG_WriteShort(&send, fragmentLength); + MSG_WriteData(&send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength); + + // send the datagram + NET_SendPacket(chan->sock, send.cursize, send.data, chan->remoteAddress); + + // Store send time and size of this packet for rate control + chan->lastSentTime = Sys_Milliseconds(); + chan->lastSentSize = send.cursize; + + if (showpackets->integer) + { + Com_Printf("%s send %4i : s=%i fragment=%i,%i\n", netsrcString[chan->sock], send.cursize, + chan->outgoingSequence, chan->unsentFragmentStart, fragmentLength); + } + + chan->unsentFragmentStart += fragmentLength; + + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + if (chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE) + { + chan->outgoingSequence++; + chan->unsentFragments = false; + } +} + +/* +=============== +Netchan_Transmit + +Sends a message to a connection, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +void Netchan_Transmit(netchan_t *chan, int length, const byte *data) +{ + msg_t send; + byte send_buf[MAX_PACKETLEN]; + + if (length > MAX_MSGLEN) + { + Com_Error(ERR_DROP, "Netchan_Transmit: length = %i", length); + } + chan->unsentFragmentStart = 0; + + // fragment large reliable messages + if (length >= FRAGMENT_SIZE) + { + chan->unsentFragments = true; + chan->unsentLength = length; + ::memcpy(chan->unsentBuffer, data, length); + + // only send the first fragment now + Netchan_TransmitNextFragment(chan); + + return; + } + + // write the packet header + MSG_InitOOB(&send, send_buf, sizeof(send_buf)); + + MSG_WriteLong(&send, chan->outgoingSequence); + + // send the qport if we are a client + if (chan->sock == NS_CLIENT) MSG_WriteShort(&send, qport->integer); + + if (chan->alternateProtocol == 0) + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + + chan->outgoingSequence++; + + MSG_WriteData(&send, data, length); + + // send the datagram + NET_SendPacket(chan->sock, send.cursize, send.data, chan->remoteAddress); + + // Store send time and size of this packet for rate control + chan->lastSentTime = Sys_Milliseconds(); + chan->lastSentSize = send.cursize; + + if (showpackets->integer) + { + Com_Printf("%s send %4i : s=%i ack=%i\n", netsrcString[chan->sock], send.cursize, chan->outgoingSequence - 1, + chan->incomingSequence); + } +} + +/* +================= +Netchan_Process + +Returns false if the message should not be processed due to being +out of order or a fragment. + +Msg must be large enough to hold MAX_MSGLEN, because if this is the +final fragment of a multi-part message, the entire thing will be +copied out. +================= +*/ +bool Netchan_Process(netchan_t *chan, msg_t *msg) +{ + int sequence; + int fragmentStart, fragmentLength; + int checksum; + bool fragmented; + + // XOR unscramble all data in the packet after the header + // Netchan_UnScramblePacket( msg ); + + // get sequence numbers + MSG_BeginReadingOOB(msg); + sequence = MSG_ReadLong(msg); + + // check for fragment information + if (sequence & FRAGMENT_BIT) + { + sequence &= ~FRAGMENT_BIT; + fragmented = true; + } + else + { + fragmented = false; + } + + // read the qport if we are a server + if (chan->sock == NS_SERVER) + { + MSG_ReadShort(msg); + } + + if (chan->alternateProtocol == 0) + { + checksum = MSG_ReadLong(msg); + + // UDP spoofing protection + if (NETCHAN_GENCHECKSUM(chan->challenge, sequence) != checksum) return false; + } + + // read the fragment information + if (fragmented) + { + fragmentStart = MSG_ReadShort(msg); + fragmentLength = MSG_ReadShort(msg); + } + else + { + fragmentStart = 0; // stop warning message + fragmentLength = 0; + } + + if (showpackets->integer) + { + if (fragmented) + { + Com_Printf("%s recv %4i : s=%i fragment=%i,%i\n", netsrcString[chan->sock], msg->cursize, sequence, + fragmentStart, fragmentLength); + } + else + { + Com_Printf("%s recv %4i : s=%i\n", netsrcString[chan->sock], msg->cursize, sequence); + } + } + + // + // discard out of order or duplicated packets + // + if (sequence <= chan->incomingSequence) + { + if (showdrop->integer || showpackets->integer) + { + Com_Printf("%s:Out of order packet %i at %i\n", NET_AdrToString(chan->remoteAddress), sequence, + chan->incomingSequence); + } + return false; + } + + // + // dropped packets don't keep the message from being used + // + chan->dropped = sequence - (chan->incomingSequence + 1); + if (chan->dropped > 0) + { + if (showdrop->integer || showpackets->integer) + { + Com_Printf("%s:Dropped %i packets at %i\n", NET_AdrToString(chan->remoteAddress), chan->dropped, sequence); + } + } + + // + // if this is the final fragment of a reliable message, + // bump incoming_reliable_sequence + // + if (fragmented) + { + // TTimo + // make sure we add the fragments in correct order + // either a packet was dropped, or we received this one too soon + // we don't reconstruct the fragments. We will wait till this fragment gets to us again + // (NOTE: we could probably try to rebuild by out of order chunks if needed) + if (sequence != chan->fragmentSequence) + { + chan->fragmentSequence = sequence; + chan->fragmentLength = 0; + } + + // if we missed a fragment, dump the message + if (fragmentStart != chan->fragmentLength) + { + if (showdrop->integer || showpackets->integer) + { + Com_Printf("%s:Dropped a message fragment\n", NET_AdrToString(chan->remoteAddress)); + } + // we can still keep the part that we have so far, + // so we don't need to clear chan->fragmentLength + return false; + } + + // copy the fragment to the fragment buffer + if (fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize + || (size_t)(chan->fragmentLength + fragmentLength) > sizeof(chan->fragmentBuffer)) + { + if (showdrop->integer || showpackets->integer) + { + Com_Printf("%s:illegal fragment length\n", NET_AdrToString(chan->remoteAddress)); + } + return false; + } + + ::memcpy(chan->fragmentBuffer + chan->fragmentLength, msg->data + msg->readcount, fragmentLength); + + chan->fragmentLength += fragmentLength; + + // if this wasn't the last fragment, don't process anything + if (fragmentLength == FRAGMENT_SIZE) + { + return false; + } + + if (chan->fragmentLength > msg->maxsize) + { + Com_Printf( + "%s:fragmentLength %i > msg->maxsize\n", NET_AdrToString(chan->remoteAddress), chan->fragmentLength); + return false; + } + + // copy the full message over the partial fragment + + // make sure the sequence number is still there + *(int *)msg->data = LittleLong(sequence); + + ::memcpy(msg->data + 4, chan->fragmentBuffer, chan->fragmentLength); + msg->cursize = chan->fragmentLength + 4; + chan->fragmentLength = 0; + msg->readcount = 4; // past the sequence number + msg->bit = 32; // past the sequence number + + // TTimo + // clients were not acking fragmented messages + chan->incomingSequence = sequence; + + return true; + } + + // + // the message can now be read from the current message pointer + // + chan->incomingSequence = sequence; + + return true; +} + +//============================================================================== + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ + +// there needs to be enough loopback messages to hold a complete +// gamestate of maximum size +#define MAX_LOOPBACK 16 + +typedef struct { + byte data[MAX_PACKETLEN]; + int datalen; +} loopmsg_t; + +typedef struct { + loopmsg_t msgs[MAX_LOOPBACK]; + int get, send; +} loopback_t; + +loopback_t loopbacks[2]; + +bool NET_GetLoopPacket(netsrc_t sock, netadr_t *net_from, msg_t *net_message) +{ + int i; + loopback_t *loop; + + loop = &loopbacks[sock]; + + if (loop->send - loop->get > MAX_LOOPBACK) loop->get = loop->send - MAX_LOOPBACK; + + if (loop->get >= loop->send) return false; + + i = loop->get & (MAX_LOOPBACK - 1); + loop->get++; + + ::memcpy(net_message->data, loop->msgs[i].data, loop->msgs[i].datalen); + net_message->cursize = loop->msgs[i].datalen; + ::memset(net_from, 0, sizeof(*net_from)); + net_from->type = NA_LOOPBACK; + return true; +} + +void NET_SendLoopPacket(netsrc_t sock, int length, const void *data, netadr_t to) +{ + int i; + loopback_t *loop; + + loop = &loopbacks[sock ^ 1]; + + i = loop->send & (MAX_LOOPBACK - 1); + loop->send++; + + ::memcpy(loop->msgs[i].data, data, length); + loop->msgs[i].datalen = length; +} + +//============================================================================= + +typedef struct packetQueue_s { + struct packetQueue_s *next; + int length; + byte *data; + netadr_t to; + int release; +} packetQueue_t; + +packetQueue_t *packetQueue = NULL; + +static void NET_QueuePacket(int length, const void *data, netadr_t to, int offset) +{ + packetQueue_t *_new, *next = packetQueue; + + if (offset > 999) offset = 999; + + _new = (packetQueue_t *)S_Malloc(sizeof(packetQueue_t)); + _new->data = (byte *)S_Malloc(length); + ::memcpy(_new->data, data, length); + _new->length = length; + _new->to = to; + _new->release = Sys_Milliseconds() + (int)((float)offset / com_timescale->value); + _new->next = NULL; + + if (!packetQueue) + { + packetQueue = _new; + return; + } + while (next) + { + if (!next->next) + { + next->next = _new; + return; + } + next = next->next; + } +} + +void NET_FlushPacketQueue(void) +{ + packetQueue_t *last; + int now; + + while (packetQueue) + { + now = Sys_Milliseconds(); + if (packetQueue->release >= now) break; + Sys_SendPacket(packetQueue->length, packetQueue->data, packetQueue->to); + last = packetQueue; + packetQueue = packetQueue->next; + Z_Free(last->data); + Z_Free(last); + } +} + +void NET_SendPacket(netsrc_t sock, int length, const void *data, netadr_t to) +{ + // sequenced packets are shown in netchan, so just show oob + if (showpackets->integer && *(int *)data == -1) + { + Com_Printf("send packet %4i\n", length); + } + + if (to.type == NA_LOOPBACK) + { + NET_SendLoopPacket(sock, length, data, to); + return; + } + if (to.type == NA_BAD) + { + return; + } + + if (sock == NS_CLIENT && cl_packetdelay->integer > 0) + { + NET_QueuePacket(length, data, to, cl_packetdelay->integer); + } + else if (sock == NS_SERVER && sv_packetdelay->integer > 0) + { + NET_QueuePacket(length, data, to, sv_packetdelay->integer); + } + else + { + Sys_SendPacket(length, data, to); + } +} + +/* +=============== +NET_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void QDECL NET_OutOfBandPrint(netsrc_t sock, netadr_t adr, const char *format, ...) +{ + va_list argptr; + char string[MAX_MSGLEN]; + + // set the header + string[0] = -1; + string[1] = -1; + string[2] = -1; + string[3] = -1; + + va_start(argptr, format); + Q_vsnprintf(string + 4, sizeof(string) - 4, format, argptr); + va_end(argptr); + + // send the datagram + NET_SendPacket(sock, strlen(string), string, adr); +} + +/* +=============== +NET_OutOfBandPrint + +Sends a data message in an out-of-band datagram (only used for "connect") +================ +*/ +void QDECL NET_OutOfBandData(netsrc_t sock, netadr_t adr, byte *format, int len) +{ + byte string[MAX_MSGLEN * 2]; + int i; + msg_t mbuf; + + // set the header + string[0] = 0xff; + string[1] = 0xff; + string[2] = 0xff; + string[3] = 0xff; + + for (i = 0; i < len; i++) + { + string[i + 4] = format[i]; + } + + mbuf.data = string; + mbuf.cursize = len + 4; + Huff_Compress(&mbuf, 12); + // send the datagram + NET_SendPacket(sock, mbuf.cursize, mbuf.data, adr); +} + +/* +============= +NET_StringToAdr + +Traps "localhost" for loopback, passes everything else to system +return 0 on address not found, 1 on address found with port, 2 on address found without port. +============= +*/ +int NET_StringToAdr(const char *s, netadr_t *a, netadrtype_t family) +{ + char base[MAX_STRING_CHARS], *search; + char *port = NULL; + + if (!strcmp(s, "localhost")) + { + ::memset(a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + // as NA_LOOPBACK doesn't require ports report port was given. + return 1; + } + + Q_strncpyz(base, s, sizeof(base)); + + if (*base == '[' || Q_CountChar(base, ':') > 1) + { + // This is an ipv6 address, handle it specially. + search = strchr(base, ']'); + if (search) + { + *search = '\0'; + search++; + + if (*search == ':') port = search + 1; + } + + if (*base == '[') + search = base + 1; + else + search = base; + } + else + { + // look for a port number + port = strchr(base, ':'); + + if (port) + { + *port = '\0'; + port++; + } + + search = base; + } + + a->alternateProtocol = 0; + + if (!Sys_StringToAdr(search, a, family)) + { + a->type = NA_BAD; + return 0; + } + + if (port) + { + a->port = BigShort((short)atoi(port)); + return 1; + } + else + { + a->port = BigShort(PORT_SERVER); + return 2; + } +} diff --git a/src/qcommon/net_ip.cpp b/src/qcommon/net_ip.cpp new file mode 100644 index 0000000..910301d --- /dev/null +++ b/src/qcommon/net_ip.cpp @@ -0,0 +1,1842 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "net.h" + +#include "cmd.h" +#include "cvar.h" +#include "msg.h" +#include "q_shared.h" +#include "qcommon.h" + +#ifdef _WIN32 +#include +#include +#if WINVER < 0x501 +#ifdef __MINGW32__ +// wspiapi.h isn't available on MinGW, so if it's +// present it's because the end user has added it +// and we should look for it in our tree +#include "wspiapi.h" +#else +#include +#endif +#else +#include +#endif + +typedef int socklen_t; +#ifdef ADDRESS_FAMILY +#define sa_family_t ADDRESS_FAMILY +#else +typedef unsigned short sa_family_t; +#endif + +#define EAGAIN WSAEWOULDBLOCK +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define ECONNRESET WSAECONNRESET +typedef u_long ioctlarg_t; +#define socketError WSAGetLastError() + +static WSADATA winsockdata; +static bool winsockInitialized = false; + +#else + +#if MAC_OS_X_VERSION_MIN_REQUIRED == 1020 +// needed for socklen_t on OSX 10.2 +#define _BSD_SOCKLEN_T_ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(__sun) && !defined(__sgi) +#include +#endif + +#ifdef __sun +#include +#endif + +typedef int SOCKET; +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#define closesocket close +#define ioctlsocket ioctl +typedef int ioctlarg_t; +#define socketError errno + +#endif + +static bool usingSocks = false; +static int networkingEnabled = 0; + +static cvar_t *net_enabled; +static cvar_t *net_alternateProtocols; + +static cvar_t *net_socksEnabled; +static cvar_t *net_socksServer; +static cvar_t *net_socksPort; +static cvar_t *net_socksUsername; +static cvar_t *net_socksPassword; + +static cvar_t *net_ip; +static cvar_t *net_ip6; +static cvar_t *net_ports[3]; +static cvar_t *net_port6s[3]; +static cvar_t *net_mcast6addr; +static cvar_t *net_mcast6iface; + +static cvar_t *net_dropsim; + +static struct sockaddr socksRelayAddr; + +static SOCKET ip_sockets[3] = {INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET}; +static SOCKET ip6_sockets[3] = {INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET}; +/* +TODO: accommodate +static SOCKET socks_socket = INVALID_SOCKET; +static SOCKET multicast6_socket = INVALID_SOCKET; +*/ + +// Keep track of currently joined multicast group. +static struct ipv6_mreq curgroup; +// And the currently bound address. +static struct sockaddr_in6 boundto; + +#ifndef IF_NAMESIZE +#define IF_NAMESIZE 16 +#endif + +// use an admin local address per default so that network admins can decide on how to handle quake3 traffic. +#define NET_MULTICAST_IP6 "ff04::696f:7175:616b:6533" + +#define MAX_IPS 32 + +typedef struct { + char ifname[IF_NAMESIZE]; + + netadrtype_t type; + sa_family_t family; + struct sockaddr_storage addr; + struct sockaddr_storage netmask; +} nip_localaddr_t; + +static nip_localaddr_t localIP[MAX_IPS]; +static int numIP; + +//============================================================================= + +/* +==================== +NET_ErrorString +==================== +*/ +const char *NET_ErrorString(void) +{ +#ifdef _WIN32 + // FIXME: replace with FormatMessage? + switch (socketError) + { + case WSAEINTR: + return "WSAEINTR"; + case WSAEBADF: + return "WSAEBADF"; + case WSAEACCES: + return "WSAEACCES"; + case WSAEDISCON: + return "WSAEDISCON"; + case WSAEFAULT: + return "WSAEFAULT"; + case WSAEINVAL: + return "WSAEINVAL"; + case WSAEMFILE: + return "WSAEMFILE"; + case WSAEWOULDBLOCK: + return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: + return "WSAEINPROGRESS"; + case WSAEALREADY: + return "WSAEALREADY"; + case WSAENOTSOCK: + return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: + return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: + return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: + return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: + return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: + return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: + return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: + return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: + return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: + return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: + return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: + return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: + return "WSAENETDOWN"; + case WSAENETUNREACH: + return "WSAENETUNREACH"; + case WSAENETRESET: + return "WSAENETRESET"; + case WSAECONNABORTED: + return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET: + return "WSAECONNRESET"; + case WSAENOBUFS: + return "WSAENOBUFS"; + case WSAEISCONN: + return "WSAEISCONN"; + case WSAENOTCONN: + return "WSAENOTCONN"; + case WSAESHUTDOWN: + return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: + return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: + return "WSAETIMEDOUT"; + case WSAECONNREFUSED: + return "WSAECONNREFUSED"; + case WSAELOOP: + return "WSAELOOP"; + case WSAENAMETOOLONG: + return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: + return "WSAEHOSTDOWN"; + case WSASYSNOTREADY: + return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: + return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: + return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: + return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: + return "WSATRY_AGAIN"; + case WSANO_RECOVERY: + return "WSANO_RECOVERY"; + case WSANO_DATA: + return "WSANO_DATA"; + default: + return "NO ERROR"; + } +#else + return strerror(socketError); +#endif +} + +static void NetadrToSockadr(netadr_t *a, struct sockaddr *s) +{ + if (a->type == NA_BROADCAST) + { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_port = a->port; +#ifdef __FreeBSD__ + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_ANY; +#else + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; +#endif + } + else if (a->type == NA_IP) + { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + ((struct sockaddr_in *)s)->sin_port = a->port; + } + else if (a->type == NA_IP6) + { + ((struct sockaddr_in6 *)s)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)s)->sin6_addr = *((struct in6_addr *)&a->ip6); + ((struct sockaddr_in6 *)s)->sin6_port = a->port; + ((struct sockaddr_in6 *)s)->sin6_scope_id = a->scope_id; + } + else if (a->type == NA_MULTICAST6) + { + ((struct sockaddr_in6 *)s)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)s)->sin6_addr = curgroup.ipv6mr_multiaddr; + ((struct sockaddr_in6 *)s)->sin6_port = a->port; + } +} + +static void SockadrToNetadr(struct sockaddr *s, netadr_t *a) +{ + if (s->sa_family == AF_INET) + { + a->type = NA_IP; + *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + a->port = ((struct sockaddr_in *)s)->sin_port; + } + else if (s->sa_family == AF_INET6) + { + a->type = NA_IP6; + memcpy(a->ip6, &((struct sockaddr_in6 *)s)->sin6_addr, sizeof(a->ip6)); + a->port = ((struct sockaddr_in6 *)s)->sin6_port; + a->scope_id = ((struct sockaddr_in6 *)s)->sin6_scope_id; + } + a->alternateProtocol = 0; +} + +static struct addrinfo *SearchAddrInfo(struct addrinfo *hints, sa_family_t family) +{ + while (hints) + { + if (hints->ai_family == family) return hints; + + hints = hints->ai_next; + } + + return NULL; +} + +/* +============= +Sys_StringToSockaddr +============= +*/ +static bool Sys_StringToSockaddr(const char *s, struct sockaddr *sadr, size_t sadr_len, sa_family_t family) +{ + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *search = NULL; + struct addrinfo *hintsp; + int retval; + + memset(sadr, '\0', sizeof(*sadr)); + memset(&hints, '\0', sizeof(hints)); + + hintsp = &hints; + hintsp->ai_family = family; + hintsp->ai_socktype = SOCK_DGRAM; + + retval = getaddrinfo(s, NULL, hintsp, &res); + + if (!retval) + { + if (family == AF_UNSPEC) + { + // Decide here and now which protocol family to use + if (net_enabled->integer & NET_PRIOV6) + { + if (net_enabled->integer & NET_ENABLEV6) search = SearchAddrInfo(res, AF_INET6); + + if (!search && (net_enabled->integer & NET_ENABLEV4)) search = SearchAddrInfo(res, AF_INET); + } + else + { + if (net_enabled->integer & NET_ENABLEV4) search = SearchAddrInfo(res, AF_INET); + + if (!search && (net_enabled->integer & NET_ENABLEV6)) search = SearchAddrInfo(res, AF_INET6); + } + } + else + search = SearchAddrInfo(res, family); + + if (search) + { + if (search->ai_addrlen > sadr_len) search->ai_addrlen = sadr_len; + + memcpy(sadr, search->ai_addr, search->ai_addrlen); + freeaddrinfo(res); + + return true; + } + else + Com_Printf("Sys_StringToSockaddr: Error resolving %s: No address of required type found.\n", s); + } + else + Com_Printf("Sys_StringToSockaddr: Error resolving %s: %s\n", s, gai_strerror(retval)); + + if (res) freeaddrinfo(res); + + return false; +} + +/* +============= +Sys_SockaddrToString +============= +*/ +static void Sys_SockaddrToString(char *dest, int destlen, struct sockaddr *input) +{ + socklen_t inputlen; + + if (input->sa_family == AF_INET6) + inputlen = sizeof(struct sockaddr_in6); + else + inputlen = sizeof(struct sockaddr_in); + + if (getnameinfo(input, inputlen, dest, destlen, NULL, 0, NI_NUMERICHOST) && destlen > 0) *dest = '\0'; +} + +/* +============= +Sys_StringToAdr +============= +*/ +bool Sys_StringToAdr(const char *s, netadr_t *a, netadrtype_t family) +{ + struct sockaddr_storage sadr; + sa_family_t fam; + + switch (family) + { + case NA_IP: + fam = AF_INET; + break; + case NA_IP6: + fam = AF_INET6; + break; + default: + fam = AF_UNSPEC; + break; + } + if (!Sys_StringToSockaddr(s, (struct sockaddr *)&sadr, sizeof(sadr), fam)) + { + return false; + } + + SockadrToNetadr((struct sockaddr *)&sadr, a); + return true; +} + +/* +=================== +NET_CompareBaseAdrMask + +Compare without port, and up to the bit number given in netmask. +=================== +*/ +bool NET_CompareBaseAdrMask(netadr_t a, netadr_t b, int netmask) +{ + uint8_t cmpmask, *addra, *addrb; + int curbyte; + + if (a.alternateProtocol != b.alternateProtocol) return false; + + if (a.type != b.type) return false; + + if (a.type == NA_LOOPBACK) return true; + + if (a.type == NA_IP) + { + addra = (uint8_t *)&a.ip; + addrb = (uint8_t *)&b.ip; + + if (netmask < 0 || netmask > 32) netmask = 32; + } + else if (a.type == NA_IP6) + { + addra = (uint8_t *)&a.ip6; + addrb = (uint8_t *)&b.ip6; + + if (netmask < 0 || netmask > 128) netmask = 128; + } + else + { + Com_Printf("NET_CompareBaseAdr: bad address type\n"); + return false; + } + + curbyte = netmask >> 3; + + if (curbyte && memcmp(addra, addrb, curbyte)) return false; + + netmask &= 0x07; + if (netmask) + { + cmpmask = (1 << netmask) - 1; + cmpmask <<= 8 - netmask; + + if ((addra[curbyte] & cmpmask) == (addrb[curbyte] & cmpmask)) return true; + } + else + return true; + + return false; +} + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +bool NET_CompareBaseAdr(netadr_t a, netadr_t b) { return NET_CompareBaseAdrMask(a, b, -1); } +const char *NET_AdrToString(netadr_t a) +{ + static char s[NET_ADDRSTRMAXLEN]; + + if (a.type == NA_LOOPBACK) + Com_sprintf(s, sizeof(s), "loopback"); + else if (a.type == NA_IP || a.type == NA_IP6) + { + struct sockaddr_storage sadr; + + memset(&sadr, 0, sizeof(sadr)); + NetadrToSockadr(&a, (struct sockaddr *)&sadr); + Sys_SockaddrToString(s, sizeof(s), (struct sockaddr *)&sadr); + } + + return s; +} + +const char *NET_AdrToStringwPort(netadr_t a) +{ + static char s[NET_ADDRSTRMAXLEN]; + + if (a.type == NA_LOOPBACK) + Com_sprintf(s, sizeof(s), "loopback"); + else if (a.type == NA_IP) + Com_sprintf(s, sizeof(s), "%s:%hu", NET_AdrToString(a), ntohs(a.port)); + else if (a.type == NA_IP6) + Com_sprintf(s, sizeof(s), "[%s]:%hu", NET_AdrToString(a), ntohs(a.port)); + + return s; +} + +bool NET_CompareAdr(netadr_t a, netadr_t b) +{ + if (!NET_CompareBaseAdr(a, b)) return false; + + if (a.type == NA_IP || a.type == NA_IP6) + { + if (a.port == b.port) return true; + } + else + return true; + + return false; +} + +bool NET_IsLocalAddress(netadr_t adr) { return (bool)(adr.type == NA_LOOPBACK); } +//============================================================================= + +/* +================== +NET_GetPacket + +Receive one packet +================== +*/ +bool NET_GetPacket(netadr_t *net_from, msg_t *net_message, fd_set *fdr) +{ + int a; + int ret; + struct sockaddr_storage from; + socklen_t fromlen; + int err; + + for (a = 0; a < 3; ++a) + { + // indent + if (ip_sockets[a] != INVALID_SOCKET && FD_ISSET(ip_sockets[a], fdr)) + { + fromlen = sizeof(from); + ret = recvfrom( + ip_sockets[a], (char *)net_message->data, net_message->maxsize, 0, (struct sockaddr *)&from, &fromlen); + + if (ret == SOCKET_ERROR) + { + err = socketError; + + if (err != EAGAIN && err != ECONNRESET) Com_Printf("NET_GetPacket: %s\n", NET_ErrorString()); + } + else + { + memset(((struct sockaddr_in *)&from)->sin_zero, 0, 8); + + if (usingSocks && memcmp(&from, &socksRelayAddr, fromlen) == 0) + { + if (ret < 10 || net_message->data[0] != 0 || net_message->data[1] != 0 || + net_message->data[2] != 0 || net_message->data[3] != 1) + { + return false; + } + net_from->type = NA_IP; + net_from->ip[0] = net_message->data[4]; + net_from->ip[1] = net_message->data[5]; + net_from->ip[2] = net_message->data[6]; + net_from->ip[3] = net_message->data[7]; + net_from->port = *(short *)&net_message->data[8]; + net_message->readcount = 10; + } + else + { + SockadrToNetadr((struct sockaddr *)&from, net_from); + net_message->readcount = 0; + } + + net_from->alternateProtocol = a; + + if (ret >= net_message->maxsize) + { + Com_Printf("Oversize packet from %s\n", NET_AdrToString(*net_from)); + return false; + } + + net_message->cursize = ret; + return true; + } + } + + if (ip6_sockets[a] != INVALID_SOCKET && FD_ISSET(ip6_sockets[a], fdr)) + { + fromlen = sizeof(from); + ret = recvfrom( + ip6_sockets[a], (char *)net_message->data, net_message->maxsize, 0, (struct sockaddr *)&from, &fromlen); + + if (ret == SOCKET_ERROR) + { + err = socketError; + + if (err != EAGAIN && err != ECONNRESET) Com_Printf("NET_GetPacket: %s\n", NET_ErrorString()); + } + else + { + SockadrToNetadr((struct sockaddr *)&from, net_from); + net_message->readcount = 0; + + net_from->alternateProtocol = a; + + if (ret >= net_message->maxsize) + { + Com_Printf("Oversize packet from %s\n", NET_AdrToString(*net_from)); + return false; + } + + net_message->cursize = ret; + return true; + } + } + + /* + TODO: accommodate + if(multicast6_socket != INVALID_SOCKET && multicast6_socket != ip6_socket && FD_ISSET(multicast6_socket, fdr)) + { + fromlen = sizeof(from); + ret = recvfrom(multicast6_socket, (char*)net_message->data, net_message->maxsize, 0, (struct sockaddr *) + &from, &fromlen); + + if (ret == SOCKET_ERROR) + { + err = socketError; + + if( err != EAGAIN && err != ECONNRESET ) + Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); + } + else + { + SockadrToNetadr((struct sockaddr *) &from, net_from); + net_message->readcount = 0; + + if(ret >= net_message->maxsize) + { + Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); + return false; + } + + net_message->cursize = ret; + return true; + } + } + */ + // outdent + } + + return false; +} + +//============================================================================= + +static char socksBuf[4096]; + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket(int length, const void *data, netadr_t to) +{ + int ret = SOCKET_ERROR; + struct sockaddr_storage addr; + + if (to.type != NA_BROADCAST && to.type != NA_IP && to.type != NA_IP6 && to.type != NA_MULTICAST6) + { + Com_Error(ERR_FATAL, "Sys_SendPacket: bad address type"); + return; + } + + if ((ip_sockets[to.alternateProtocol] == INVALID_SOCKET && to.type == NA_IP) || + (ip_sockets[to.alternateProtocol] == INVALID_SOCKET && to.type == NA_BROADCAST) || + (ip6_sockets[to.alternateProtocol] == INVALID_SOCKET && to.type == NA_IP6) || + (/* TODO: accommodate ip6_socket == INVALID_SOCKET && */ to.type == NA_MULTICAST6)) + return; + + if (to.type == NA_MULTICAST6 && (net_enabled->integer & NET_DISABLEMCAST)) return; + + memset(&addr, 0, sizeof(addr)); + NetadrToSockadr(&to, (struct sockaddr *)&addr); + + if (usingSocks && to.type == NA_IP) + { + socksBuf[0] = 0; // reserved + socksBuf[1] = 0; + socksBuf[2] = 0; // fragment (not fragmented) + socksBuf[3] = 1; // address type: IPV4 + *(int *)&socksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr; + *(short *)&socksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port; + memcpy(&socksBuf[10], data, length); + ret = sendto(ip_sockets[to.alternateProtocol], (const char *)socksBuf, length + 10, 0, &socksRelayAddr, + sizeof(socksRelayAddr)); + } + else + { + if (addr.ss_family == AF_INET) + ret = sendto(ip_sockets[to.alternateProtocol], (const char *)data, length, 0, (struct sockaddr *)&addr, + sizeof(struct sockaddr_in)); + else if (addr.ss_family == AF_INET6) + ret = sendto(ip6_sockets[to.alternateProtocol], (const char *)data, length, 0, (struct sockaddr *)&addr, + sizeof(struct sockaddr_in6)); + } + if (ret == SOCKET_ERROR) + { + int err = socketError; + + // wouldblock is silent + if (err == EAGAIN) + { + return; + } + + // some PPP links do not allow broadcasts and return an error + if ((err == EADDRNOTAVAIL) && ((to.type == NA_BROADCAST))) + { + return; + } + + Com_Printf("Sys_SendPacket: %s\n", NET_ErrorString()); + } +} + +//============================================================================= + +/* +================== +Sys_IsLANAddress + +LAN clients will have their rate var ignored +================== +*/ +bool Sys_IsLANAddress(netadr_t adr) +{ + int index, run, addrsize; + bool differed; + uint8_t *compareadr, *comparemask, *compareip; + + if (adr.type == NA_LOOPBACK) + { + return true; + } + + if (adr.type == NA_IP) + { + // RFC1918: + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + if (adr.ip[0] == 10) return true; + if (adr.ip[0] == 172 && (adr.ip[1] & 0xf0) == 16) return true; + if (adr.ip[0] == 192 && adr.ip[1] == 168) return true; + + if (adr.ip[0] == 127) return true; + } + else if (adr.type == NA_IP6) + { + if (adr.ip6[0] == 0xfe && (adr.ip6[1] & 0xc0) == 0x80) return true; + if ((adr.ip6[0] & 0xfe) == 0xfc) return true; + } + + // Now compare against the networks this computer is member of. + for (index = 0; index < numIP; index++) + { + if (localIP[index].type == adr.type) + { + if (adr.type == NA_IP) + { + compareip = (uint8_t *)&((struct sockaddr_in *)&localIP[index].addr)->sin_addr.s_addr; + comparemask = (uint8_t *)&((struct sockaddr_in *)&localIP[index].netmask)->sin_addr.s_addr; + compareadr = adr.ip; + + addrsize = sizeof(adr.ip); + } + else + { + // TODO? should we check the scope_id here? + + compareip = (uint8_t *)&((struct sockaddr_in6 *)&localIP[index].addr)->sin6_addr; + comparemask = (uint8_t *)&((struct sockaddr_in6 *)&localIP[index].netmask)->sin6_addr; + compareadr = adr.ip6; + + addrsize = sizeof(adr.ip6); + } + + differed = false; + for (run = 0; run < addrsize; run++) + { + if ((compareip[run] & comparemask[run]) != (compareadr[run] & comparemask[run])) + { + differed = true; + break; + } + } + + if (!differed) return true; + } + } + + return false; +} + +/* +================== +Sys_ShowIP +================== +*/ +void Sys_ShowIP(void) +{ + int i; + char addrbuf[NET_ADDRSTRMAXLEN]; + + for (i = 0; i < numIP; i++) + { + Sys_SockaddrToString(addrbuf, sizeof(addrbuf), (struct sockaddr *)&localIP[i].addr); + + if (localIP[i].type == NA_IP) + Com_Printf("IP: %s\n", addrbuf); + else if (localIP[i].type == NA_IP6) + Com_Printf("IP6: %s\n", addrbuf); + } +} + +//============================================================================= + +/* +==================== +NET_IPSocket +==================== +*/ +SOCKET NET_IPSocket(int alternateProtocol, char *net_interface, int port, int *err) +{ + SOCKET newsocket; + struct sockaddr_in address; + ioctlarg_t _true = 1; + int i = 1; + + *err = 0; + + if (net_interface) + { + Com_Printf("Opening%s IP socket: %s:%i\n", + (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), net_interface, + port); + } + else + { + Com_Printf("Opening%s IP socket: 0.0.0.0:%i\n", + (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), port); + } + + if ((newsocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + *err = socketError; + Com_Printf("WARNING: NET_IPSocket: socket: %s\n", NET_ErrorString()); + return newsocket; + } + // make it non-blocking + if (ioctlsocket(newsocket, FIONBIO, &_true) == SOCKET_ERROR) + { + Com_Printf("WARNING: NET_IPSocket: ioctl FIONBIO: %s\n", NET_ErrorString()); + *err = socketError; + closesocket(newsocket); + return INVALID_SOCKET; + } + + // make it broadcast capable + if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) == SOCKET_ERROR) + { + Com_Printf("WARNING: NET_IPSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString()); + } + + if (!net_interface || !net_interface[0]) + { + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + } + else + { + if (!Sys_StringToSockaddr(net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET)) + { + closesocket(newsocket); + return INVALID_SOCKET; + } + } + + if (port == PORT_ANY) + { + address.sin_port = 0; + } + else + { + address.sin_port = htons((short)port); + } + + if (bind(newsocket, (const sockaddr *)&address, sizeof(address)) == SOCKET_ERROR) + { + Com_Printf("WARNING: NET_IPSocket: bind: %s\n", NET_ErrorString()); + *err = socketError; + closesocket(newsocket); + return INVALID_SOCKET; + } + + return newsocket; +} + +/* +==================== +NET_IP6Socket +==================== +*/ +SOCKET NET_IP6Socket(int alternateProtocol, char *net_interface, int port, struct sockaddr_in6 *bindto, int *err) +{ + SOCKET newsocket; + struct sockaddr_in6 address; + ioctlarg_t _true = 1; + + *err = 0; + + if (net_interface) + { + // Print the name in brackets if there is a colon: + if (Q_CountChar(net_interface, ':')) + Com_Printf("Opening%s IP6 socket: [%s]:%i\n", + (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), net_interface, + port); + else + Com_Printf("Opening%s IP6 socket: %s:%i\n", + (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), net_interface, + port); + } + else + Com_Printf("Opening%s IP6 socket: [::]:%i\n", + (alternateProtocol == 2 ? " alternate-2" : alternateProtocol == 1 ? " alternate-1" : ""), port); + + if ((newsocket = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + *err = socketError; + Com_Printf("WARNING: NET_IP6Socket: socket: %s\n", NET_ErrorString()); + return newsocket; + } + + // make it non-blocking + if (ioctlsocket(newsocket, FIONBIO, &_true) == SOCKET_ERROR) + { + Com_Printf("WARNING: NET_IP6Socket: ioctl FIONBIO: %s\n", NET_ErrorString()); + *err = socketError; + closesocket(newsocket); + return INVALID_SOCKET; + } + +#ifdef IPV6_V6ONLY + { + int i = 1; + + // ipv4 addresses should not be allowed to connect via this socket. + if (setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i, sizeof(i)) == SOCKET_ERROR) + { + // win32 systems don't seem to support this anyways. + Com_DPrintf("WARNING: NET_IP6Socket: setsockopt IPV6_V6ONLY: %s\n", NET_ErrorString()); + } + } +#endif + + if (!net_interface || !net_interface[0]) + { + address.sin6_family = AF_INET6; + address.sin6_addr = in6addr_any; + } + else + { + if (!Sys_StringToSockaddr(net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET6)) + { + closesocket(newsocket); + return INVALID_SOCKET; + } + } + + if (port == PORT_ANY) + { + address.sin6_port = 0; + } + else + { + address.sin6_port = htons((short)port); + } + + if (bind(newsocket, (const sockaddr *)&address, sizeof(address)) == SOCKET_ERROR) + { + Com_Printf("WARNING: NET_IP6Socket: bind: %s\n", NET_ErrorString()); + *err = socketError; + closesocket(newsocket); + return INVALID_SOCKET; + } + + if (bindto) *bindto = address; + + return newsocket; +} + +/* +==================== +NET_SetMulticast +Set the current multicast group +==================== +*/ +void NET_SetMulticast6(void) +{ + struct sockaddr_in6 addr; + + if (!*net_mcast6addr->string || + !Sys_StringToSockaddr(net_mcast6addr->string, (struct sockaddr *)&addr, sizeof(addr), AF_INET6)) + { + Com_Printf( + "WARNING: NET_JoinMulticast6: Incorrect multicast address given, " + "please set cvar %s to a sane value.\n", + net_mcast6addr->name); + + Cvar_SetValue(net_enabled->name, net_enabled->integer | NET_DISABLEMCAST); + + return; + } + + memcpy(&curgroup.ipv6mr_multiaddr, &addr.sin6_addr, sizeof(curgroup.ipv6mr_multiaddr)); + + if (*net_mcast6iface->string) + { +#ifdef _WIN32 + curgroup.ipv6mr_interface = net_mcast6iface->integer; +#else + curgroup.ipv6mr_interface = if_nametoindex(net_mcast6iface->string); +#endif + } + else + curgroup.ipv6mr_interface = 0; +} + +/* +==================== +NET_JoinMulticast +Join an ipv6 multicast group +==================== +*/ +void NET_JoinMulticast6(void) +{ + /* + TODO: accommodate + int err; + + if(ip6_socket == INVALID_SOCKET || multicast6_socket != INVALID_SOCKET || (net_enabled->integer & NET_DISABLEMCAST)) + return; + + if(IN6_IS_ADDR_MULTICAST(&boundto.sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(&boundto.sin6_addr)) + { + // The way the socket was bound does not prohibit receiving multi-cast packets. So we don't need to open a + new one. + multicast6_socket = ip6_socket; + } + else + { + if((multicast6_socket = NET_IP6Socket(net_mcast6addr->string, ntohs(boundto.sin6_port), NULL, &err)) == + INVALID_SOCKET) + { + // If the OS does not support binding to multicast addresses, like WinXP, at least try with the + normal file descriptor. + multicast6_socket = ip6_socket; + } + } + + if(curgroup.ipv6mr_interface) + { + if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, + (char *) &curgroup.ipv6mr_interface, sizeof(curgroup.ipv6mr_interface)) < 0) + { + Com_Printf("NET_JoinMulticast6: Couldn't set scope on multicast socket: %s\n", NET_ErrorString()); + + if(multicast6_socket != ip6_socket) + { + closesocket(multicast6_socket); + multicast6_socket = INVALID_SOCKET; + return; + } + } + } + + if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *) &curgroup, sizeof(curgroup))) + { + Com_Printf("NET_JoinMulticast6: Couldn't join multicast group: %s\n", NET_ErrorString()); + + if(multicast6_socket != ip6_socket) + { + closesocket(multicast6_socket); + multicast6_socket = INVALID_SOCKET; + return; + } + } + */ +} + +void NET_LeaveMulticast6() +{ + /* + TODO: accommodate + if(multicast6_socket != INVALID_SOCKET) + { + if(multicast6_socket != ip6_socket) + closesocket(multicast6_socket); + else + setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *) &curgroup, sizeof(curgroup)); + + multicast6_socket = INVALID_SOCKET; + } + */ +} + +/* +==================== +NET_OpenSocks +==================== +*/ +void NET_OpenSocks(int port) +{ + /* + TODO: accommodate + struct sockaddr_in address; + struct hostent *h; + int len; + bool rfc1929; + unsigned char buf[64]; + + usingSocks = false; + + Com_Printf( "Opening connection to SOCKS server.\n" ); + + if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) { + Com_Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() ); + return; + } + + h = gethostbyname( net_socksServer->string ); + if ( h == NULL ) { + Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() ); + return; + } + if ( h->h_addrtype != AF_INET ) { + Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" ); + return; + } + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(int *)h->h_addr_list[0]; + address.sin_port = htons( (short)net_socksPort->integer ); + + if ( connect( socks_socket, (struct sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() ); + return; + } + + // send socks authentication handshake + if ( *net_socksUsername->string || *net_socksPassword->string ) { + rfc1929 = true; + } + else { + rfc1929 = false; + } + + buf[0] = 5; // SOCKS version + // method count + if ( rfc1929 ) { + buf[1] = 2; + len = 4; + } + else { + buf[1] = 1; + len = 3; + } + buf[2] = 0; // method #1 - method id #00: no authentication + if ( rfc1929 ) { + buf[2] = 2; // method #2 - method id #02: username/password + } + if ( send( socks_socket, (void *)buf, len, 0 ) == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (void *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 5 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + switch( buf[1] ) { + case 0: // no authentication + break; + case 2: // username/password authentication + break; + default: + Com_Printf( "NET_OpenSocks: request denied\n" ); + return; + } + + // do username/password authentication if needed + if ( buf[1] == 2 ) { + int ulen; + int plen; + + // build the request + ulen = strlen( net_socksUsername->string ); + plen = strlen( net_socksPassword->string ); + + buf[0] = 1; // username/password authentication version + buf[1] = ulen; + if ( ulen ) { + memcpy( &buf[2], net_socksUsername->string, ulen ); + } + buf[2 + ulen] = plen; + if ( plen ) { + memcpy( &buf[3 + ulen], net_socksPassword->string, plen ); + } + + // send it + if ( send( socks_socket, (void *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (void *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 1 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + if ( buf[1] != 0 ) { + Com_Printf( "NET_OpenSocks: authentication failed\n" ); + return; + } + } + + // send the UDP associate request + buf[0] = 5; // SOCKS version + buf[1] = 3; // command: UDP associate + buf[2] = 0; // reserved + buf[3] = 1; // address type: IPV4 + *(int *)&buf[4] = INADDR_ANY; + *(short *)&buf[8] = htons( (short)port ); // port + if ( send( socks_socket, (void *)buf, 10, 0 ) == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (void *)buf, 64, 0 ); + if( len == SOCKET_ERROR ) { + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if( len < 2 || buf[0] != 5 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + // check completion code + if( buf[1] != 0 ) { + Com_Printf( "NET_OpenSocks: request denied: %i\n", buf[1] ); + return; + } + if( buf[3] != 1 ) { + Com_Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] ); + return; + } + ((struct sockaddr_in *)&socksRelayAddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&socksRelayAddr)->sin_addr.s_addr = *(int *)&buf[4]; + ((struct sockaddr_in *)&socksRelayAddr)->sin_port = *(short *)&buf[8]; + memset( ((struct sockaddr_in *)&socksRelayAddr)->sin_zero, 0, 8 ); + + usingSocks = true; + */ +} + +/* +===================== +NET_AddLocalAddress +===================== +*/ +static void NET_AddLocalAddress(char *ifname, struct sockaddr *addr, struct sockaddr *netmask) +{ + int addrlen; + sa_family_t family; + + // only add addresses that have all required info. + if (!addr || !netmask || !ifname) return; + + family = addr->sa_family; + + if (numIP < MAX_IPS) + { + if (family == AF_INET) + { + addrlen = sizeof(struct sockaddr_in); + localIP[numIP].type = NA_IP; + } + else if (family == AF_INET6) + { + addrlen = sizeof(struct sockaddr_in6); + localIP[numIP].type = NA_IP6; + } + else + return; + + Q_strncpyz(localIP[numIP].ifname, ifname, sizeof(localIP[numIP].ifname)); + + localIP[numIP].family = family; + + memcpy(&localIP[numIP].addr, addr, addrlen); + memcpy(&localIP[numIP].netmask, netmask, addrlen); + + numIP++; + } +} + +#if defined(__linux__) || defined(__APPLE__) || defined(__BSD__) +static void NET_GetLocalAddress(void) +{ + struct ifaddrs *ifap, *search; + + numIP = 0; + + if (getifaddrs(&ifap)) + Com_Printf("NET_GetLocalAddress: Unable to get list of network interfaces: %s\n", NET_ErrorString()); + else + { + for (search = ifap; search; search = search->ifa_next) + { + // Only add interfaces that are up. + if (ifap->ifa_flags & IFF_UP) NET_AddLocalAddress(search->ifa_name, search->ifa_addr, search->ifa_netmask); + } + + freeifaddrs(ifap); + + Sys_ShowIP(); + } +} +#else +static void NET_GetLocalAddress(void) +{ + char hostname[256]; + struct addrinfo hint; + struct addrinfo *res = NULL; + + numIP = 0; + + if (gethostname(hostname, 256) == SOCKET_ERROR) return; + + memset(&hint, 0, sizeof(hint)); + + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_DGRAM; + + if (!getaddrinfo(hostname, NULL, &hint, &res)) + { + struct sockaddr_in mask4; + struct sockaddr_in6 mask6; + struct addrinfo *search; + + /* On operating systems where it's more difficult to find out the configured interfaces, we'll just assume a + * netmask with all bits set. */ + + memset(&mask4, 0, sizeof(mask4)); + memset(&mask6, 0, sizeof(mask6)); + mask4.sin_family = AF_INET; + memset(&mask4.sin_addr.s_addr, 0xFF, sizeof(mask4.sin_addr.s_addr)); + mask6.sin6_family = AF_INET6; + memset(&mask6.sin6_addr, 0xFF, sizeof(mask6.sin6_addr)); + + // add all IPs from returned list. + for (search = res; search; search = search->ai_next) + { + if (search->ai_family == AF_INET) + NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *)&mask4); + else if (search->ai_family == AF_INET6) + NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *)&mask6); + } + + Sys_ShowIP(); + } + + if (res) freeaddrinfo(res); +} +#endif + +/* +==================== +NET_OpenIP +==================== +*/ +void NET_OpenIP(void) +{ + int a; + int i; + int err; + int ports[3]; + int port6s[3]; + + for (a = 0; a < 3; ++a) + { + ports[a] = net_ports[a]->integer; + port6s[a] = net_port6s[a]->integer; + } + + NET_GetLocalAddress(); + + for (a = 0; a < 3; ++a) + { + // indent + if (a == 0 && (net_alternateProtocols->integer & NET_DISABLEPRIMPROTO)) continue; + if (a == 1 && !(net_alternateProtocols->integer & NET_ENABLEALT1PROTO)) continue; + if (a == 2 && !(net_alternateProtocols->integer & NET_ENABLEALT2PROTO)) continue; + + // automatically scan for a valid port, so multiple + // dedicated servers can be started without requiring + // a different net_port for each one + + if (net_enabled->integer & NET_ENABLEV6) + { + for (i = 0; i < 10; i++) + { + ip6_sockets[a] = NET_IP6Socket(a, net_ip6->string, port6s[a] + i, &boundto, &err); + if (ip6_sockets[a] != INVALID_SOCKET) + { + Cvar_SetValue((a == 2 ? "net_alt2port6" : a == 1 ? "net_alt1port6" : "net_port6"), port6s[a] + i); + break; + } + else + { + if (err == EAFNOSUPPORT) break; + } + } + if (ip6_sockets[a] == INVALID_SOCKET) + Com_Printf("WARNING: Couldn't bind to a%s v6 ip address.\n", + (a == 2 ? "n alternate-2" : a == 1 ? "n alternate-1" : "")); + } + + if (net_enabled->integer & NET_ENABLEV4) + { + for (i = 0; i < 10; i++) + { + ip_sockets[a] = NET_IPSocket(a, net_ip->string, ports[a] + i, &err); + if (ip_sockets[a] != INVALID_SOCKET) + { + Cvar_SetValue((a == 2 ? "net_alt2port" : a == 1 ? "net_alt1port" : "net_port"), ports[a] + i); + + if (net_socksEnabled->integer) NET_OpenSocks(ports[a] + i); + + break; + } + else + { + if (err == EAFNOSUPPORT) break; + } + } + + if (ip_sockets[a] == INVALID_SOCKET) + Com_Printf("WARNING: Couldn't bind to a%s v4 ip address.\n", + (a == 2 ? "n alternate-2" : a == 1 ? "n alternate-1" : "")); + } + // outdent + } +} + +//=================================================================== + +/* +==================== +NET_GetCvars +==================== +*/ +static bool NET_GetCvars(void) +{ + int modified; + int a; + +#ifdef DEDICATED + // I want server owners to explicitly turn on ipv6 support. + net_enabled = Cvar_Get("net_enabled", "1", CVAR_LATCH | CVAR_ARCHIVE); +#else + /* End users have it enabled so they can connect to ipv6-only hosts, but ipv4 will be + * used if available due to ping */ + net_enabled = Cvar_Get("net_enabled", "3", CVAR_LATCH | CVAR_ARCHIVE); +#endif + modified = net_enabled->modified; + net_enabled->modified = false; + + net_alternateProtocols = Cvar_Get("net_alternateProtocols", "3", CVAR_LATCH | CVAR_ARCHIVE); + modified += net_alternateProtocols->modified; + net_alternateProtocols->modified = false; + + net_ip = Cvar_Get("net_ip", "0.0.0.0", CVAR_LATCH); + modified += net_ip->modified; + net_ip->modified = false; + + net_ip6 = Cvar_Get("net_ip6", "::", CVAR_LATCH); + modified += net_ip6->modified; + net_ip6->modified = false; + + for (a = 0; a < 3; ++a) + { + net_ports[a] = Cvar_Get((a == 2 ? "net_alt2port" : a == 1 ? "net_alt1port" : "net_port"), + (a == 2 ? XSTRING(ALT2PORT_SERVER) : a == 1 ? XSTRING(ALT1PORT_SERVER) : XSTRING(PORT_SERVER)), CVAR_LATCH); + modified += net_ports[a]->modified; + net_ports[a]->modified = false; + + net_port6s[a] = Cvar_Get((a == 2 ? "net_alt2port6" : a == 1 ? "net_alt1port6" : "net_port6"), + (a == 2 ? XSTRING(ALT2PORT_SERVER) : a == 1 ? XSTRING(ALT1PORT_SERVER) : XSTRING(PORT_SERVER)), CVAR_LATCH); + modified += net_port6s[a]->modified; + net_port6s[a]->modified = false; + } + + // Some cvars for configuring multicast options which facilitates scanning for servers on local subnets. + net_mcast6addr = Cvar_Get("net_mcast6addr", NET_MULTICAST_IP6, CVAR_LATCH | CVAR_ARCHIVE); + modified += net_mcast6addr->modified; + net_mcast6addr->modified = false; + +#ifdef _WIN32 + net_mcast6iface = Cvar_Get("net_mcast6iface", "0", CVAR_LATCH | CVAR_ARCHIVE); +#else + net_mcast6iface = Cvar_Get("net_mcast6iface", "", CVAR_LATCH | CVAR_ARCHIVE); +#endif + modified += net_mcast6iface->modified; + net_mcast6iface->modified = false; + + net_socksEnabled = Cvar_Get("net_socksEnabled", "0", CVAR_LATCH | CVAR_ARCHIVE); + modified += net_socksEnabled->modified; + net_socksEnabled->modified = false; + + net_socksServer = Cvar_Get("net_socksServer", "", CVAR_LATCH | CVAR_ARCHIVE); + modified += net_socksServer->modified; + net_socksServer->modified = false; + + net_socksPort = Cvar_Get("net_socksPort", "1080", CVAR_LATCH | CVAR_ARCHIVE); + modified += net_socksPort->modified; + net_socksPort->modified = false; + + net_socksUsername = Cvar_Get("net_socksUsername", "", CVAR_LATCH | CVAR_ARCHIVE); + modified += net_socksUsername->modified; + net_socksUsername->modified = false; + + net_socksPassword = Cvar_Get("net_socksPassword", "", CVAR_LATCH | CVAR_ARCHIVE); + modified += net_socksPassword->modified; + net_socksPassword->modified = false; + + net_dropsim = Cvar_Get("net_dropsim", "", CVAR_TEMP); + + return modified ? true : false; +} + +/* +==================== +NET_Config +==================== +*/ +void NET_Config(bool enableNetworking) +{ + bool modified; + bool stop; + bool start; + int a; + + // get any latched changes to cvars + modified = NET_GetCvars(); + + if (!net_enabled->integer) + { + enableNetworking = false; + } + + // if enable state is the same and no cvars were modified, we have nothing to do + if (enableNetworking == networkingEnabled && !modified) + { + return; + } + + if (enableNetworking == networkingEnabled) + { + if (enableNetworking) + { + stop = true; + start = true; + } + else + { + stop = false; + start = false; + } + } + else + { + if (enableNetworking) + { + stop = false; + start = true; + } + else + { + stop = true; + start = false; + } + networkingEnabled = enableNetworking; + } + + if (stop) + { + for (a = 0; a < 3; ++a) + { + if (ip_sockets[a] != INVALID_SOCKET) + { + closesocket(ip_sockets[a]); + ip_sockets[a] = INVALID_SOCKET; + } + + if (ip6_sockets[a] != INVALID_SOCKET) + { + closesocket(ip6_sockets[a]); + ip6_sockets[a] = INVALID_SOCKET; + } + } + + /* + TODO: accommodate + if(multicast6_socket != INVALID_SOCKET) + { + if(multicast6_socket != ip6_socket) + closesocket(multicast6_socket); + + multicast6_socket = INVALID_SOCKET; + } + + if ( socks_socket != INVALID_SOCKET ) { + closesocket( socks_socket ); + socks_socket = INVALID_SOCKET; + } + */ + } + + if (start) + { + if (net_enabled->integer) + { + NET_OpenIP(); + NET_SetMulticast6(); + } + } +} + +/* +==================== +NET_Init +==================== +*/ +void NET_Init(void) +{ +#ifdef _WIN32 + int r; + + r = WSAStartup(MAKEWORD(1, 1), &winsockdata); + if (r) + { + Com_Printf("WARNING: Winsock initialization failed, returned %d\n", r); + return; + } + + winsockInitialized = true; + Com_Printf("Winsock Initialized\n"); +#endif + + NET_Config(true); + + Cmd_AddCommand("net_restart", NET_Restart_f); +} + +/* +==================== +NET_Shutdown +==================== +*/ +void NET_Shutdown(void) +{ + if (!networkingEnabled) + { + return; + } + + NET_Config(false); + +#ifdef _WIN32 + WSACleanup(); + winsockInitialized = false; +#endif +} + +/* +==================== +NET_Event + +Called from NET_Sleep which uses select() to determine which sockets have seen action. +==================== +*/ + +void NET_Event(fd_set *fdr) +{ + uint8_t bufData[MAX_MSGLEN + 1]; + netadr_t from; + msg_t netmsg; + + memset(&from, 0, sizeof(from)); + + while (1) + { + MSG_Init(&netmsg, bufData, sizeof(bufData)); + + if (NET_GetPacket(&from, &netmsg, fdr)) + { + if (net_dropsim->value > 0.0f && net_dropsim->value <= 100.0f) + { + // com_dropsim->value percent of incoming packets get dropped. + if (rand() < (int)(((double)RAND_MAX) / 100.0 * (double)net_dropsim->value)) + continue; // drop this packet + } + + if (com_sv_running->integer) + Com_RunAndTimeServerPacket(&from, &netmsg); + else + CL_PacketEvent(from, &netmsg); + } + else + break; + } +} + +/* +==================== +NET_Sleep + +Sleeps msec or until something happens on the network +==================== +*/ +void NET_Sleep(int msec) +{ + struct timeval timeout; + fd_set fdr; + int retval; + int a; + SOCKET highestfd = INVALID_SOCKET; + + if (msec < 0) msec = 0; + + FD_ZERO(&fdr); + + for (a = 0; a < 3; ++a) + { + if (ip_sockets[a] != INVALID_SOCKET) + { + FD_SET(ip_sockets[a], &fdr); + + if (highestfd == INVALID_SOCKET || ip_sockets[a] > highestfd) highestfd = ip_sockets[a]; + } + if (ip6_sockets[a] != INVALID_SOCKET) + { + FD_SET(ip6_sockets[a], &fdr); + + if (highestfd == INVALID_SOCKET || ip6_sockets[a] > highestfd) highestfd = ip6_sockets[a]; + } + } + +#ifdef _WIN32 + if (highestfd == INVALID_SOCKET) + { + // windows ain't happy when select is called without valid FDs + SleepEx(msec, 0); + return; + } +#endif + + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + retval = select(highestfd + 1, &fdr, NULL, NULL, &timeout); + + if (retval == SOCKET_ERROR) + Com_Printf("Warning: select() syscall failed: %s\n", NET_ErrorString()); + else if (retval > 0) + NET_Event(&fdr); +} + +/* +==================== +NET_Restart_f +==================== +*/ +void NET_Restart_f(void) { NET_Config(true); } diff --git a/src/qcommon/parse.cpp b/src/qcommon/parse.cpp new file mode 100644 index 0000000..aa72dec --- /dev/null +++ b/src/qcommon/parse.cpp @@ -0,0 +1,3725 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "files.h" +#include "q_shared.h" +#include "qcommon.h" + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#define TT_BINARY 0x0400 // binary number +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 + +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + const char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN_CHARS]; //available token + int type; //last read token type + int subtype; //last read token sub type + unsigned long int intvalue; //integer value + double floatvalue; //floating point value + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[1024]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[MAX_QPATH]; //file name of the script + char includepath[MAX_QPATH]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + +#define MAX_DEFINEPARMS 128 + +//directive name with parse function +typedef struct directive_s +{ + const char *name; + bool (*func)(source_t *source); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +static bool Parse_ReadToken(source_t *source, token_t *token); +static bool Parse_AddDefineToSourceFromString( source_t *source, const char *string ); + +int numtokens; + +//list with global defines added to every source loaded +define_t *globaldefines; + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, + {"$",P_DOLLAR, NULL}, + {NULL, 0, NULL} +}; + +/* +=============== +Parse_CreatePunctuationTable +=============== +*/ +static void Parse_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) +{ + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if (!script->punctuationtable) + script->punctuationtable = (punctuation_t **)Z_Malloc(256 * sizeof(punctuation_t *)); + + ::memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) + { + if (strlen(p->p) < strlen(newp->p)) + { + newp->next = p; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + break; + } + lastp = p; + } + if (!p) + { + newp->next = NULL; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + } + } +} + +/* +=============== +Parse_ScriptError +=============== +*/ +__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_ScriptError(script_t *script, const char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOERRORS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text); +} + +/* +=============== +Parse_ScriptWarning +=============== +*/ +__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_ScriptWarning(script_t *script, const char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOWARNINGS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text); +} + +/* +=============== +Parse_SetScriptPunctuations +=============== +*/ +static void Parse_SetScriptPunctuations(script_t *script, punctuation_t *p) +{ + if (p) Parse_CreatePunctuationTable(script, p); + else Parse_CreatePunctuationTable(script, default_punctuations); + if (p) script->punctuations = p; + else script->punctuations = default_punctuations; +} + +/* +=============== +Parse_ReadWhiteSpace +=============== +*/ +static bool Parse_ReadWhiteSpace(script_t *script) +{ + while(1) + { + //skip white space + while(*script->script_p <= ' ') + { + if (!*script->script_p) return false; + if (*script->script_p == '\n') script->line++; + script->script_p++; + } + //skip comments + if (*script->script_p == '/') + { + //comments // + if (*(script->script_p+1) == '/') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return false; + } + while(*script->script_p != '\n'); + script->line++; + script->script_p++; + if (!*script->script_p) return false; + continue; + } + //comments /* */ + else if (*(script->script_p+1) == '*') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return false; + if (*script->script_p == '\n') script->line++; + } + while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); + script->script_p++; + if (!*script->script_p) return false; + script->script_p++; + if (!*script->script_p) return false; + continue; + } + } + break; + } + return true; +} + +/* +=============== +Parse_ReadEscapeCharacter +=============== +*/ +static bool Parse_ReadEscapeCharacter(script_t *script, char *ch) +{ + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch(*script->script_p) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; + else break; + val = (val << 4) + c; + } + script->script_p--; + if (val > 0xFF) + { + Parse_ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } + c = val; + break; + } + default: //NOTE: decimal ASCII code, NOT octal + { + if (*script->script_p < '0' || *script->script_p > '9') Parse_ScriptError(script, "unknown escape char"); + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else break; + val = val * 10 + c; + } + script->script_p--; + if (val > 0xFF) + { + Parse_ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } + c = val; + break; + } + } + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //succesfully read escape character + return true; +} + +/* +=============== +Parse_ReadString + +Reads C-like string. Escape characters are interpretted. +Quotes are included with the string. +Reads two strings with a white space between them as one string. +=============== +*/ +static bool Parse_ReadString(script_t *script, token_t *token, int quote) +{ + int len, tmpline; + char *tmpscript_p; + + if (quote == '\"') token->type = TT_STRING; + else token->type = TT_LITERAL; + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while(1) + { + //minus 2 because trailing double quote and zero have to be appended + if (len >= MAX_TOKEN_CHARS - 2) + { + Parse_ScriptError(script, "string longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return false; + } + //if there is an escape character and + //if escape characters inside a string are allowed + if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) + { + if (!Parse_ReadEscapeCharacter(script, &token->string[len])) + { + token->string[len] = 0; + return false; + } + len++; + } + //if a trailing quote + else if (*script->script_p == quote) + { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if (script->flags & SCFL_NOSTRINGWHITESPACES) break; + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if (!Parse_ReadWhiteSpace(script)) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } + //if there's no leading double qoute + if (*script->script_p != quote) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } + //step over the new leading double quote + script->script_p++; + } + else + { + if (*script->script_p == '\0') + { + token->string[len] = 0; + Parse_ScriptError(script, "missing trailing quote"); + return false; + } + if (*script->script_p == '\n') + { + token->string[len] = 0; + Parse_ScriptError(script, "newline inside string %s", token->string); + return false; + } + token->string[len++] = *script->script_p++; + } + } + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return true; +} + +/* +=============== +Parse_ReadName +=============== +*/ +static bool Parse_ReadName(script_t *script, token_t *token) +{ + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "name longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return false; + } + c = *script->script_p; + } while ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_'); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return true; +} + +/* +=============== +Parse_NumberValue +=============== +*/ +static void Parse_NumberValue(char *string, int subtype, unsigned long int *intvalue, + double *floatvalue) +{ + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if (subtype & TT_FLOAT) + { + while(*string) + { + if (*string == '.') + { + if (dotfound) return; + dotfound = 10; + string++; + } + if (dotfound) + { + *floatvalue = *floatvalue + (double) (*string - '0') / + (double) dotfound; + dotfound *= 10; + } + else + { + *floatvalue = *floatvalue * 10.0 + (double) (*string - '0'); + } + string++; + } + *intvalue = (unsigned long) *floatvalue; + } + else if (subtype & TT_DECIMAL) + { + while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); + *floatvalue = *intvalue; + } + else if (subtype & TT_HEX) + { + //step over the leading 0x or 0X + string += 2; + while(*string) + { + *intvalue <<= 4; + if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; + else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; + else *intvalue += *string - '0'; + string++; + } + *floatvalue = *intvalue; + } + else if (subtype & TT_OCTAL) + { + //step over the first zero + string += 1; + while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); + *floatvalue = *intvalue; + } + else if (subtype & TT_BINARY) + { + //step over the leading 0b or 0B + string += 2; + while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); + *floatvalue = *intvalue; + } +} + +/* +=============== +Parse_ReadNumber +=============== +*/ +static bool Parse_ReadNumber(script_t *script, token_t *token) +{ + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if (*script->script_p == '0' && + (*(script->script_p + 1) == 'x' || + *(script->script_p + 1) == 'X')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'A')) + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "hexadecimal number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return false; + } + c = *script->script_p; + } + token->subtype |= TT_HEX; + } +#ifdef BINARYNUMBERS + //check for a binary number + else if (*script->script_p == '0' && + (*(script->script_p + 1) == 'b' || + *(script->script_p + 1) == 'B')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //binary + while(c == '0' || c == '1') + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "binary number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return false; + } + c = *script->script_p; + } + token->subtype |= TT_BINARY; + } +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = false; + dot = false; + if (*script->script_p == '0') octal = true; + while(1) + { + c = *script->script_p; + if (c == '.') dot = true; + else if (c == '8' || c == '9') octal = false; + else if (c < '0' || c > '9') break; + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS - 1) + { + Parse_ScriptError(script, "number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return false; + } + } + if (octal) token->subtype |= TT_OCTAL; + else token->subtype |= TT_DECIMAL; + if (dot) token->subtype |= TT_FLOAT; + } + for (i = 0; i < 2; i++) + { + c = *script->script_p; + //check for a LONG number + if ( (c == 'l' || c == 'L') + && !(token->subtype & TT_LONG)) + { + script->script_p++; + token->subtype |= TT_LONG; + } + //check for an UNSIGNED number + else if ( (c == 'u' || c == 'U') + && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) + { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } + } + token->string[len] = '\0'; + Parse_NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); + if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; + return true; +} + +/* +=============== +Parse_ReadPunctuation +=============== +*/ +static bool Parse_ReadPunctuation(script_t *script, token_t *token) +{ + int len; + const char *p; + punctuation_t *punc; + + for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) + { + p = punc->p; + len = strlen(p); + //if the script contains at least as much characters as the punctuation + if (script->script_p + len <= script->end_p) + { + //if the script contains the punctuation + if (!strncmp(script->script_p, p, len)) + { + strncpy(token->string, p, MAX_TOKEN_CHARS); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return true; + } + } + } + return false; +} + +/* +=============== +Parse_ReadPrimitive +=============== +*/ +static bool Parse_ReadPrimitive(script_t *script, token_t *token) +{ + int len; + + len = 0; + while(*script->script_p > ' ' && *script->script_p != ';') + { + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "primitive token longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return false; + } + token->string[len++] = *script->script_p++; + } + token->string[len] = 0; + //copy the token into the script structure + ::memcpy(&script->token, token, sizeof(token_t)); + //primitive reading successfull + return true; +} + +/* +=============== +Parse_ReadScriptToken +=============== +*/ +static bool Parse_ReadScriptToken(script_t *script, token_t *token) +{ + //if there is a token available (from UnreadToken) + if (script->tokenavailable) + { + script->tokenavailable = 0; + ::memcpy(token, &script->token, sizeof(token_t)); + return true; + } + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + ::memset(token, 0, sizeof(token_t)); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if (!Parse_ReadWhiteSpace(script)) return false; + + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if (*script->script_p == '\"') + { + if (!Parse_ReadString(script, token, '\"')) return false; + } + //if an literal + else if (*script->script_p == '\'') + { + //if (!Parse_ReadLiteral(script, token)) return false; + if (!Parse_ReadString(script, token, '\'')) return false; + } + //if there is a number + else if ((*script->script_p >= '0' && *script->script_p <= '9') || + (*script->script_p == '.' && + (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) + { + if (!Parse_ReadNumber(script, token)) return 0; + } + //if this is a primitive script + else if (script->flags & SCFL_PRIMITIVE) + { + return Parse_ReadPrimitive(script, token); + } + //if there is a name + else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || + (*script->script_p >= 'A' && *script->script_p <= 'Z') || + *script->script_p == '_') + { + if (!Parse_ReadName(script, token)) return false; + } + //check for punctuations + else if (!Parse_ReadPunctuation(script, token)) + { + Parse_ScriptError(script, "can't read token"); + return false; + } + //copy the token into the script structure + ::memcpy(&script->token, token, sizeof(token_t)); + //succesfully read a token + return true; +} + +/* +=============== +Parse_StripDoubleQuotes +=============== +*/ +static void Parse_StripDoubleQuotes(char *string) +{ + if (*string == '\"') + { + memmove( string, string + 1, strlen( string ) + 1 ); + } + if (string[strlen(string)-1] == '\"') + { + string[strlen(string)-1] = '\0'; + } +} + +/* +=============== +Parse_EndOfScript +=============== +*/ +static bool Parse_EndOfScript(script_t *script) +{ + return script->script_p >= script->end_p; +} + +/* +=============== +Parse_LoadScriptFile +=============== +*/ +static script_t *Parse_LoadScriptFile(const char *filename) +{ + fileHandle_t fp; + int length; + void *buffer; + script_t *script; + + length = FS_FOpenFileRead( filename, &fp, false ); + if (!fp) return NULL; + + buffer = Z_Malloc(sizeof(script_t) + length + 1); + ::memset( buffer, 0, sizeof(script_t) + length + 1 ); + + script = (script_t *) buffer; + ::memset(script, 0, sizeof(script_t)); + strcpy(script->filename, filename); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + Parse_SetScriptPunctuations(script, NULL); + // + FS_Read(script->buffer, length, fp); + FS_FCloseFile(fp); + // + + return script; +} + +/* +=============== +Parse_LoadScriptMemory +=============== +*/ +static script_t *Parse_LoadScriptMemory(const char *ptr, int length, const char *name) +{ + void *buffer; + script_t *script; + + buffer = Z_Malloc(sizeof(script_t) + length + 1); + ::memset( buffer, 0, sizeof(script_t) + length + 1 ); + + script = (script_t *) buffer; + ::memset(script, 0, sizeof(script_t)); + strcpy(script->filename, name); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + Parse_SetScriptPunctuations(script, NULL); + // + ::memcpy(script->buffer, ptr, length); + // + return script; +} + +/* +=============== +Parse_FreeScript +=============== +*/ +static void Parse_FreeScript(script_t *script) +{ + if (script->punctuationtable) Z_Free(script->punctuationtable); + Z_Free(script); +} + +/* +=============== +Parse_SourceError +=============== +*/ +__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_SourceError(source_t *source, const char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +} + +/* +=============== +Parse_SourceWarning +=============== +*/ +__attribute__ ((format (printf, 2, 3))) static void QDECL Parse_SourceWarning(source_t *source, const char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +} + +/* +=============== +Parse_PushIndent +=============== +*/ +static void Parse_PushIndent(source_t *source, int type, int skip) +{ + indent_t *indent; + + indent = (indent_t *) Z_Malloc(sizeof(indent_t)); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = (skip != 0); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} + +/* +=============== +Parse_PopIndent +=============== +*/ +static void Parse_PopIndent(source_t *source, int *type, int *skip) +{ + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if (!indent) return; + + //must be an indent from the current script + if (source->indentstack->script != source->scriptstack) return; + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + Z_Free(indent); +} + +/* +=============== +Parse_PushScript +=============== +*/ +static void Parse_PushScript(source_t *source, script_t *script) +{ + script_t *s; + + for (s = source->scriptstack; s; s = s->next) + { + if (!Q_stricmp(s->filename, script->filename)) + { + Parse_SourceError(source, "%s recursively included", script->filename); + return; + } + } + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} + +/* +=============== +Parse_CopyToken +=============== +*/ +static token_t *Parse_CopyToken(token_t *token) +{ + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) Z_Malloc(sizeof(token_t)); +// t = freetokens; + if (!t) + { + Com_Error(ERR_FATAL, "out of token space\n"); + return NULL; + } +// freetokens = freetokens->next; + ::memcpy(t, token, sizeof(token_t)); + t->next = NULL; + numtokens++; + return t; +} + +/* +=============== +Parse_FreeToken +=============== +*/ +static void Parse_FreeToken(token_t *token) +{ + //free(token); + Z_Free(token); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} + +/* +=============== +Parse_ReadSourceToken +=============== +*/ +static bool Parse_ReadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + script_t *script; + int type, skip, lines; + + lines = 0; + //if there's no token already available + while(!source->tokens) + { + //if there's a token to read from the script + if( Parse_ReadScriptToken( source->scriptstack, token ) ) + { + token->linescrossed += lines; + return true; + } + + // if lines were crossed before the end of the script, count them + lines += source->scriptstack->line - source->scriptstack->lastline; + + //if at the end of the script + if (Parse_EndOfScript(source->scriptstack)) + { + //remove all indents of the script + while(source->indentstack && + source->indentstack->script == source->scriptstack) + { + Parse_SourceWarning(source, "missing #endif"); + Parse_PopIndent(source, &type, &skip); + } + } + //if this was the initial script + if (!source->scriptstack->next) return false; + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + Parse_FreeScript(script); + } + //copy the already available token + ::memcpy(token, source->tokens, sizeof(token_t)); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + Parse_FreeToken(t); + return true; +} + +/* +=============== +Parse_UnreadSourceToken +=============== +*/ +static bool Parse_UnreadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + + t = Parse_CopyToken(token); + t->next = source->tokens; + source->tokens = t; + return true; +} + +/* +=============== +Parse_ReadDefineParms +=============== +*/ +static bool Parse_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) +{ + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "define %s missing parms", define->name); + return false; + } + // + if (define->numparms > maxparms) + { + Parse_SourceError(source, "define with more than %d parameters", maxparms); + return false; + } + // + for (i = 0; i < define->numparms; i++) parms[i] = NULL; + //if no leading "(" + if (strcmp(token.string, "(")) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "define %s missing parms", define->name); + return false; + } + //read the define parameters + for (done = 0, numparms = 0, indent = 0; !done;) + { + if (numparms >= maxparms) + { + Parse_SourceError(source, "define %s with too many parms", define->name); + return false; + } + if (numparms >= define->numparms) + { + Parse_SourceWarning(source, "define %s has too many parms", define->name); + return false; + } + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while(!done) + { + // + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "define %s incomplete", define->name); + return false; + } + // + if (!strcmp(token.string, ",")) + { + if (indent <= 0) + { + if (lastcomma) Parse_SourceWarning(source, "too many comma's"); + lastcomma = 1; + break; + } + } + lastcomma = 0; + // + if (!strcmp(token.string, "(")) + { + indent++; + continue; + } + else if (!strcmp(token.string, ")")) + { + if (--indent <= 0) + { + if (!parms[define->numparms-1]) + { + Parse_SourceWarning(source, "too few define parms"); + } + done = 1; + break; + } + } + // + if (numparms < define->numparms) + { + // + t = Parse_CopyToken(&token); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } + } + numparms++; + } + return true; +} + +/* +=============== +Parse_StringizeTokens +=============== +*/ +static bool Parse_StringizeTokens(token_t *tokens, token_t *token) +{ + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat(token->string, "\""); + for (t = tokens; t; t = t->next) + { + strncat(token->string, t->string, MAX_TOKEN_CHARS - strlen(token->string)); + } + strncat(token->string, "\"", MAX_TOKEN_CHARS - strlen(token->string)); + return true; +} + +/* +=============== +Parse_MergeTokens +=============== +*/ +static bool Parse_MergeTokens(token_t *t1, token_t *t2) +{ + //merging of a name with a name or number + if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) + { + strcat(t1->string, t2->string); + return true; + } + //merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) + { + //remove trailing double quote + t1->string[strlen(t1->string)-1] = '\0'; + //concat without leading double quote + strcat(t1->string, &t2->string[1]); + return true; + } + //FIXME: merging of two number of the same sub type + return false; +} + +/* +=============== +Parse_NameHash +=============== +*/ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; +static int Parse_NameHash(char *name) +{ + int hash, i; + + hash = 0; + for (i = 0; name[i] != '\0'; i++) + { + hash += name[i] * (119 + i); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} + +/* +=============== +Parse_AddDefineToHash +=============== +*/ +static void Parse_AddDefineToHash(define_t *define, define_t **definehash) +{ + int hash; + + hash = Parse_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} + +/* +=============== +Parse_FindHashedDefine +=============== +*/ +static define_t *Parse_FindHashedDefine(define_t **definehash, char *name) +{ + define_t *d; + int hash; + + hash = Parse_NameHash(name); + for (d = definehash[hash]; d; d = d->hashnext) + { + if (!strcmp(d->name, name)) return d; + } + return NULL; +} + +/* +=============== +Parse_FindDefineParm +=============== +*/ +static int Parse_FindDefineParm(define_t *define, char *name) +{ + token_t *p; + int i; + + i = 0; + for (p = define->parms; p; p = p->next) + { + if (!strcmp(p->string, name)) return i; + i++; + } + return -1; +} + +/* +=============== +Parse_FreeDefine +=============== +*/ +static void Parse_FreeDefine(define_t *define) +{ + token_t *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) + { + next = t->next; + Parse_FreeToken(t); + } + //free the define tokens + for (t = define->tokens; t; t = next) + { + next = t->next; + Parse_FreeToken(t); + } + //free the define + Z_Free(define); +} + +/* +=============== +Parse_ExpandBuiltinDefine +=============== +*/ +static bool Parse_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *token; + time_t t; + + char *curtime; + + token = Parse_CopyToken(deftoken); + switch(define->builtin) + { + case BUILTIN_LINE: + { + sprintf(token->string, "%d", deftoken->line); + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_FILE: + { + strcpy(token->string, source->scriptstack->filename); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_DATE: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+4, 7); + strncat(token->string+7, curtime+20, 4); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_TIME: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+11, 8); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + } + return true; +} + +/* +=============== +Parse_ExpandDefine +=============== +*/ +static bool Parse_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if (define->builtin) + { + return Parse_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); + } + //if the define has parameters + if (define->numparms) + { + if (!Parse_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return false; + } + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for (dt = define->tokens; dt; dt = dt->next) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if (dt->type == TT_NAME) + { + parmnum = Parse_FindDefineParm(define, dt->string); + } + //if it is a define parameter + if (parmnum >= 0) + { + for (pt = parms[parmnum]; pt; pt = pt->next) + { + t = Parse_CopyToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + else + { + //if stringizing operator + if (dt->string[0] == '#' && dt->string[1] == '\0') + { + //the stringizing operator must be followed by a define parameter + if (dt->next) parmnum = Parse_FindDefineParm(define, dt->next->string); + else parmnum = -1; + // + if (parmnum >= 0) + { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if (!Parse_StringizeTokens(parms[parmnum], &token)) + { + Parse_SourceError(source, "can't stringize tokens"); + return false; + } + t = Parse_CopyToken(&token); + } + else + { + Parse_SourceWarning(source, "stringizing operator without define parameter"); + continue; + } + } + else + { + t = Parse_CopyToken(dt); + } + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + //check for the merging operator + for (t = first; t; ) + { + if (t->next) + { + //if the merging operator + if (t->next->string[0] == '#' && t->next->string[1] == '#') + { + t1 = t; + t2 = t->next->next; + if (t2) + { + if (!Parse_MergeTokens(t1, t2)) + { + Parse_SourceError(source, "can't merge %s with %s", t1->string, t2->string); + return false; + } + Parse_FreeToken(t1->next); + t1->next = t2->next; + if (t2 == last) last = t1; + Parse_FreeToken(t2); + continue; + } + } + } + t = t->next; + } + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for (i = 0; i < define->numparms; i++) + { + for (pt = parms[i]; pt; pt = nextpt) + { + nextpt = pt->next; + Parse_FreeToken(pt); + } + } + // + return true; +} + +/* +=============== +Parse_ExpandDefineIntoSource +=============== +*/ +static bool Parse_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) +{ + token_t *firsttoken, *lasttoken; + + if (!Parse_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return false; + + if (firsttoken && lasttoken) + { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return true; + } + return false; +} + +/* +=============== +Parse_ConvertPath +=============== +*/ +static void Parse_ConvertPath(char *path) +{ + char *ptr; + + //remove double path seperators + for (ptr = path; *ptr;) + { + if ((*ptr == '\\' || *ptr == '/') && + (*(ptr+1) == '\\' || *(ptr+1) == '/')) + { + memmove(ptr, ptr+1, strlen(ptr)); + } + else + { + ptr++; + } + } + //set OS dependent path seperators + for (ptr = path; *ptr;) + { + if (*ptr == '/' || *ptr == '\\') *ptr = PATH_SEP; + ptr++; + } +} + +/* +=============== +Parse_ReadLine + +reads a token from the current line, continues reading on the next +line only if a backslash '\' is encountered. +=============== +*/ +static bool Parse_ReadLine(source_t *source, token_t *token) +{ + int crossline; + + crossline = 0; + do + { + if (!Parse_ReadSourceToken(source, token)) return false; + + if (token->linescrossed > crossline) + { + Parse_UnreadSourceToken(source, token); + return false; + } + crossline = 1; + } while(!strcmp(token->string, "\\")); + return true; +} + +/* +=============== +Parse_OperatorPriority +=============== +*/ +typedef struct operator_s +{ + int _operator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +static bool Parse_OperatorPriority(int op) +{ + switch(op) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } + return false; +} + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue(val) \ + if (numvalues >= MAX_VALUES) { \ + Parse_SourceError(source, "out of value space\n"); \ + error = 1; \ + break; \ + } \ + else \ + val = &value_heap[numvalues++]; +#define FreeValue(val) +// +#define AllocOperator(op) \ + if (numoperators >= MAX_OPERATORS) { \ + Parse_SourceError(source, "out of operator space\n"); \ + error = 1; \ + break; \ + } \ + else \ + op = &operator_heap[numoperators++]; +#define FreeOperator(op) + +/* +=============== +Parse_EvaluateTokens +=============== +*/ +static bool Parse_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, + double *floatvalue, int integer) +{ + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = false; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for (t = tokens; t; t = t->next) + { + switch(t->type) + { + case TT_NAME: + { + if (lastwasvalue || negativevalue) + { + Parse_SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } + if (strcmp(t->string, "defined")) + { + Parse_SourceError(source, "undefined name %s in #if/#elif", t->string); + error = 1; + break; + } + t = t->next; + if (!strcmp(t->string, "(")) + { + brace = true; + t = t->next; + } + if (!t || t->type != TT_NAME) + { + Parse_SourceError(source, "defined without name in #if/#elif"); + error = 1; + break; + } + //v = (value_t *) Z_Malloc(sizeof(value_t)); + AllocValue(v); + if (Parse_FindHashedDefine(source->definehash, t->string)) + { + v->intvalue = 1; + v->floatvalue = 1; + } + else + { + v->intvalue = 0; + v->floatvalue = 0; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) + { + t = t->next; + if (!t || strcmp(t->string, ")")) + { + Parse_SourceError(source, "defined without ) in #if/#elif"); + error = 1; + break; + } + } + brace = false; + // defined() creates a value + lastwasvalue = 1; + break; + } + case TT_NUMBER: + { + if (lastwasvalue) + { + Parse_SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } + //v = (value_t *) Z_Malloc(sizeof(value_t)); + AllocValue(v); + if (negativevalue) + { + v->intvalue = - (signed int) t->intvalue; + v->floatvalue = - t->floatvalue; + } + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } + case TT_PUNCTUATION: + { + if (negativevalue) + { + Parse_SourceError(source, "misplaced minus sign in #if/#elif"); + error = 1; + break; + } + if (t->subtype == P_PARENTHESESOPEN) + { + parentheses++; + break; + } + else if (t->subtype == P_PARENTHESESCLOSE) + { + parentheses--; + if (parentheses < 0) + { + Parse_SourceError(source, "too many ) in #if/#elsif"); + error = 1; + } + break; + } + //check for invalid operators on floating point values + if (!integer) + { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) + { + Parse_SourceError(source, "illigal operator %s on floating point operands\n", t->string); + error = 1; + break; + } + } + switch(t->subtype) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) + { + Parse_SourceError(source, "! or ~ after value in #if/#elif"); + error = 1; + break; + } + break; + } + case P_INC: + case P_DEC: + { + Parse_SourceError(source, "++ or -- used in #if/#elif"); + break; + } + case P_SUB: + { + if (!lastwasvalue) + { + negativevalue = 1; + break; + } + } + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) + { + Parse_SourceError(source, "operator %s after operator in #if/#elif", t->string); + error = 1; + break; + } + break; + } + default: + { + Parse_SourceError(source, "invalid operator %s in #if/#elif", t->string); + error = 1; + break; + } + } + if (!error && !negativevalue) + { + //o = (operator_t *) Z_Malloc(sizeof(operator_t)); + AllocOperator(o); + o->_operator = t->subtype; + o->priority = Parse_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } + break; + } + default: + { + Parse_SourceError(source, "unknown %s in #if/#elif", t->string); + error = 1; + break; + } + } + if (error) break; + } + if (!error) + { + if (!lastwasvalue) + { + Parse_SourceError(source, "trailing operator in #if/#elif"); + error = 1; + } + else if (parentheses) + { + Parse_SourceError(source, "too many ( in #if/#elif"); + error = 1; + } + } + // + gotquestmarkvalue = false; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while(!error && firstoperator) + { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) break; + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) + { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) break; + } + //if the arity of the operator isn't equal to 1 + if (o->_operator != P_LOGIC_NOT + && o->_operator != P_BIN_NOT) v = v->next; + //if there's no value or no next value + if (!v) + { + Parse_SourceError(source, "mising values in #if/#elif"); + error = 1; + break; + } + } + if (error) break; + v1 = v; + v2 = v->next; + switch(o->_operator) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + Parse_SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + Parse_SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) + { + Parse_SourceError(source, ": without ? in #if/#elif"); + error = 1; + break; + } + if (integer) + { + if (!questmarkintvalue) v1->intvalue = v2->intvalue; + } + else + { + if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; + } + gotquestmarkvalue = false; + break; + } + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) + { + Parse_SourceError(source, "? after ? in #if/#elif"); + error = 1; + break; + } + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = true; + break; + } + } + if (error) break; + //if not an operator with arity 1 + if (o->_operator != P_LOGIC_NOT + && o->_operator != P_BIN_NOT) + { + //remove the second value if not question mark operator + if (o->_operator != P_QUESTIONMARK) v = v->next; + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //Z_Free(v); + FreeValue(v); + } + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //Z_Free(o); + FreeOperator(o); + } + if (firstvalue) + { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } + for (o = firstoperator; o; o = lastoperator) + { + lastoperator = o->next; + //Z_Free(o); + FreeOperator(o); + } + for (v = firstvalue; v; v = lastvalue) + { + lastvalue = v->next; + //Z_Free(v); + FreeValue(v); + } + if (!error) return true; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + return false; +} + +/* +=============== +Parse_Evaluate +=============== +*/ +static bool Parse_Evaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = false; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "no value after #if/#elif"); + return false; + } + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = false; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if (!strcmp(token.string, "defined")) + { + defined = true; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else + { + //then it must be a define + define = Parse_FindHashedDefine(source->definehash, token.string); + if (!define) + { + Parse_SourceError(source, "can't evaluate %s, not defined", token.string); + return false; + } + if (!Parse_ExpandDefineIntoSource(source, &token, define)) return false; + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else //can't evaluate the token + { + Parse_SourceError(source, "can't evaluate %s", token.string); + return false; + } + } while(Parse_ReadLine(source, &token)); + // + if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return false; + // + for (t = firsttoken; t; t = nexttoken) + { + nexttoken = t->next; + Parse_FreeToken(t); + } + // + return true; +} + +/* +=============== +Parse_DollarEvaluate +=============== +*/ +static bool Parse_DollarEvaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + int indent, defined = false; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "no leading ( after $evalint/$evalfloat"); + return false; + } + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "nothing to evaluate"); + return false; + } + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = false; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if (!strcmp(token.string, "defined")) + { + defined = true; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else + { + //then it must be a define + define = Parse_FindHashedDefine(source->definehash, token.string); + if (!define) + { + Parse_SourceError(source, "can't evaluate %s, not defined", token.string); + return false; + } + if (!Parse_ExpandDefineIntoSource(source, &token, define)) return false; + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + if (*token.string == '(') indent++; + else if (*token.string == ')') indent--; + if (indent <= 0) break; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else //can't evaluate the token + { + Parse_SourceError(source, "can't evaluate %s", token.string); + return false; + } + } while(Parse_ReadSourceToken(source, &token)); + // + if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return false; + // + for (t = firsttoken; t; t = nexttoken) + { + nexttoken = t->next; + Parse_FreeToken(t); + } + // + return true; +} + +/* +=============== +Parse_Directive_include +=============== +*/ +static bool Parse_Directive_include(source_t *source) +{ + script_t *script; + token_t token; + char path[MAX_QPATH]; + + if (source->skip > 0) return true; + // + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "#include without file name"); + return false; + } + if (token.linescrossed > 0) + { + Parse_SourceError(source, "#include without file name"); + return false; + } + if (token.type == TT_STRING) + { + Parse_StripDoubleQuotes(token.string); + Parse_ConvertPath(token.string); + script = Parse_LoadScriptFile(token.string); + if (!script) + { + strcpy(path, source->includepath); + strcat(path, token.string); + script = Parse_LoadScriptFile(path); + } + } + else if (token.type == TT_PUNCTUATION && *token.string == '<') + { + strcpy(path, source->includepath); + while(Parse_ReadSourceToken(source, &token)) + { + if (token.linescrossed > 0) + { + Parse_UnreadSourceToken(source, &token); + break; + } + if (token.type == TT_PUNCTUATION && *token.string == '>') break; + strncat(path, token.string, MAX_QPATH - 1); + } + if (*token.string != '>') + { + Parse_SourceWarning(source, "#include missing trailing >"); + } + if (!strlen(path)) + { + Parse_SourceError(source, "#include without file name between < >"); + return false; + } + Parse_ConvertPath(path); + script = Parse_LoadScriptFile(path); + } + else + { + Parse_SourceError(source, "#include without file name"); + return false; + } + if (!script) + { + Parse_SourceError(source, "file %s not found", path); + return false; + } + Parse_PushScript(source, script); + return true; +} + +/* +=============== +Parse_WhiteSpaceBeforeToken +=============== +*/ +static bool Parse_WhiteSpaceBeforeToken(token_t *token) +{ + return token->endwhitespace_p - token->whitespace_p > 0; +} + +/* +=============== +Parse_ClearTokenWhiteSpace +=============== +*/ +static void Parse_ClearTokenWhiteSpace(token_t *token) +{ + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} + +/* +=============== +Parse_Directive_undef +=============== +*/ +static bool Parse_Directive_undef(source_t *source) +{ + token_t token; + define_t *define, *lastdefine; + int hash; + + if (source->skip > 0) return true; + // + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "undef without name"); + return false; + } + if (token.type != TT_NAME) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "expected name, found %s", token.string); + return false; + } + + hash = Parse_NameHash(token.string); + for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + Parse_SourceWarning(source, "can't undef %s", token.string); + } + else + { + if (lastdefine) lastdefine->hashnext = define->hashnext; + else source->definehash[hash] = define->hashnext; + Parse_FreeDefine(define); + } + break; + } + lastdefine = define; + } + return true; +} + +/* +=============== +Parse_Directive_elif +=============== +*/ +static bool Parse_Directive_elif(source_t *source) +{ + signed long int value; + int type, skip; + + Parse_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + Parse_SourceError(source, "misplaced #elif"); + return false; + } + if (!Parse_Evaluate(source, &value, NULL, true)) return false; + skip = (value == 0); + Parse_PushIndent(source, INDENT_ELIF, skip); + return true; +} + +/* +=============== +Parse_Directive_if +=============== +*/ +static bool Parse_Directive_if(source_t *source) +{ + signed long int value; + int skip; + + if (!Parse_Evaluate(source, &value, NULL, true)) return false; + skip = (value == 0); + Parse_PushIndent(source, INDENT_IF, skip); + return true; +} + +/* +=============== +Parse_Directive_line +=============== +*/ +static bool Parse_Directive_line(source_t *source) +{ + Parse_SourceError(source, "#line directive not supported"); + return false; +} + +/* +=============== +Parse_Directive_error +=============== +*/ +static bool Parse_Directive_error(source_t *source) +{ + token_t token; + + strcpy(token.string, ""); + Parse_ReadSourceToken(source, &token); + Parse_SourceError(source, "#error directive: %s", token.string); + return false; +} + +/* +=============== +Parse_Directive_pragma +=============== +*/ +static bool Parse_Directive_pragma(source_t *source) +{ + token_t token; + + Parse_SourceWarning(source, "#pragma directive not supported"); + while(Parse_ReadLine(source, &token)) ; + return true; +} + +/* +=============== +Parse_UnreadSignToken +=============== +*/ +static void Parse_UnreadSignToken(source_t *source) +{ + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy(token.string, "-"); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + Parse_UnreadSourceToken(source, &token); +} + +/* +=============== +Parse_Directive_eval +=============== +*/ +static bool Parse_Directive_eval(source_t *source) +{ + signed long int value; + token_t token; + + if (!Parse_Evaluate(source, &value, NULL, true)) return false; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%ld", labs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return true; +} + +/* +=============== +Parse_Directive_evalfloat +=============== +*/ +static bool Parse_Directive_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!Parse_Evaluate(source, NULL, &value, false)) return false; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return true; +} + +/* +=============== +Parse_DollarDirective_evalint +=============== +*/ +static bool Parse_DollarDirective_evalint(source_t *source) +{ + signed long int value; + token_t token; + + if (!Parse_DollarEvaluate(source, &value, NULL, true)) return false; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%ld", labs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + token.intvalue = value; + token.floatvalue = value; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return true; +} + +/* +=============== +Parse_DollarDirective_evalfloat +=============== +*/ +static bool Parse_DollarDirective_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!Parse_DollarEvaluate(source, NULL, &value, false)) return false; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + token.intvalue = (unsigned long) value; + token.floatvalue = value; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return true; +} + +/* +=============== +Parse_ReadDollarDirective +=============== +*/ +directive_t dollardirectives[20] = +{ + {"evalint", Parse_DollarDirective_evalint}, + {"evalfloat", Parse_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +static bool Parse_ReadDollarDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "found $ without name"); + return false; + } + //directive name must be on the same line + if (token.linescrossed > 0) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "found $ at end of line"); + return false; + } + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + return dollardirectives[i].func(source); + } + } + } + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "unknown precompiler directive %s", token.string); + return false; +} + +/* +=============== +Parse_Directive_if_def +=============== +*/ +static bool Parse_Directive_if_def(source_t *source, int type) +{ + token_t token; + define_t *d; + int skip; + + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "#ifdef without name"); + return false; + } + if (token.type != TT_NAME) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "expected name after #ifdef, found %s", token.string); + return false; + } + d = Parse_FindHashedDefine(source->definehash, token.string); + skip = (type == INDENT_IFDEF) == (d == NULL); + Parse_PushIndent(source, type, skip); + return true; +} + +/* +=============== +Parse_Directive_ifdef +=============== +*/ +static bool Parse_Directive_ifdef(source_t *source) +{ + return Parse_Directive_if_def(source, INDENT_IFDEF); +} + +/* +=============== +Parse_Directive_ifndef +=============== +*/ +static bool Parse_Directive_ifndef(source_t *source) +{ + return Parse_Directive_if_def(source, INDENT_IFNDEF); +} + +/* +=============== +Parse_Directive_else +=============== +*/ +static bool Parse_Directive_else(source_t *source) +{ + int type, skip; + + Parse_PopIndent(source, &type, &skip); + if (!type) + { + Parse_SourceError(source, "misplaced #else"); + return false; + } + if (type == INDENT_ELSE) + { + Parse_SourceError(source, "#else after #else"); + return false; + } + Parse_PushIndent(source, INDENT_ELSE, !skip); + return true; +} + +/* +=============== +Parse_Directive_endif +=============== +*/ +static bool Parse_Directive_endif(source_t *source) +{ + int type, skip; + + Parse_PopIndent(source, &type, &skip); + if (!type) + { + Parse_SourceError(source, "misplaced #endif"); + return false; + } + return true; +} + +/* +=============== +Parse_CheckTokenString +=============== +*/ +static bool Parse_CheckTokenString(source_t *source, const char *string) +{ + token_t tok; + + if (!Parse_ReadToken(source, &tok)) return false; + //if the token is available + if (!strcmp(tok.string, string)) return true; + // + Parse_UnreadSourceToken(source, &tok); + return false; +} + +/* +=============== +Parse_Directive_define +=============== +*/ +static bool Parse_Directive_define(source_t *source) +{ + token_t token, *t, *last; + define_t *define; + + if (source->skip > 0) return true; + // + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "#define without name"); + return false; + } + if (token.type != TT_NAME) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "expected name after #define, found %s", token.string); + return false; + } + //check if the define already exists + define = Parse_FindHashedDefine(source->definehash, token.string); + if (define) + { + if (define->flags & DEFINE_FIXED) + { + Parse_SourceError(source, "can't redefine %s", token.string); + return false; + } + Parse_SourceWarning(source, "redefinition of %s", token.string); + //unread the define name before executing the #undef directive + Parse_UnreadSourceToken(source, &token); + if (!Parse_Directive_undef(source)) return false; + //if the define was not removed (define->flags & DEFINE_FIXED) + define = Parse_FindHashedDefine(source->definehash, token.string); + } + //allocate define + define = (define_t *) Z_Malloc(sizeof(define_t) + strlen(token.string) + 1); + ::memset(define, 0, sizeof(define_t)); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.string); + //add the define to the source + Parse_AddDefineToHash(define, source->definehash); + //if nothing is defined, just return + if (!Parse_ReadLine(source, &token)) return true; + //if it is a define with parameters + if (!Parse_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) + { + //read the define parameters + last = NULL; + if (!Parse_CheckTokenString(source, ")")) + { + while(1) + { + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "expected define parameter"); + return false; + } + //if it isn't a name + if (token.type != TT_NAME) + { + Parse_SourceError(source, "invalid define parameter"); + return false; + } + // + if (Parse_FindDefineParm(define, token.string) >= 0) + { + Parse_SourceError(source, "two the same define parameters"); + return false; + } + //add the define parm + t = Parse_CopyToken(&token); + Parse_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + //read next token + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "define parameters not terminated"); + return false; + } + // + if (!strcmp(token.string, ")")) break; + //then it must be a comma + if (strcmp(token.string, ",")) + { + Parse_SourceError(source, "define not terminated"); + return false; + } + } + } + if (!Parse_ReadLine(source, &token)) return true; + } + //read the defined stuff + last = NULL; + do + { + t = Parse_CopyToken(&token); + if (t->type == TT_NAME && !strcmp(t->string, define->name)) + { + Parse_SourceError(source, "recursive define (removed recursion)"); + continue; + } + Parse_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->tokens = t; + last = t; + } while(Parse_ReadLine(source, &token)); + // + if (last) + { + //check for merge operators at the beginning or end + if (!strcmp(define->tokens->string, "##") || + !strcmp(last->string, "##")) + { + Parse_SourceError(source, "define with misplaced ##"); + return false; + } + } + return true; +} + +/* +=============== +Parse_ReadDirective +=============== +*/ +directive_t directives[20] = +{ + {"if", Parse_Directive_if}, + {"ifdef", Parse_Directive_ifdef}, + {"ifndef", Parse_Directive_ifndef}, + {"elif", Parse_Directive_elif}, + {"else", Parse_Directive_else}, + {"endif", Parse_Directive_endif}, + {"include", Parse_Directive_include}, + {"define", Parse_Directive_define}, + {"undef", Parse_Directive_undef}, + {"line", Parse_Directive_line}, + {"error", Parse_Directive_error}, + {"pragma", Parse_Directive_pragma}, + {"eval", Parse_Directive_eval}, + {"evalfloat", Parse_Directive_evalfloat}, + {NULL, NULL} +}; + +static bool Parse_ReadDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "found # without name"); + return false; + } + //directive name must be on the same line + if (token.linescrossed > 0) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "found # at end of line"); + return false; + } + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; directives[i].name; i++) + { + if (!strcmp(directives[i].name, token.string)) + { + return directives[i].func(source); + } + } + } + Parse_SourceError(source, "unknown precompiler directive %s", token.string); + return false; +} + +/* +=============== +Parse_UnreadToken +=============== +*/ +static void Parse_UnreadToken(source_t *source, token_t *token) +{ + Parse_UnreadSourceToken(source, token); +} + +/* +=============== +Parse_ReadEnumeration + +It is assumed that the 'enum' token has already been consumed +This is fairly basic: it doesn't catch some fairly obvious errors like nested +enums, and enumerated names conflict with #define parameters +=============== +*/ +static bool Parse_ReadEnumeration( source_t *source ) +{ + token_t newtoken; + int value; + + if( !Parse_ReadToken( source, &newtoken ) ) + return false; + + if( newtoken.type != TT_PUNCTUATION || newtoken.subtype != P_BRACEOPEN ) + { + Parse_SourceError( source, "Found %s when expecting {\n", + newtoken.string ); + return false; + } + + for( value = 0;; value++ ) + { + token_t name; + + // read the name + if( !Parse_ReadToken( source, &name ) ) + break; + + // it's ok for the enum to end immediately + if( name.type == TT_PUNCTUATION && name.subtype == P_BRACECLOSE ) + { + if( !Parse_ReadToken( source, &name ) ) + break; + + // ignore trailing semicolon + if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON ) + Parse_UnreadToken( source, &name ); + + return true; + } + + // ... but not for it to do anything else + if( name.type != TT_NAME ) + { + Parse_SourceError( source, "Found %s when expecting identifier\n", + name.string ); + return false; + } + + if( !Parse_ReadToken( source, &newtoken ) ) + break; + + if( newtoken.type != TT_PUNCTUATION ) + { + Parse_SourceError( source, "Found %s when expecting , or = or }\n", + newtoken.string ); + return false; + } + + if( newtoken.subtype == P_ASSIGN ) + { + int neg = 1; + + if( !Parse_ReadToken( source, &newtoken ) ) + break; + + // Parse_ReadToken doesn't seem to read negative numbers, so we do it + // ourselves + if( newtoken.type == TT_PUNCTUATION && newtoken.subtype == P_SUB ) + { + neg = -1; + + // the next token should be the number + if( !Parse_ReadToken( source, &newtoken ) ) + break; + } + + if( newtoken.type != TT_NUMBER || !( newtoken.subtype & TT_INTEGER ) ) + { + Parse_SourceError( source, "Found %s when expecting integer\n", + newtoken.string ); + return false; + } + + // this is somewhat silly, but cheap to check + if( neg == -1 && ( newtoken.subtype & TT_UNSIGNED ) ) + { + Parse_SourceWarning( source, "Value in enumeration is negative and " + "unsigned\n" ); + } + + // set the new define value + value = newtoken.intvalue * neg; + + if( !Parse_ReadToken( source, &newtoken ) ) + break; + } + + if( newtoken.type != TT_PUNCTUATION || ( newtoken.subtype != P_COMMA && + newtoken.subtype != P_BRACECLOSE ) ) + { + Parse_SourceError( source, "Found %s when expecting , or }\n", + newtoken.string ); + return false; + } + + if( !Parse_AddDefineToSourceFromString( source, va( "%s %d\n", name.string, + value ) ) ) + { + Parse_SourceWarning( source, "Couldn't add define to source: %s = %d\n", + name.string, value ); + return false; + } + + if( newtoken.subtype == P_BRACECLOSE ) + { + if( !Parse_ReadToken( source, &name ) ) + break; + + // ignore trailing semicolon + if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON ) + Parse_UnreadToken( source, &name ); + + return true; + } + } + + // got here if a ReadToken returned false + return false; +} + +/* +=============== +Parse_ReadToken +=============== +*/ +static bool Parse_ReadToken(source_t *source, token_t *token) +{ + define_t *define; + + while(1) + { + if (!Parse_ReadSourceToken(source, token)) return false; + //check for precompiler directives + if (token->type == TT_PUNCTUATION && *token->string == '#') + { + { + //read the precompiler directive + if (!Parse_ReadDirective(source)) return false; + continue; + } + } + if (token->type == TT_PUNCTUATION && *token->string == '$') + { + { + //read the precompiler directive + if (!Parse_ReadDollarDirective(source)) return false; + continue; + } + } + if( token->type == TT_NAME && !Q_stricmp( token->string, "enum" ) ) + { + if( !Parse_ReadEnumeration( source ) ) + return false; + continue; + } + // recursively concatenate strings that are behind each other still resolving defines + if (token->type == TT_STRING) + { + token_t newtoken; + if (Parse_ReadToken(source, &newtoken)) + { + if (newtoken.type == TT_STRING) + { + token->string[strlen(token->string)-1] = '\0'; + if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN_CHARS) + { + Parse_SourceError(source, "string longer than MAX_TOKEN_CHARS %d\n", MAX_TOKEN_CHARS); + return false; + } + strcat(token->string, newtoken.string+1); + } + else + { + Parse_UnreadToken(source, &newtoken); + } + } + } + //if skipping source because of conditional compilation + if (source->skip) continue; + //if the token is a name + if (token->type == TT_NAME) + { + //check if the name is a define macro + define = Parse_FindHashedDefine(source->definehash, token->string); + //if it is a define macro + if (define) + { + //expand the defined macro + if (!Parse_ExpandDefineIntoSource(source, token, define)) return false; + continue; + } + } + //copy token for unreading + ::memcpy(&source->token, token, sizeof(token_t)); + //found a token + return true; + } +} + +/* +=============== +Parse_DefineFromString +=============== +*/ +static define_t *Parse_DefineFromString(char *string) +{ + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + script = Parse_LoadScriptMemory(string, strlen(string), "*extern"); + //create a new source + ::memset(&src, 0, sizeof(source_t)); + strncpy(src.filename, "*extern", MAX_QPATH); + src.scriptstack = script; + src.definehash = (define_t**)Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *)); + ::memset( src.definehash, 0, DEFINEHASHSIZE * sizeof(define_t *)); + //create a define from the source + res = Parse_Directive_define(&src); + //free any tokens if left + for (t = src.tokens; t; t = src.tokens) + { + src.tokens = src.tokens->next; + Parse_FreeToken(t); + } + def = NULL; + for (i = 0; i < DEFINEHASHSIZE; i++) + { + if (src.definehash[i]) + { + def = src.definehash[i]; + break; + } + } + // + Z_Free(src.definehash); + // + Parse_FreeScript(script); + //if the define was created succesfully + if (res > 0) return def; + //free the define is created + if (src.defines) Parse_FreeDefine(def); + // + return NULL; +} + +/* +=============== +Parse_AddDefineToSourceFromString +=============== +*/ +static bool Parse_AddDefineToSourceFromString( source_t *source, const char *string ) +{ + Parse_PushScript( source, Parse_LoadScriptMemory(string, strlen(string), "*extern") ); + return Parse_Directive_define( source ); +} + +/* +=============== +Parse_AddGlobalDefine + +add a globals define that will be added to all opened sources +=============== +*/ +bool Parse_AddGlobalDefine(char *string) +{ + define_t *define; + + define = Parse_DefineFromString(string); + if (!define) return false; + define->next = globaldefines; + globaldefines = define; + return true; +} + +/* +=============== +Parse_CopyDefine +=============== +*/ +static define_t *Parse_CopyDefine(define_t *define) +{ + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) Z_Malloc(sizeof(define_t) + strlen(define->name) + 1); + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) + { + newtoken = Parse_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) + { + newtoken = Parse_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } + return newdefine; +} + +/* +=============== +Parse_AddGlobalDefinesToSource +=============== +*/ +static void Parse_AddGlobalDefinesToSource(source_t *source) +{ + define_t *define, *newdefine; + + for (define = globaldefines; define; define = define->next) + { + newdefine = Parse_CopyDefine(define); + Parse_AddDefineToHash(newdefine, source->definehash); + } +} + +/* +=============== +Parse_LoadSourceFile +=============== +*/ +static source_t *Parse_LoadSourceFile(const char *filename) +{ + source_t *source; + script_t *script; + + script = Parse_LoadScriptFile(filename); + if (!script) return NULL; + + script->next = NULL; + + source = (source_t *) Z_Malloc(sizeof(source_t)); + ::memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, filename, MAX_QPATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + + source->definehash = (define_t**)Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *)); + ::memset( source->definehash, 0, DEFINEHASHSIZE * sizeof(define_t *)); + Parse_AddGlobalDefinesToSource(source); + return source; +} + +/* +=============== +Parse_FreeSource +=============== +*/ +static void Parse_FreeSource(source_t *source) +{ + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + int i; + + //Parse_PrintDefineHashTable(source->definehash); + //free all the scripts + while(source->scriptstack) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + Parse_FreeScript(script); + } + //free all the tokens + while(source->tokens) + { + token = source->tokens; + source->tokens = source->tokens->next; + Parse_FreeToken(token); + } + for (i = 0; i < DEFINEHASHSIZE; i++) + { + while(source->definehash[i]) + { + define = source->definehash[i]; + source->definehash[i] = source->definehash[i]->hashnext; + Parse_FreeDefine(define); + } + } + //free all indents + while(source->indentstack) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + Z_Free(indent); + } + // + if (source->definehash) Z_Free(source->definehash); + //free the source itself + Z_Free(source); +} + +#define MAX_SOURCEFILES 64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +/* +=============== +Parse_LoadSourceHandle +=============== +*/ +int Parse_LoadSourceHandle(const char *filename) +{ + source_t *source; + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (!sourceFiles[i]) + break; + } + if (i >= MAX_SOURCEFILES) + return 0; + source = Parse_LoadSourceFile(filename); + if (!source) + return 0; + sourceFiles[i] = source; + return i; +} + +/* +=============== +Parse_FreeSourceHandle +=============== +*/ +bool Parse_FreeSourceHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return false; + if (!sourceFiles[handle]) + return false; + + Parse_FreeSource(sourceFiles[handle]); + sourceFiles[handle] = NULL; + return true; +} + +/* +=============== +Parse_ReadTokenHandle +=============== +*/ +bool Parse_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ + token_t token; + bool ret; + + if (handle < 1 || handle >= MAX_SOURCEFILES) + return false; + if (!sourceFiles[handle]) + return false; + + ret = Parse_ReadToken(sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + if (pc_token->type == TT_STRING) + Parse_StripDoubleQuotes(pc_token->string); + return ret; +} + +/* +=============== +Parse_SourceFileAndLine +=============== +*/ +bool Parse_SourceFileAndLine(int handle, char *filename, int *line) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return false; + if (!sourceFiles[handle]) + return false; + + strcpy(filename, sourceFiles[handle]->filename); + if (sourceFiles[handle]->scriptstack) + *line = sourceFiles[handle]->scriptstack->line; + else + *line = 0; + return true; +} diff --git a/src/qcommon/puff.cpp b/src/qcommon/puff.cpp new file mode 100644 index 0000000..6874b28 --- /dev/null +++ b/src/qcommon/puff.cpp @@ -0,0 +1,759 @@ +/* + * This is a modified version of Mark Adlers work, + * see below for the original copyright. + * 2006 - Joerg Dietrich + */ + +/* + * puff.c + * Copyright (C) 2002-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 1.8, 9 Jan 2004 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format. It is not written for speed but rather simplicity. As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications. For typical deflate + * data, zlib's inflate() is about four times as fast as puff(). zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack. The stack required + * is less than 2K bytes. This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits. puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to to conserve memory. The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format. This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + * http://www.zlib.org/rfc-deflate.html + */ + +/* + * Change history: + * + * 1.0 10 Feb 2002 - First version + * 1.1 17 Feb 2002 - Clarifications of some comments and notes + * - Update puff() dest and source pointers on negative + * errors to facilitate debugging deflators + * - Remove longest from struct huffman -- not needed + * - Simplify offs[] index in construct() + * - Add input size and checking, using longjmp() to + * maintain easy readability + * - Use short data type for large arrays + * - Use pointers instead of long to specify source and + * destination sizes to avoid arbitrary 4 GB limits + * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), + * but leave simple version for readabilty + * - Make sure invalid distances detected if pointers + * are 16 bits + * - Fix fixed codes table error + * - Provide a scanning mode for determining size of + * uncompressed data + * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Jean-loup] + * - Add a puff.h file for the interface + * - Add braces in puff() for else do [Jean-loup] + * - Use indexes instead of pointers for readability + * 1.4 31 Mar 2002 - Simplify construct() code set check + * - Fix some comments + * - Add FIXLCODES #define + * 1.5 6 Apr 2002 - Minor comment fixes + * 1.6 7 Aug 2002 - Minor format changes + * 1.7 3 Mar 2003 - Added test code for distribution + * - Added zlib-like license + * 1.8 9 Jan 2004 - Added some comments on no distance codes case + */ + +#include "puff.h" /* prototype for puff() */ + +#include /* for setjmp(), longjmp(), and jmp_buf */ + +#define local static /* for local function definitions */ + +/* + * Maximums for allocations and loops. It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15 /* maximum bits in a code */ +#define MAXLCODES 286 /* maximum number of literal/length codes */ +#define MAXDCODES 30 /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ +#define FIXLCODES 288 /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { + /* output state */ + uint8_t *out; /* output buffer */ + uint32_t outlen; /* available space at out */ + uint32_t outcnt; /* bytes written to out so far */ + + /* input state */ + uint8_t *in; /* input buffer */ + uint32_t inlen; /* available input at in */ + uint32_t incnt; /* bytes read so far */ + int32_t bitbuf; /* bit buffer */ + int32_t bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int32_t bits(struct state *s, int32_t need) +{ + int32_t val; /* bit accumulator (can use up to 20 bits) */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->incnt == s->inlen) longjmp(s->env, 1); /* out of input */ + val |= (int32_t)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = (int32_t)(val >> need); + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return (int32_t)(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + * stored bytes are byte-aligned for fast copying. Therefore any leftover + * bits in the byte that has the last bit of the type, as many as seven, are + * discarded. The value of the discarded bits are not defined and should not + * be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + * checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length. This is sometimes used to byte-align + * subsets of the compressed data for random access or partial recovery. + */ +local int32_t stored(struct state *s) +{ + uint32_t len; /* length of stored block */ + + /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ + s->bitbuf = 0; + s->bitcnt = 0; + + /* get length and check against its one's complement */ + if (s->incnt + 4 > s->inlen) return 2; /* not enough input */ + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if (s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff)) + return -2; /* didn't match complement! */ + + /* copy len bytes from in to out */ + if (s->incnt + len > s->inlen) return 2; /* not enough input */ + if (s->out != NULL) { + if (s->outcnt + len > s->outlen) + return 1; /* not enough output space */ + while (len--) + s->out[s->outcnt++] = s->in[s->incnt++]; + } + else { /* just scanning */ + s->outcnt += len; + s->incnt += len; + } + + /* done with a valid stored block */ + return 0; +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + int16_t *count; /* number of symbols of each length */ + int16_t *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -9 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. A table-based decoding + * scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros. Subsequent codes of + * the same length are simply integer increments of the previous code. When + * moving up a length, a zero bit is appended to the code. For a complete + * code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + * in the deflate format. See the format notes for fixed() and dynamic(). + */ +local int32_t decode(struct state *s, struct huffman *h) +{ + int32_t len; /* current number of bits in code */ + int32_t code; /* len bits being decoded */ + int32_t first; /* first code of length len */ + int32_t count; /* number of codes of length len */ + int32_t index; /* index of first code of length len in symbol table */ + int32_t bitbuf; /* bits from stream */ + int32_t left; /* bits left in next or left to process */ + int16_t *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if (code < first + count) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) break; + if (s->incnt == s->inlen) longjmp(s->env, 1); /* out of input */ + bitbuf = s->in[s->incnt++]; + if (left > 8) left = 8; + } + return -9; /* ran out of codes */ +} + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code. So n - h->count[0] is the number of + * codes. This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + * codes and any code with a single symbol which in deflate is coded as one + * bit instead of zero bits. See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + * the code bits definition. + */ +local int32_t construct(struct huffman *h, int16_t *length, int32_t n) +{ + int32_t symbol; /* current symbol when stepping through length[] */ + int32_t len; /* current length when stepping through h->count[] */ + int32_t left; /* number of possible codes left of current length */ + int16_t offs[MAXBITS+1]; /* offsets in symbol table for each length */ + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + * description if dynamic is a combination of literals and length/distance + * pairs terminated by and end-of-block code. Literals are simply Huffman + * coded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + * code of up to 286 symbols. They are 256 literals (0..255), 29 length + * symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + * to represent all of those. Lengths 3..10 and 258 are in fact represented + * by just a length symbol. Lengths 11..257 are represented as a symbol and + * some number of extra bits that are added as an integer to the base length + * of the length symbol. The number of extra bits is determined by the base + * length symbol. These are in the static arrays below, lens[] for the base + * lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + * often in highly redundant files. Note that 258 can also be coded as the + * base value 227 plus the maximum extra value of 31. While a good deflate + * should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + * followed a distance code. There are up to 30 distance symbols. Again + * there are many more possible distances (1..32768), so extra bits are added + * to a base value represented by the symbol. The distances 1..4 get their + * own symbol, but the rest require extra bits. The base distances and + * corresponding number of extra bits are below in the static arrays dist[] + * and dext[]. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 258 + * simply copies the last byte 258 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. You should not use memcpy() since its behavior is not + * defined for overlapped arrays. You should not use memmove() or bcopy() + * since though their behavior -is- defined for overlapping arrays, it is + * defined to do the wrong thing in this case. + */ +local int32_t codes(struct state *s, + struct huffman *lencode, + struct huffman *distcode) +{ + int32_t symbol; /* decoded symbol */ + int32_t len; /* length for copy */ + uint32_t dist; /* distance for copy */ + static const int16_t lens[29] = { /* Size base for length codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + static const int16_t lext[29] = { /* Extra bits for length codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const int16_t dists[30] = { /* Offset base for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; + static const int16_t dext[30] = { /* Extra bits for distance codes 0..29 */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + /* decode literals and length/distance pairs */ + do { + symbol = decode(s, lencode); + if (symbol < 0) return symbol; /* invalid symbol */ + if (symbol < 256) { /* literal: symbol is the byte */ + /* write out the literal */ + if (s->out != NULL) { + if (s->outcnt == s->outlen) return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } + else if (symbol > 256) { /* length */ + /* get and compute length */ + symbol -= 257; + if (symbol >= 29) return -9; /* invalid fixed code */ + len = lens[symbol] + bits(s, lext[symbol]); + + /* get and check distance */ + symbol = decode(s, distcode); + if (symbol < 0) return symbol; /* invalid symbol */ + dist = dists[symbol] + bits(s, dext[symbol]); + if (dist > s->outcnt) + return -10; /* distance too far back */ + + /* copy length bytes from distance bytes back */ + if (s->out != NULL) { + if (s->outcnt + len > s->outlen) return 1; + while (len--) { + s->out[s->outcnt] = s->out[s->outcnt - dist]; + s->outcnt++; + } + } + else + s->outcnt += len; + } + } while (symbol != 256); /* end of block symbol */ + + /* done with a valid fixed or dynamic block */ + return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + * which the size of the code descriptions in a dynamic block exceeds the + * benefit of custom codes for that block. For fixed codes, no bits are + * spent on code descriptions. Instead the code lengths for literal/length + * codes and distance codes are fixed. The specific lengths for each symbol + * can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + * and should result in an error if received. This cannot be implemented + * simply as an incomplete code since those two symbols are in the "middle" + * of the code. They are eight bits long and the longest literal/length\ + * code is nine bits. Therefore the code must be constructed with those + * symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + * in an error if received. Since all of the distance codes are the same + * length, this can be implemented as an incomplete code. Then the invalid + * codes are detected while decoding. + */ +local int32_t fixed(struct state *s) +{ + static int32_t virgin = 1; + static int16_t lencnt[MAXBITS+1], lensym[FIXLCODES]; + static int16_t distcnt[MAXBITS+1], distsym[MAXDCODES]; + static struct huffman lencode = {lencnt, lensym}; + static struct huffman distcode = {distcnt, distsym}; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + int32_t symbol; + int16_t lengths[FIXLCODES]; + + /* literal/length table */ + for (symbol = 0; symbol < 144; symbol++) + lengths[symbol] = 8; + for (; symbol < 256; symbol++) + lengths[symbol] = 9; + for (; symbol < 280; symbol++) + lengths[symbol] = 7; + for (; symbol < FIXLCODES; symbol++) + lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + /* distance table */ + for (symbol = 0; symbol < MAXDCODES; symbol++) + lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + /* do this just once */ + virgin = 0; + } + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + * distance codes for that block. New dynamic blocks allow the compressor to + * rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + * the actual bits of the codes are generated in an unambiguous way simply + * from the number of bits in each code. Therefore the code descriptions + * are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + * provided for each of the literal/length symbols, and for each of the + * distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as + * as the code length. This does not mean a zero-length code, but rather + * that no code should be created for this symbol. There is no way in the + * deflate format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + * any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + * interesting consequence. Normally if only one symbol is used for a given + * code, then in fact that code could be represented with zero bits. However + * in deflate, that code has to be at least one bit. So for example, if + * only a single distance base symbol appears in a block, then it will be + * represented by a single code of length one, in particular one 0 bit. This + * is an incomplete code, since if a 1 bit is received, it has no meaning, + * and should result in an error. So incomplete distance codes of one symbol + * should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + * must be the end-of-block code, since every dynamic block has one. This + * is not the most efficient way to create an empty block (an empty fixed + * block is fewer bits), but it is allowed by the format. So incomplete + * literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + * codes. This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + * are themselves compressed using Huffman codes and run-length encoding. In + * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + * that length, and the symbols 16, 17, and 18 are run-length instructions. + * Each of 16, 17, and 18 are follwed by extra bits to define the length of + * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 + * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols + * are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + * described first. This is simply a sequence of up to 19 three-bit values + * representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + * the number of literal/length code lengths, the number of distance code + * lengths, and the number of code length code lengths (ok, you come up with + * a better name!) in the code descriptions. For the literal/length and + * distance codes, lengths after those provided are considered zero, i.e. no + * code. The code length code lengths are received in a permuted order (see + * the order[] array below) to make a short code length code length list more + * likely. As it turns out, very short and very long codes are less likely + * to be seen in a dynamic code description, hence what may appear initially + * to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + * lengths (ndist), then they are treated as one long list of nlen + ndist + * code lengths. Therefore run-length coding can and often does cross the + * boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + * three counts for the number of code lengths for the literal/length codes, + * the distance codes, and the code length codes. This is followed by the + * code length code lengths, three bits each. This is used to construct the + * code length code which is used to read the remainder of the lengths. Then + * the literal/length code lengths and distance lengths are read as a single + * set of lengths using the code length codes. Codes are constructed from + * the resulting two sets of lengths, and then finally you can start + * decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + * block is around 80 bytes. + */ +local int32_t dynamic(struct state *s) +{ + int32_t nlen, ndist, ncode; /* number of lengths in descriptor */ + int32_t index; /* index of lengths[] */ + int32_t err; /* construct() return value */ + int16_t lengths[MAXCODES]; /* descriptor code lengths */ + int16_t lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ + int16_t distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ + struct huffman lencode = {lencnt, lensym}; /* length code */ + struct huffman distcode = {distcnt, distsym}; /* distance code */ + static const int16_t order[19] = /* permutation of code length codes */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* get number of lengths in each table, check lengths */ + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if (nlen > MAXLCODES || ndist > MAXDCODES) + return -3; /* bad counts */ + + /* read code length code lengths (really), missing lengths are zero */ + for (index = 0; index < ncode; index++) + lengths[order[index]] = bits(s, 3); + for (; index < 19; index++) + lengths[order[index]] = 0; + + /* build huffman table for code lengths codes (use lencode temporarily) */ + err = construct(&lencode, lengths, 19); + if (err != 0) return -4; /* require complete code set here */ + + /* read length/literal and distance code length tables */ + index = 0; + while (index < nlen + ndist) { + int32_t symbol; /* decoded value */ + int32_t len; /* last length to repeat */ + + symbol = decode(s, &lencode); + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + len = 0; /* assume repeating zeros */ + if (symbol == 16) { /* repeat last length 3..6 times */ + if (index == 0) return -5; /* no last length! */ + len = lengths[index - 1]; /* last length */ + symbol = 3 + bits(s, 2); + } + else if (symbol == 17) /* repeat zero 3..10 times */ + symbol = 3 + bits(s, 3); + else /* == 18, repeat zero 11..138 times */ + symbol = 11 + bits(s, 7); + if (index + symbol > nlen + ndist) + return -6; /* too many lengths! */ + while (symbol--) /* repeat last or zero symbol times */ + lengths[index++] = len; + } + } + + /* build huffman table for literal/length codes */ + err = construct(&lencode, lengths, nlen); + if (err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) + return -7; /* only allow incomplete codes if just one code */ + + /* build huffman table for distance codes */ + err = construct(&distcode, lengths + nlen, ndist); + if (err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) + return -8; /* only allow incomplete codes if just one code */ + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest. On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero. If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space. In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written. For this dest must be (uint8_t *)0. In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + * 2: available inflate data did not terminate + * 1: output space exhausted before completing inflate + * 0: successful inflate + * -1: invalid block type (type == 3) + * -2: stored block length did not match one's complement + * -3: dynamic block code description: too many length or distance codes + * -4: dynamic block code description: code lengths codes incomplete + * -5: dynamic block code description: repeat lengths with no first length + * -6: dynamic block code description: repeat more than specified lengths + * -7: dynamic block code description: invalid literal/length code lengths + * -8: dynamic block code description: invalid distance code lengths + * -9: invalid literal/length or distance code in fixed or dynamic block + * -10: distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + * whether or not it is the last block. Then the block is decoded and the + * process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + * block (if it was a fixed or dynamic block) are undefined and have no + * expected values to check. + */ +int32_t puff(uint8_t *dest, /* pointer to destination pointer */ + uint32_t *destlen, /* amount of output space */ + uint8_t *source, /* pointer to source data pointer */ + uint32_t *sourcelen) /* amount of input available */ +{ + struct state s; /* input/output state */ + int32_t last, type; /* block information */ + int32_t err; /* return value */ + + /* initialize output state */ + s.out = dest; + s.outlen = *destlen; /* ignored if dest is NULL */ + s.outcnt = 0; + + /* initialize input state */ + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp() */ + err = 2; /* then skip do-loop, return error */ + else { + /* process blocks until last block or error */ + do { + last = bits(&s, 1); /* one if last block */ + type = bits(&s, 2); /* block type 0..3 */ + err = type == 0 ? stored(&s) : + (type == 1 ? fixed(&s) : + (type == 2 ? dynamic(&s) : + -1)); /* type == 3, invalid */ + if (err != 0) break; /* return with error */ + } while (!last); + } + + /* update the lengths and return */ + if (err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + return err; +} diff --git a/src/qcommon/puff.h b/src/qcommon/puff.h new file mode 100644 index 0000000..14070f6 --- /dev/null +++ b/src/qcommon/puff.h @@ -0,0 +1,43 @@ +/* + * This is a modified version of Mark Adlers work, + * see below for the original copyright. + * 2006 - Joerg Dietrich + */ + +/* puff.h + Copyright (C) 2002, 2003 Mark Adler, all rights reserved + version 1.7, 3 Mar 2002 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +#ifndef __PUFF_H +#define __PUFF_H + +#include "q_shared.h" /* for definitions of the types */ + +/* + * See puff.c for purpose and usage. + */ +int32_t puff(uint8_t *dest, /* pointer to destination pointer */ + uint32_t *destlen, /* amount of output space */ + uint8_t *source, /* pointer to source data pointer */ + uint32_t *sourcelen); /* amount of input available */ + +#endif // __PUFF_H diff --git a/src/qcommon/q3_lauxlib.cpp b/src/qcommon/q3_lauxlib.cpp new file mode 100644 index 0000000..ee7efa2 --- /dev/null +++ b/src/qcommon/q3_lauxlib.cpp @@ -0,0 +1,46 @@ +#include "q3_lauxlib.h" + +#include + +#include +#include + +#include "sys/sys_shared.h" + +#include "cvar.h" +#include "msg.h" +#include "net.h" +#include "q_shared.h" +#include "qcommon.h" + +size_t qlua_writestring(const char* string, size_t n) +{ +#ifndef DEDICATED + CL_ConsolePrint( string ); +#endif + Q_StripIndentMarker( const_cast(string) ); + Sys_Print( string ); + + return n; +} + +int qlua_writeline(void) +{ +#ifndef DEDICATED + CL_ConsolePrint( "\n" ); +#endif + Sys_Print( "\n" ); + return 0; +} + +int qlua_writestringerror(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char m[MAXPRINTMSG]; + Q_vsnprintf(m, sizeof(m), fmt, ap); + va_end (ap); + Com_Printf(S_COLOR_YELLOW "%s\n", m); + return 0; +} + diff --git a/src/qcommon/q3_lauxlib.h b/src/qcommon/q3_lauxlib.h new file mode 100644 index 0000000..caba8e4 --- /dev/null +++ b/src/qcommon/q3_lauxlib.h @@ -0,0 +1,46 @@ +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . + +#ifndef OVERRIDE_LAUXLIB_H +#define OVERRIDE_LAUXLIB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +size_t qlua_writestring(const char* string, size_t n); +int qlua_writeline(void); +int qlua_writestringerror(const char *fmt, ...); + +#define lua_writestring qlua_writestring +#define lua_writeline qlua_writeline +#define lua_writestringerror qlua_writestringerror + +#define LUA_TMPNAMTEMPLATE "/tmp/tremulous_XXXXXX" + +// Because: src/lua-5.3.3/include/luaconf.h:69:9: warning: 'LUA_USE_POSIX' macro redefined [-Wmacro-redefined] +//#ifndef _WIN32 +//#define LUA_USE_POSIX 1 +//#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c index 2a641a9..9944850 100644 --- a/src/qcommon/q_math.c +++ b/src/qcommon/q_math.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // @@ -36,133 +37,28 @@ vec3_t vec3_origin = {0,0,0}; vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; -vec4_t colorBlack = {0.000f, 0.000f, 0.000f, 1.000f}; -vec4_t colorRed = {1.000f, 0.000f, 0.000f, 1.000f}; -vec4_t colorGreen = {0.000f, 1.000f, 0.000f, 1.000f}; -vec4_t colorBlue = {0.000f, 0.000f, 1.000f, 1.000f}; -vec4_t colorYellow = {1.000f, 1.000f, 0.000f, 1.000f}; -vec4_t colorMagenta = {1.000f, 0.000f, 1.000f, 1.000f}; -vec4_t colorCyan = {0.000f, 1.000f, 1.000f, 1.000f}; -vec4_t colorWhite = {1.000f, 1.000f, 1.000f, 1.000f}; -vec4_t colorGray = {0.502f, 0.502f, 0.502f, 1.000f}; -vec4_t colorOrange = {1.000f, 0.686f, 0.000f, 1.000f}; -vec4_t colorRoseBud = {0.996f, 0.671f, 0.604f, 1.000f}; -vec4_t colorPaleGreen = {0.596f, 0.984f, 0.596f, 1.000f}; -vec4_t colorPaleGolden = {0.933f, 0.910f, 0.667f, 1.000f}; -vec4_t colorColumbiaBlue = {0.608f, 0.867f, 1.000f, 1.000f}; -vec4_t colorPaleTurquoise = {0.686f, 0.933f, 0.933f, 1.000f}; -vec4_t colorPaleVioletRed = {0.859f, 0.439f, 0.576f, 1.000f}; -vec4_t colorPalacePaleWhite = {0.910f, 0.898f, 0.863f, 1.000f}; -vec4_t colorOlive = {0.231f, 0.235f, 0.212f, 1.000f}; -vec4_t colorTomato = {1.000f, 0.388f, 0.278f, 1.000f}; -vec4_t colorLime = {0.749f, 1.000f, 0.000f, 1.000f}; -vec4_t colorLemon = {1.000f, 0.969f, 0.000f, 1.000f}; -vec4_t colorBlueBerry = {0.310f, 0.525f, 0.969f, 1.000f}; -vec4_t colorTurquoise = {0.251f, 0.878f, 0.816f, 1.000f}; -vec4_t colorWildWatermelon = {0.992f, 0.357f, 0.471f, 1.000f}; -vec4_t colorSaltpan = {0.933f, 0.953f, 0.898f, 1.000f}; -vec4_t colorGrayChateau = {0.624f, 0.639f, 0.655f, 1.000f}; -vec4_t colorRust = {0.718f, 0.255f, 0.055f, 1.000f}; -vec4_t colorCopperGreen = {0.431f, 0.553f, 0.443f, 1.000f}; -vec4_t colorGold = {1.000f, 0.843f, 0.000f, 1.000f}; -vec4_t colorSteelBlue = {0.275f, 0.510f, 0.706f, 1.000f}; -vec4_t colorSteelGray = {0.482f, 0.565f, 0.584f, 1.000f}; -vec4_t colorBronze = {0.804f, 0.498f, 0.196f, 1.000f}; -vec4_t colorSilver = {0.753f, 0.753f, 0.753f, 1.000f}; -vec4_t colorDarkGray = {0.663f, 0.663f, 0.663f, 1.000f}; -vec4_t colorDarkOrange = {1.000f, 0.549f, 0.000f, 1.000f}; -vec4_t colorDarkGreen = {0.000f, 0.392f, 0.000f, 1.000f}; -vec4_t colorRedOrange = {1.000f, 0.247f, 0.204f, 1.000f}; -vec4_t colorForestGreen = {0.133f, 0.545f, 0.133f, 1.000f}; -vec4_t colorBrightSun = {0.926f, 0.741f, 0.173f, 1.000f}; -vec4_t colorMediumSlateBlue = {0.482f, 0.408f, 0.933f, 1.000f}; -vec4_t colorCeleste = {0.698f, 1.000f, 1.000f, 1.000f}; -vec4_t colorIronstone = {0.525f, 0.314f, 0.251f, 1.000f}; -vec4_t colorTimberwolf = {0.859f, 0.843f, 0.824f, 1.000f}; -vec4_t colorOnyx = {0.059f, 0.059f, 0.059f, 1.000f}; -vec4_t colorRosewood = {0.396f, 0.000f, 0.043f, 1.000f}; -vec4_t colorKokoda = {0.482f, 0.471f, 0.353f, 1.000f}; -vec4_t colorPorsche = {0.875f, 0.616f, 0.357f, 1.000f}; -vec4_t colorCloudBurst = {0.208f, 0.369f, 0.310f, 1.000f}; -vec4_t colorBlueDiane = {0.208f, 0.318f, 0.310f, 1.000f}; -vec4_t colorRope = {0.557f, 0.349f, 0.235f, 1.000f}; -vec4_t colorBlonde = {0.980f, 0.941f, 0.745f, 1.000f}; -vec4_t colorSmokeyBlack = {0.063f, 0.047f, 0.031f, 1.000f}; -vec4_t colorAmericanRose = {1.000f, 0.012f, 0.243f, 1.000f}; -vec4_t colorNeonGreen = {0.224f, 1.000f, 0.078f, 1.000f}; -vec4_t colorNeonYellow = {0.980f, 0.929f, 0.153f, 1.000f}; -vec4_t colorUltramarine = {0.071f, 0.039f, 0.561f, 1.000f}; -vec4_t colorTurquoiseBlue = {0.000f, 1.000f, 0.937f, 1.000f}; -vec4_t colorDarkMagenta = {0.545f, 0.000f, 0.545f, 1.000f}; -vec4_t colorMagicMint = {0.667f, 0.941f, 0.820f, 1.000f}; -vec4_t colorLightGray = {0.827f, 0.827f, 0.827f, 1.000f}; -vec4_t colorLightSalmon = {1.000f, 0.600f, 0.600f, 1.000f}; -vec4_t colorLightGreen = {0.565f, 0.933f, 0.565f, 1.000f}; - -vec4_t g_color_table[62] = +vec4_t colorBlack = {0, 0, 0, 1}; +vec4_t colorRed = {1, 0, 0, 1}; +vec4_t colorGreen = {0, 1, 0, 1}; +vec4_t colorBlue = {0, 0, 1, 1}; +vec4_t colorYellow = {1, 1, 0, 1}; +vec4_t colorMagenta= {1, 0, 1, 1}; +vec4_t colorCyan = {0, 1, 1, 1}; +vec4_t colorWhite = {1, 1, 1, 1}; +vec4_t colorLtGrey = {0.75, 0.75, 0.75, 1}; +vec4_t colorMdGrey = {0.5, 0.5, 0.5, 1}; +vec4_t colorDkGrey = {0.25, 0.25, 0.25, 1}; + +vec4_t g_color_table[8] = { - {0.250f, 0.250f, 0.250f, 1.000f}, - {1.000f, 0.000f, 0.000f, 1.000f}, - {0.000f, 1.000f, 0.000f, 1.000f}, - {1.000f, 1.000f, 0.000f, 1.000f}, - {0.000f, 0.000f, 1.000f, 1.000f}, - {0.000f, 1.000f, 1.000f, 1.000f}, - {1.000f, 0.000f, 1.000f, 1.000f}, - {1.000f, 1.000f, 1.000f, 1.000f}, - {0.502f, 0.502f, 0.502f, 1.000f}, - {1.000f, 0.686f, 0.000f, 1.000f}, - {0.996f, 0.671f, 0.604f, 1.000f}, - {0.596f, 0.984f, 0.596f, 1.000f}, - {0.933f, 0.910f, 0.667f, 1.000f}, - {0.608f, 0.867f, 1.000f, 1.000f}, - {0.686f, 0.933f, 0.933f, 1.000f}, - {0.859f, 0.439f, 0.576f, 1.000f}, - {0.910f, 0.898f, 0.863f, 1.000f}, - {0.231f, 0.235f, 0.212f, 1.000f}, - {1.000f, 0.388f, 0.278f, 1.000f}, - {0.749f, 1.000f, 0.000f, 1.000f}, - {1.000f, 0.969f, 0.000f, 1.000f}, - {0.310f, 0.525f, 0.969f, 1.000f}, - {0.251f, 0.878f, 0.816f, 1.000f}, - {0.992f, 0.357f, 0.471f, 1.000f}, - {0.933f, 0.953f, 0.898f, 1.000f}, - {0.624f, 0.639f, 0.655f, 1.000f}, - {0.718f, 0.255f, 0.055f, 1.000f}, - {0.431f, 0.553f, 0.443f, 1.000f}, - {1.000f, 0.843f, 0.000f, 1.000f}, - {0.275f, 0.510f, 0.706f, 1.000f}, - {0.482f, 0.565f, 0.584f, 1.000f}, - {0.804f, 0.498f, 0.196f, 1.000f}, - {0.753f, 0.753f, 0.753f, 1.000f}, - {0.663f, 0.663f, 0.663f, 1.000f}, - {1.000f, 0.549f, 0.000f, 1.000f}, - {0.000f, 0.392f, 0.000f, 1.000f}, - {1.000f, 0.247f, 0.204f, 1.000f}, - {0.133f, 0.545f, 0.133f, 1.000f}, - {0.926f, 0.741f, 0.173f, 1.000f}, - {0.482f, 0.408f, 0.933f, 1.000f}, - {0.698f, 1.000f, 1.000f, 1.000f}, - {0.525f, 0.314f, 0.251f, 1.000f}, - {0.859f, 0.843f, 0.824f, 1.000f}, - {0.059f, 0.059f, 0.059f, 1.000f}, - {0.396f, 0.000f, 0.043f, 1.000f}, - {0.482f, 0.471f, 0.353f, 1.000f}, - {0.875f, 0.616f, 0.357f, 1.000f}, - {0.208f, 0.369f, 0.310f, 1.000f}, - {0.208f, 0.318f, 0.310f, 1.000f}, - {0.557f, 0.349f, 0.235f, 1.000f}, - {0.980f, 0.941f, 0.745f, 1.000f}, - {0.063f, 0.047f, 0.031f, 1.000f}, - {1.000f, 0.012f, 0.243f, 1.000f}, - {0.224f, 1.000f, 0.078f, 1.000f}, - {0.980f, 0.929f, 0.153f, 1.000f}, - {0.071f, 0.039f, 0.561f, 1.000f}, - {0.000f, 1.000f, 0.937f, 1.000f}, - {0.545f, 0.000f, 0.545f, 1.000f}, - {0.667f, 0.941f, 0.820f, 1.000f}, - {0.827f, 0.827f, 0.827f, 1.000f}, - {1.000f, 0.600f, 0.600f, 1.000f}, - {0.565f, 0.933f, 0.565f, 1.000f}, + {0.2, 0.2, 0.2, 1.0}, + {1.0, 0.0, 0.0, 1.0}, + {0.0, 1.0, 0.0, 1.0}, + {1.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 1.0}, + {0.0, 1.0, 1.0, 1.0}, + {1.0, 0.0, 1.0, 1.0}, + {1.0, 1.0, 1.0, 1.0}, }; @@ -601,7 +497,7 @@ void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) inv_denom = 1.0f / DotProduct( normal, normal ); #ifndef Q3_VM - assert( Q_fabs(inv_denom) != 0.0f ); // bk010122 - zero vectors get here + assert( Q_fabs(inv_denom) != 0.0f ); // zero vectors get here #endif inv_denom = 1.0f / inv_denom; @@ -655,10 +551,7 @@ void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ) */ float Q_rsqrt( float number ) { - union { - float f; - int i; - } t; + floatint_t t; float x2, y; const float threehalfs = 1.5F; @@ -669,14 +562,14 @@ float Q_rsqrt( float number ) y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed - //assert( !isnan(y) ); // bk010122 - FPE? return y; } float Q_fabs( float f ) { - int tmp = * ( int * ) &f; - tmp &= 0x7FFFFFFF; - return * ( float * ) &tmp; + floatint_t fi; + fi.f = f; + fi.i &= 0x7FFFFFFF; + return fi.f; } #endif @@ -804,50 +697,14 @@ void SetPlaneSignbits (cplane_t *out) { BoxOnPlaneSide Returns 1, 2, or 1 + 2 - -// this is the slow, general version -int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) -{ - int i; - float dist1, dist2; - int sides; - vec3_t corners[2]; - - for (i=0 ; i<3 ; i++) - { - if (p->normal[i] < 0) - { - corners[0][i] = emins[i]; - corners[1][i] = emaxs[i]; - } - else - { - corners[1][i] = emins[i]; - corners[0][i] = emaxs[i]; - } - } - dist1 = DotProduct (p->normal, corners[0]) - p->dist; - dist2 = DotProduct (p->normal, corners[1]) - p->dist; - sides = 0; - if (dist1 >= 0) - sides = 1; - if (dist2 < 0) - sides |= 2; - - return sides; -} - ================== */ - -#if !id386 - -int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +int BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p) { - float dist1, dist2; - int sides; + float dist[2]; + int sides, b, i; -// fast axial cases + // fast axial cases if (p->type < 3) { if (p->dist <= emins[p->type]) @@ -857,291 +714,27 @@ int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) return 3; } -// general case - switch (p->signbits) + // general case + dist[0] = dist[1] = 0; + if (p->signbits < 8) // >= 8: default case is original code (dist[0]=dist[1]=0) { - case 0: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - break; - case 1: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - break; - case 2: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - break; - case 3: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - break; - case 4: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - break; - case 5: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; - break; - case 6: - dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - break; - case 7: - dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; - dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; - break; - default: - dist1 = dist2 = 0; // shut up compiler - break; + for (i=0 ; i<3 ; i++) + { + b = (p->signbits >> i) & 1; + dist[ b] += p->normal[i]*emaxs[i]; + dist[!b] += p->normal[i]*emins[i]; + } } sides = 0; - if (dist1 >= p->dist) + if (dist[0] >= p->dist) sides = 1; - if (dist2 < p->dist) + if (dist[1] < p->dist) sides |= 2; return sides; } -#elif __GNUC__ -// use matha.s -#else -#pragma warning( disable: 4035 ) - -__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) -{ - static int bops_initialized; - static int Ljmptab[8]; - - __asm { - - push ebx - - cmp bops_initialized, 1 - je initialized - mov bops_initialized, 1 - - mov Ljmptab[0*4], offset Lcase0 - mov Ljmptab[1*4], offset Lcase1 - mov Ljmptab[2*4], offset Lcase2 - mov Ljmptab[3*4], offset Lcase3 - mov Ljmptab[4*4], offset Lcase4 - mov Ljmptab[5*4], offset Lcase5 - mov Ljmptab[6*4], offset Lcase6 - mov Ljmptab[7*4], offset Lcase7 - -initialized: - - mov edx,dword ptr[4+12+esp] - mov ecx,dword ptr[4+4+esp] - xor eax,eax - mov ebx,dword ptr[4+8+esp] - mov al,byte ptr[17+edx] - cmp al,8 - jge Lerror - fld dword ptr[0+edx] - fld st(0) - jmp dword ptr[Ljmptab+eax*4] -Lcase0: - fmul dword ptr[ebx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ecx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ebx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ecx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ebx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ecx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase1: - fmul dword ptr[ecx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ebx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ebx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ecx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ebx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ecx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase2: - fmul dword ptr[ebx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ecx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ecx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ebx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ebx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ecx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase3: - fmul dword ptr[ecx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ebx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ecx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ebx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ebx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ecx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase4: - fmul dword ptr[ebx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ecx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ebx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ecx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ecx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ebx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase5: - fmul dword ptr[ecx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ebx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ebx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ecx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ecx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ebx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase6: - fmul dword ptr[ebx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ecx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ecx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ebx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ecx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ebx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) - jmp LSetSides -Lcase7: - fmul dword ptr[ecx] - fld dword ptr[0+4+edx] - fxch st(2) - fmul dword ptr[ebx] - fxch st(2) - fld st(0) - fmul dword ptr[4+ecx] - fld dword ptr[0+8+edx] - fxch st(2) - fmul dword ptr[4+ebx] - fxch st(2) - fld st(0) - fmul dword ptr[8+ecx] - fxch st(5) - faddp st(3),st(0) - fmul dword ptr[8+ebx] - fxch st(1) - faddp st(3),st(0) - fxch st(3) - faddp st(2),st(0) -LSetSides: - faddp st(2),st(0) - fcomp dword ptr[12+edx] - xor ecx,ecx - fnstsw ax - fcomp dword ptr[12+edx] - and ah,1 - xor ah,1 - add cl,ah - fnstsw ax - and ah,1 - add ah,ah - add cl,ah - pop ebx - mov eax,ecx - ret -Lerror: - int 3 - } -} -#pragma warning( default: 4035 ) -#endif /* ================= @@ -1191,16 +784,64 @@ void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { } } +qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs, + const vec3_t mins2, const vec3_t maxs2) +{ + if ( maxs[0] < mins2[0] || + maxs[1] < mins2[1] || + maxs[2] < mins2[2] || + mins[0] > maxs2[0] || + mins[1] > maxs2[1] || + mins[2] > maxs2[2]) + { + return qfalse; + } + + return qtrue; +} + +qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs, + const vec3_t origin, vec_t radius) +{ + if ( origin[0] - radius > maxs[0] || + origin[0] + radius < mins[0] || + origin[1] - radius > maxs[1] || + origin[1] + radius < mins[1] || + origin[2] - radius > maxs[2] || + origin[2] + radius < mins[2]) + { + return qfalse; + } + + return qtrue; +} + +qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs, + const vec3_t origin) +{ + if ( origin[0] > maxs[0] || + origin[0] < mins[0] || + origin[1] > maxs[1] || + origin[1] < mins[1] || + origin[2] > maxs[2] || + origin[2] < mins[2]) + { + return qfalse; + } + + return qtrue; +} vec_t VectorNormalize( vec3_t v ) { - // NOTE: TTimo - Apple G4 altivec source uses double? float length, ilength; length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; - length = sqrt (length); if ( length ) { - ilength = 1/length; + /* writing it this way allows gcc to recognize that rsqrt can be used */ + ilength = 1/(float)sqrt (length); + /* sqrt(length) = length * (1 / sqrt(length)) */ + length *= ilength; v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; @@ -1213,21 +854,17 @@ vec_t VectorNormalize2( const vec3_t v, vec3_t out) { float length, ilength; length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; - length = sqrt (length); if (length) { -#ifndef Q3_VM // bk0101022 - FPE related -// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) ); -#endif - ilength = 1/length; + /* writing it this way allows gcc to recognize that rsqrt can be used */ + ilength = 1/(float)sqrt (length); + /* sqrt(length) = length * (1 / sqrt(length)) */ + length *= ilength; out[0] = v[0]*ilength; out[1] = v[1]*ilength; out[2] = v[2]*ilength; } else { -#ifndef Q3_VM // bk0101022 - FPE related -// assert( ((Q_fabs(v[0])==0.0f) && (Q_fabs(v[1])==0.0f) && (Q_fabs(v[2])==0.0f)) ); -#endif VectorClear( out ); } @@ -1653,15 +1290,40 @@ Don't pass doubles to this */ int Q_isnan( float x ) { - union - { - float f; - unsigned int i; - } t; + floatint_t fi; + + fi.f = x; + fi.ui &= 0x7FFFFFFF; + fi.ui = 0x7F800000 - fi.ui; + + return (int)( (unsigned int)fi.ui >> 31 ); +} +//------------------------------------------------------------------------ + +#ifndef Q3_VM +/* +===================== +Q_acos - t.f = x; - t.i &= 0x7FFFFFFF; - t.i = 0x7F800000 - t.i; +the msvc acos doesn't always return a value between -PI and PI: - return (int)( (unsigned int)t.i >> 31 ); +int i; +i = 1065353246; +acos(*(float*) &i) == -1.#IND0 + +===================== +*/ +float Q_acos(float c) { + float angle; + + angle = acos(c); + + if (angle > M_PI) { + return (float)M_PI; + } + if (angle < -M_PI) { + return (float)M_PI; + } + return angle; } +#endif diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h index 84b87b9..b72d285 100644 --- a/src/qcommon/q_platform.h +++ b/src/qcommon/q_platform.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,20 +17,27 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ -// + #ifndef __Q_PLATFORM_H #define __Q_PLATFORM_H +#ifdef __cplusplus +extern "C" { +#endif + // this is for determining if we have an asm version of a C function +#define idx64 0 + #ifdef Q3_VM #define id386 0 #define idppc 0 #define idppc_altivec 0 +#define idsparc 0 #else @@ -59,12 +67,19 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define idppc_altivec 0 #endif +#if defined(__sparc__) && !defined(C_ONLY) +#define idsparc 1 +#else +#define idsparc 0 +#endif + #endif #ifndef __ASM_I386__ // don't include the C bits if included from qasm.h // for windows fastcall option #define QDECL +#define QCALL //================================================================= WIN64/32 === @@ -101,7 +116,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // For cl_updates.cpp #define RELEASE_PACKAGE_NAME ( "release-mingw32-" ARCH_STRING ".zip" ) - + #define RELEASE_SIGNATURE_NAME ( "release-mingw32-" ARCH_STRING ".zip.sig" ) + #define GRANGER_EXE ( "granger" EXE_EXT ) + #elif defined(_WIN32) || defined(__WIN32__) #undef QDECL @@ -132,7 +149,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // For cl_updates.cpp #define RELEASE_PACKAGE_NAME ( "release-mingw32-" ARCH_STRING ".zip" ) - + #define RELEASE_SIGNATURE_NAME ( "release-mingw32-" ARCH_STRING ".zip.sig" ) + #define GRANGER_EXE ( "granger" EXE_EXT ) + #endif @@ -162,7 +181,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // For cl_updates.cpp #define RELEASE_PACKAGE_NAME ( "release-darwin-" ARCH_STRING ".zip" ) - + #define RELEASE_SIGNATURE_NAME ( "release-darwin-" ARCH_STRING ".zip.sig" ) + #define GRANGER_EXE ( "granger" EXE_EXT ) + #endif //================================================================= LINUX === @@ -194,6 +215,23 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # define Q3_LITTLE_ENDIAN #undef idx64 #define idx64 1 + #elif defined __arm__ + # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + # error "Big endian ARM is not supported" + # endif + # if defined __armhf__ + # define ARCH_STRING "armhf" + # define Q3_LITTLE_ENDIAN + # else + # define ARCH_STRING "armel" + # define Q3_LITTLE_ENDIAN + # endif + #elif defined __aarch64__ + # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + # error "Big endian ARM is not supported" + # endif + # define ARCH_STRING "aarch64" + # define Q3_LITTLE_ENDIAN #endif #define DLL_EXT ".so" @@ -201,6 +239,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // For cl_updates.cpp #define RELEASE_PACKAGE_NAME ( "release-linux-" ARCH_STRING ".zip" ) + #define RELEASE_SIGNATURE_NAME ( "release-linux-" ARCH_STRING ".zip.sig" ) + #define GRANGER_EXE ( "granger" EXE_EXT ) #endif @@ -336,51 +376,63 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA //endianness +void CopyShortSwap (void *dest, void *src); +void CopyLongSwap (void *dest, void *src); short ShortSwap (short l); int LongSwap (int l); float FloatSwap (const float *f); #if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN ) -#error "Endianness defined as both big and little" + #error "Endianness defined as both big and little" #elif defined( Q3_BIG_ENDIAN ) -#define LittleShort(x) ShortSwap(x) -#define LittleLong(x) LongSwap(x) -#define LittleFloat(x) FloatSwap(&x) -#define BigShort -#define BigLong -#define BigFloat + #define CopyLittleShort(dest, src) CopyShortSwap(dest, src) + #define CopyLittleLong(dest, src) CopyLongSwap(dest, src) + #define LittleShort(x) ShortSwap(x) + #define LittleLong(x) LongSwap(x) + #define LittleFloat(x) FloatSwap(&x) + #define BigShort + #define BigLong + #define BigFloat #elif defined( Q3_LITTLE_ENDIAN ) -#define LittleShort -#define LittleLong -#define LittleFloat -#define BigShort(x) ShortSwap(x) -#define BigLong(x) LongSwap(x) -#define BigFloat(x) FloatSwap(&x) + #define CopyLittleShort(dest, src) memcpy(dest, src, 2) + #define CopyLittleLong(dest, src) memcpy(dest, src, 4) + #define LittleShort + #define LittleLong + #define LittleFloat + #define BigShort(x) ShortSwap(x) + #define BigLong(x) LongSwap(x) + #define BigFloat(x) FloatSwap(&x) #elif defined( Q3_VM ) -#define LittleShort -#define LittleLong -#define LittleFloat -#define BigShort -#define BigLong -#define BigFloat + #define LittleShort + #define LittleLong + #define LittleFloat + #define BigShort + #define BigLong + #define BigFloat #else -#error "Endianness not defined" + + #error "Endianness not defined" + #endif //platform string #ifdef NDEBUG -#define PLATFORM_STRING OS_STRING "-" ARCH_STRING + #define PLATFORM_STRING OS_STRING "-" ARCH_STRING #else -#define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug" + #define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug" +#endif + #endif +#ifdef __cplusplus +} #endif #endif diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c index 2ac5537..938a7e5 100644 --- a/src/qcommon/q_shared.c +++ b/src/qcommon/q_shared.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // @@ -54,52 +55,79 @@ char *COM_SkipPath (char *pathname) return last; } +/* +============ +COM_GetExtension +============ +*/ +const char *COM_GetExtension( const char *name ) +{ + const char *dot = strrchr(name, '.'), *slash; + if (dot && (!(slash = strrchr(name, '/')) || slash < dot)) + return dot + 1; + else + return ""; +} + + /* ============ COM_StripExtension ============ */ -void COM_StripExtension( const char *in, char *out, int destsize ) { - int length; +void COM_StripExtension( const char *in, char *out, int destsize ) +{ + const char *dot = strrchr(in, '.'), *slash; - Q_strncpyz(out, in, destsize); + if (dot && (!(slash = strrchr(in, '/')) || slash < dot)) + destsize = (destsize < dot-in+1 ? destsize : dot-in+1); - length = strlen(out)-1; - while (length > 0 && out[length] != '.') + if ( in == out && destsize > 1 ) + out[destsize-1] = '\0'; + else + Q_strncpyz(out, in, destsize); +} + +/* +============ +COM_CompareExtension + +string compare the end of the strings and return qtrue if strings match +============ +*/ +qboolean COM_CompareExtension(const char *in, const char *ext) +{ + int inlen, extlen; + + inlen = strlen(in); + extlen = strlen(ext); + + if(extlen <= inlen) { - length--; - if (out[length] == '/') - return; // no extension + in += inlen - extlen; + + if(!Q_stricmp(in, ext)) + return qtrue; } - if (length) - out[length] = 0; + + return qfalse; } - /* ================== COM_DefaultExtension + +if path doesn't have an extension, then append + the specified one (which should include the .) ================== */ -void COM_DefaultExtension (char *path, int maxSize, const char *extension ) { - char oldPath[MAX_QPATH]; - char *src; - -// -// if path doesn't have a .EXT, append extension -// (extension should include the .) -// - src = path + strlen(path) - 1; - - while (*src != '/' && src != path) { - if ( *src == '.' ) { - return; // it has an extension - } - src--; - } - - Q_strncpyz( oldPath, path, sizeof( oldPath ) ); - Com_sprintf( path, maxSize, "%s%s", oldPath, extension ); +void COM_DefaultExtension( char *path, int maxSize, const char *extension ) +{ + const char *dot = strrchr(path, '.'), *slash; + if (dot && (!(slash = strrchr(path, '/')) || slash < dot)) + return; + else + Q_strcat(path, maxSize, extension); } /* @@ -131,6 +159,24 @@ float BigFloat (const float *l) {return _BigFloat(l);} float LittleFloat (const float *l) {return _LittleFloat(l);} */ +void CopyShortSwap(void *dest, void *src) +{ + byte *to = dest, *from = src; + + to[0] = from[1]; + to[1] = from[0]; +} + +void CopyLongSwap(void *dest, void *src) +{ + byte *to = dest, *from = src; + + to[0] = from[3]; + to[1] = from[2]; + to[2] = from[1]; + to[3] = from[0]; +} + short ShortSwap (short l) { byte b1,b2; @@ -158,47 +204,17 @@ int LongSwap (int l) return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; } -int LongNoSwap (int l) -{ - return l; -} - -qint64 Long64Swap (qint64 ll) -{ - qint64 result; - - result.b0 = ll.b7; - result.b1 = ll.b6; - result.b2 = ll.b5; - result.b3 = ll.b4; - result.b4 = ll.b3; - result.b5 = ll.b2; - result.b6 = ll.b1; - result.b7 = ll.b0; - - return result; -} - -qint64 Long64NoSwap (qint64 ll) +float FloatSwap(const float *f) { - return ll; -} - -typedef union { - float f; - unsigned int i; -} _FloatByteUnion; - -float FloatSwap (const float *f) { - _FloatByteUnion out; + floatint_t out; out.f = *f; - out.i = LongSwap(out.i); + out.ui = LongSwap(out.ui); return out.f; } -float FloatNoSwap (const float *f) +float FloatNoSwap(const float *f) { return *f; } @@ -251,15 +267,22 @@ PARSING static char com_token[MAX_TOKEN_CHARS]; static char com_parsename[MAX_TOKEN_CHARS]; static int com_lines; +static int com_tokenline; void COM_BeginParseSession( const char *name ) { - com_lines = 0; + com_lines = 1; + com_tokenline = 0; Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name); } int COM_GetCurrentParseLine( void ) { + if ( com_tokenline ) + { + return com_tokenline; + } + return com_lines; } @@ -274,10 +297,10 @@ void COM_ParseError( char *format, ... ) static char string[4096]; va_start (argptr, format); - vsprintf (string, format, argptr); + Q_vsnprintf (string, sizeof(string), format, argptr); va_end (argptr); - Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string); + Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, COM_GetCurrentParseLine(), string); } void COM_ParseWarning( char *format, ... ) @@ -286,10 +309,10 @@ void COM_ParseWarning( char *format, ... ) static char string[4096]; va_start (argptr, format); - vsprintf (string, format, argptr); + Q_vsnprintf (string, sizeof(string), format, argptr); va_end (argptr); - Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string); + Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, COM_GetCurrentParseLine(), string); } /* @@ -340,52 +363,53 @@ int COM_Compress( char *data_p ) { in++; if ( *in ) in += 2; - // record when we hit a newline - } else if ( c == '\n' || c == '\r' ) { - newline = qtrue; - in++; - // record when we hit whitespace - } else if ( c == ' ' || c == '\t') { - whitespace = qtrue; - in++; - // an actual token + // record when we hit a newline + } else if ( c == '\n' || c == '\r' ) { + newline = qtrue; + in++; + // record when we hit whitespace + } else if ( c == ' ' || c == '\t') { + whitespace = qtrue; + in++; + // an actual token } else { - // if we have a pending newline, emit it (and it counts as whitespace) - if (newline) { - *out++ = '\n'; - newline = qfalse; - whitespace = qfalse; - } if (whitespace) { - *out++ = ' '; - whitespace = qfalse; - } - - // copy quoted strings unmolested - if (c == '"') { - *out++ = c; - in++; - while (1) { - c = *in; - if (c && c != '"') { - *out++ = c; - in++; - } else { - break; - } - } - if (c == '"') { - *out++ = c; - in++; - } - } else { - *out = c; - out++; - in++; - } + // if we have a pending newline, emit it (and it counts as whitespace) + if (newline) { + *out++ = '\n'; + newline = qfalse; + whitespace = qfalse; + } if (whitespace) { + *out++ = ' '; + whitespace = qfalse; + } + + // copy quoted strings unmolested + if (c == '"') { + *out++ = c; + in++; + while (1) { + c = *in; + if (c && c != '"') { + *out++ = c; + in++; + } else { + break; + } + } + if (c == '"') { + *out++ = c; + in++; + } + } else { + *out = c; + out++; + in++; + } } } + + *out = 0; } - *out = 0; return out - data_p; } @@ -398,6 +422,7 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) data = *data_p; len = 0; com_token[0] = 0; + com_tokenline = 0; // make sure incoming data is valid if ( !data ) @@ -437,6 +462,10 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) data += 2; while ( *data && ( *data != '*' || data[1] != '/' ) ) { + if ( *data == '\n' ) + { + com_lines++; + } data++; } if ( *data ) @@ -450,6 +479,9 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) } } + // token starts on this line + com_tokenline = com_lines; + // handle quoted strings if (c == '\"') { @@ -463,6 +495,10 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) *data_p = ( char * ) data; return com_token; } + if ( c == '\n' ) + { + com_lines++; + } if (len < MAX_TOKEN_CHARS - 1) { com_token[len] = c; @@ -481,8 +517,6 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) } data++; c = *data; - if ( c == '\n' ) - com_lines++; } while (c>32); com_token[len] = 0; @@ -491,62 +525,6 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) return com_token; } - -#if 0 -// no longer used -/* -=============== -COM_ParseInfos -=============== -*/ -int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ) { - char *token; - int count; - char key[MAX_TOKEN_CHARS]; - - count = 0; - - while ( 1 ) { - token = COM_Parse( &buf ); - if ( !token[0] ) { - break; - } - if ( strcmp( token, "{" ) ) { - Com_Printf( "Missing { in info file\n" ); - break; - } - - if ( count == max ) { - Com_Printf( "Max infos exceeded\n" ); - break; - } - - infos[count][0] = 0; - while ( 1 ) { - token = COM_ParseExt( &buf, qtrue ); - if ( !token[0] ) { - Com_Printf( "Unexpected end of info file\n" ); - break; - } - if ( !strcmp( token, "}" ) ) { - break; - } - Q_strncpyz( key, token, sizeof( key ) ); - - token = COM_ParseExt( &buf, qfalse ); - if ( !token[0] ) { - strcpy( token, "" ); - } - Info_SetValueForKey( infos[count], key, token ); - } - count++; - } - - return count; -} -#endif - - /* ================== COM_MatchToken @@ -566,16 +544,14 @@ void COM_MatchToken( char **buf_p, char *match ) { ================= SkipBracedSection -The next token should be an open brace. +The next token should be an open brace or set depth to 1 if already parsed it. Skips until a matching close brace is found. Internal brace depths are properly skipped. ================= */ -void SkipBracedSection (char **program) { +qboolean SkipBracedSection (char **program, int depth) { char *token; - int depth; - depth = 0; do { token = COM_ParseExt( program, qtrue ); if( token[1] == 0 ) { @@ -587,6 +563,8 @@ void SkipBracedSection (char **program) { } } } while( depth && *program ); + + return ( depth == 0 ); } /* @@ -599,6 +577,10 @@ void SkipRestOfLine ( char **data ) { int c; p = *data; + + if ( !*p ) + return; + while ( (c = *p++) != 0 ) { if ( c == '\n' ) { com_lines++; @@ -648,6 +630,45 @@ void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m) { COM_MatchToken( buf_p, ")" ); } +/* +=================== +Com_HexStrToInt +=================== +*/ +int Com_HexStrToInt( const char *str ) +{ + if ( !str || !str[ 0 ] ) + return -1; + + // check for hex code + if( str[ 0 ] == '0' && str[ 1 ] == 'x' ) + { + size_t i; + int n = 0; + + for( i = 2; i < strlen( str ); i++ ) + { + char digit; + + n *= 16; + + digit = tolower( str[ i ] ); + + if( digit >= '0' && digit <= '9' ) + digit -= '0'; + else if( digit >= 'a' && digit <= 'f' ) + digit = digit - 'a' + 10; + else + return -1; + + n += digit; + } + + return n; + } + + return -1; +} /* ============================================================================ @@ -685,32 +706,56 @@ int Q_isalpha( int c ) return ( 0 ); } -int Q_isdigit( int c ) +qboolean Q_isanumber( const char *s ) { - if ((c >= '0' && c <= '9')) - return ( 1 ); - return ( 0 ); + char *p; + double UNUSED_VAR d; + + if( *s == '\0' ) + return qfalse; + + d = strtod( s, &p ); + + return *p == '\0'; } -char* Q_strrchr( const char* string, int c ) +qboolean Q_isintegral( float f ) { - char cc = c; - char *s; - char *sp=(char *)0; + return (int)f == f; +} - s = (char*)string; +#ifdef _MSC_VER +/* +============= +Q_vsnprintf + +Special wrapper function for Microsoft's broken _vsnprintf() function. +MinGW comes with its own snprintf() which is not broken. +============= +*/ + +int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + int retval; + + retval = _vsnprintf(str, size, format, ap); - while (*s) + if(retval < 0 || retval == size) { - if (*s == cc) - sp = s; - s++; + // Microsoft doesn't adhere to the C99 standard of vsnprintf, + // which states that the return value must be the number of + // bytes written if the output string had sufficient length. + // + // Obviously we cannot determine that value from Microsoft's + // implementation, so we have no choice but to return size. + + str[size - 1] = '\0'; + return size; } - if (cc == 0) - sp = s; - - return sp; + + return retval; } +#endif /* ============= @@ -720,7 +765,6 @@ Safe strncpy that ensures a trailing zero ============= */ void Q_strncpyz( char *dest, const char *src, int destsize ) { - // bk001129 - also NULL dest if ( !dest ) { Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" ); } @@ -738,7 +782,6 @@ void Q_strncpyz( char *dest, const char *src, int destsize ) { int Q_stricmpn (const char *s1, const char *s2, int n) { int c1, c2; - // bk001129 - moved in 1.17 fix not in id codebase if ( s1 == NULL ) { if ( s2 == NULL ) return 0; @@ -832,6 +875,38 @@ void Q_strcat( char *dest, int size, const char *src ) { Q_strncpyz( dest + l1, src, size - l1 ); } +/* +* Find the first occurrence of find in s. +*/ +const char *Q_stristr( const char *s, const char *find) +{ + char c, sc; + size_t len; + + if ((c = *find++) != 0) + { + if (c >= 'a' && c <= 'z') + { + c -= ('a' - 'A'); + } + len = strlen(find); + do + { + do + { + if ((sc = *s++) == 0) + return NULL; + if (sc >= 'a' && sc <= 'z') + { + sc -= ('a' - 'A'); + } + } while (sc != c); + } while (Q_stricmpn(s, find, len) != 0); + s--; + } + return s; +} + int Q_PrintStrlen( const char *string ) { int len; @@ -877,29 +952,52 @@ char *Q_CleanStr( char *string ) { return string; } +int Q_CountChar(const char *string, char tocount) +{ + int count; + + for(count = 0; *string; string++) + { + if(*string == tocount) + count++; + } + + return count; +} -void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) { +void Q_StripIndentMarker(char *string) +{ + int i, j; + + for (i = j = 0; string[i]; i++) { + if (string[i] != INDENT_MARKER) { + string[j++] = string[i]; + } + } + string[j] = 0; +} + +void Q_ParseNewlines( char *dest, const char *src, int destsize ) +{ + for( ; *src && destsize > 1; src++, destsize-- ) + *dest++ = ( ( *src == '\\' && *( ++src ) == 'n' ) ? '\n' : *src ); + *dest++ = '\0'; +} + +int QDECL Com_sprintf(char *dest, int size, const char *fmt, ...) +{ int len; va_list argptr; - char bigbuffer[32000]; // big, but small enough to fit in PPC stack va_start (argptr,fmt); - len = vsprintf (bigbuffer,fmt,argptr); + len = Q_vsnprintf(dest, size, fmt, argptr); va_end (argptr); - if ( len >= sizeof( bigbuffer ) ) { - Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" ); - } - if (len >= size) { - Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); -#ifdef _DEBUG - __asm { - int 3; - } -#endif - } - Q_strncpyz (dest, bigbuffer, size ); -} + if(len >= size) + Com_Printf("Com_sprintf: Output length %d too short, require %d bytes.\n", size, len + 1); + + return len; +} /* ============ @@ -907,20 +1005,19 @@ va does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functions. -FIXME: make this buffer size safe someday ============ */ -char * QDECL va( char *format, ... ) { +const char * QDECL va(const char *format, ... ) { va_list argptr; - static char string[2][32000]; // in case va is called by nested functions - static int index = 0; - char *buf; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; buf = string[index & 1]; index++; va_start (argptr, format); - vsprintf (buf, format,argptr); + Q_vsnprintf (buf, sizeof(*string), format, argptr); va_end (argptr); return buf; @@ -1100,7 +1197,8 @@ void Info_RemoveKey( char *s, const char *key ) { if (!strcmp (key, pkey) ) { - strcpy (start, s); // remove this part + memmove(start, s, strlen(s) + 1); // remove this part + return; } @@ -1155,7 +1253,7 @@ void Info_RemoveKey_Big( char *s, const char *key ) { if (!strcmp (key, pkey) ) { - strcpy (start, s); // remove this part + memmove(start, s, strlen(s) + 1); // remove this part return; } @@ -1241,6 +1339,7 @@ void Info_SetValueForKey( char *s, const char *key, const char *value ) { Info_SetValueForKey_Big Changes or adds a key/value pair +Includes and retains zero-length values ================== */ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { @@ -1261,14 +1360,15 @@ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { } Info_RemoveKey_Big (s, key); - if (!value || !strlen(value)) + if (!value) return; Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); if (strlen(newi) + strlen(s) >= BIG_INFO_STRING) { - Com_Printf ("BIG Info string length exceeded\n"); + Com_Printf ("BIG Info string length exceeded: setting %s to %s " + "failed\n", key, value); return; } @@ -1285,9 +1385,9 @@ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { Com_CharIsOneOfCharset ================== */ -static qboolean Com_CharIsOneOfCharset( char c, char *set ) +static qboolean Com_CharIsOneOfCharset( char c, const char *set ) { - int i; + size_t i; for( i = 0; i < strlen( set ); i++ ) { @@ -1303,7 +1403,7 @@ static qboolean Com_CharIsOneOfCharset( char c, char *set ) Com_SkipCharset ================== */ -char *Com_SkipCharset( char *s, char *sep ) +char *Com_SkipCharset( char *s, const char *sep ) { char *p = s; @@ -1323,7 +1423,7 @@ char *Com_SkipCharset( char *s, char *sep ) Com_SkipTokens ================== */ -char *Com_SkipTokens( char *s, int numTokens, char *sep ) +char *Com_SkipTokens( char *s, int numTokens, const char *sep ) { int sepCount = 0; char *p = s; @@ -1345,3 +1445,85 @@ char *Com_SkipTokens( char *s, int numTokens, char *sep ) else return s; } + +/* +============ +Com_ClientListContains +============ +*/ +qboolean Com_ClientListContains( const clientList_t *list, int clientNum ) +{ + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) + return qfalse; + if( clientNum < 32 ) + return ( ( list->lo & ( 1 << clientNum ) ) != 0 ); + else + return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 ); +} + +/* +============ +Com_ClientListAdd +============ +*/ +void Com_ClientListAdd( clientList_t *list, int clientNum ) +{ + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) + return; + if( clientNum < 32 ) + list->lo |= ( 1 << clientNum ); + else + list->hi |= ( 1 << ( clientNum - 32 ) ); +} + +/* +============ +Com_ClientListRemove +============ +*/ +void Com_ClientListRemove( clientList_t *list, int clientNum ) +{ + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) + return; + if( clientNum < 32 ) + list->lo &= ~( 1 << clientNum ); + else + list->hi &= ~( 1 << ( clientNum - 32 ) ); +} + +/* +============ +Com_ClientListString +============ +*/ +char *Com_ClientListString( const clientList_t *list ) +{ + static char s[ 17 ]; + + s[ 0 ] = '\0'; + if( !list ) + return s; + Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo ); + return s; +} + +/* +============ +Com_ClientListParse +============ +*/ +void Com_ClientListParse( clientList_t *list, const char *s ) +{ + char t[ 9 ]; + if( !list ) + return; + list->lo = 0; + list->hi = 0; + if( !s ) + return; + if( strlen( s ) != 16 ) + return; + Q_strncpyz( t, s, 9 ); + sscanf( t, "%x", &list->hi ); + sscanf( s + 8, "%x", &list->lo ); +} diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h index 06f1bb2..a854de8 100644 --- a/src/qcommon/q_shared.h +++ b/src/qcommon/q_shared.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,28 +17,42 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // #ifndef __Q_SHARED_H #define __Q_SHARED_H +#ifdef __cplusplus +extern "C" { +#endif + // q_shared.h -- included first by ALL program modules. // A user mod should never modify this file -#define VERSION_NUMBER "1.1.0" -#define Q3_VERSION "tremulous " VERSION_NUMBER -#ifndef SVN_VERSION -#define SVN_VERSION Q3_VERSION +#define PRODUCT_NAME "tremulous" + +#ifndef PRODUCT_VERSION +# define PRODUCT_VERSION "1.3.0 alpha" #endif -#define CLIENT_WINDOW_TITLE "Tremulous " VERSION_NUMBER -#define CLIENT_WINDOW_ICON "Tremulous" -#define CONSOLE_WINDOW_TITLE "Tremulous " VERSION_NUMBER " console" -#define CONSOLE_WINDOW_ICON "Tremulous console" -#define MAX_TEAMNAME 32 +#define CLIENT_WINDOW_TITLE "Tremulous " PRODUCT_VERSION +#define CLIENT_WINDOW_MIN_TITLE "Tremulous" +#define Q3_VERSION PRODUCT_NAME " " PRODUCT_VERSION + +#define GAMENAME_FOR_MASTER "Tremulous" +#define HOMEPATH_NAME_UNIX ".tremulous" +#define HOMEPATH_NAME_WIN "Tremulous" +#define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN + +// Heartbeat for dpmaster protocol. You shouldn't change this unless you know what you're doing +#define HEARTBEAT_FOR_MASTER GAMENAME_FOR_MASTER + +#define MAX_MASTER_SERVERS 5 // number of supported master servers + +#define DEMOEXT "dm_" // standard demo extension #ifdef _MSC_VER @@ -72,6 +87,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif #endif +#ifdef __GNUC__ +#define UNUSED_VAR __attribute__((unused)) +#else +#define UNUSED_VAR +#endif + #if (defined _MSC_VER) #define Q_EXPORT __declspec(dllexport) #elif (defined __SUNPRO_C) @@ -100,7 +121,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #ifdef Q3_VM -#include "../game/bg_lib.h" +#include "game/bg_lib.h" + +typedef int intptr_t; #else @@ -114,60 +137,136 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include +#ifdef _MSC_VER + #include + + typedef __int64 int64_t; + typedef __int32 int32_t; + typedef __int16 int16_t; + typedef __int8 int8_t; + typedef unsigned __int64 uint64_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int8 uint8_t; + + // vsnprintf is ISO/IEC 9899:1999 + // abstracting this to make it portable + int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap); +#else + #include + + #define Q_vsnprintf vsnprintf + #define Q_snprintf snprintf #endif -#include "q_platform.h" +#endif -//============================================================= -#ifdef Q3_VM - typedef int intptr_t; -#else - #ifndef _MSC_VER - #include - #else - #include - typedef __int64 int64_t; - typedef __int32 int32_t; - typedef __int16 int16_t; - typedef __int8 int8_t; - typedef unsigned __int64 uint64_t; - typedef unsigned __int32 uint32_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int8 uint8_t; - #endif -#endif +#include "qcommon/q_platform.h" + +//============================================================= typedef unsigned char byte; -typedef enum {qfalse, qtrue} qboolean; +typedef enum {qfalse, qtrue} qboolean; + +typedef union { + float f; + int i; + unsigned int ui; +} floatint_t; typedef int qhandle_t; typedef int sfxHandle_t; typedef int fileHandle_t; typedef int clipHandle_t; -#define PAD(x,y) (((x)+(y)-1) & ~((y)-1)) +#define PAD(base, alignment) (((base)+(alignment)-1) & ~((alignment)-1)) +#define PADLEN(base, alignment) (PAD((base), (alignment)) - (base)) + +#define PADP(base, alignment) ((void *) PAD((intptr_t) (base), (alignment))) #ifdef __GNUC__ -#define ALIGN(x) __attribute__((aligned(x))) +#define QALIGN(x) __attribute__((aligned(x))) #else -#define ALIGN(x) +#define QALIGN(x) #endif #ifndef NULL #define NULL ((void *)0) #endif +#define STRING(s) #s +// expand constants before stringifying them +#define XSTRING(s) STRING(s) + #define MAX_QINT 0x7fffffff #define MIN_QINT (-MAX_QINT-1) +#define ARRAY_LEN(x) (sizeof(x) / sizeof(*(x))) +#define STRARRAY_LEN(x) (ARRAY_LEN(x) - 1) // angle indexes #define PITCH 0 // up / down #define YAW 1 // left / right #define ROLL 2 // fall over +/* FILESYSTEM */ +enum FS_Mode { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +}; + +enum FS_Origin { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +}; + +/* CVAR */ + +#define CVAR_ARCHIVE 0x0001 // set to cause it to be saved to vars.rc +// used for system variables, not for player +// specific configurations +#define CVAR_USERINFO 0x0002 // sent to server on connect or change +#define CVAR_SERVERINFO 0x0004 // sent in response to front end requests +#define CVAR_SYSTEMINFO 0x0008 // these cvars will be duplicated on all clients +#define CVAR_INIT 0x0010 // don't allow change from console at all, +// but can be set from the command line +#define CVAR_LATCH 0x0020 // will only change when C code next does +// a Cvar_Get(), so it can't be changed without proper initialization. +// modified will be set, even though the value hasn't changed yet +#define CVAR_ROM 0x0040 // display only, cannot be set by user at all +#define CVAR_USER_CREATED 0x0080 // created by a set command +#define CVAR_TEMP 0x0100 // can be set even when cheats are disabled, but is not archived +#define CVAR_CHEAT 0x0200 // can not be changed if cheats are disabled +#define CVAR_NORESTART 0x0400 // do not clear when a cvar_restart is issued + +#define CVAR_SERVER_CREATED 0x0800 // cvar was created by a server the client connected to. +#define CVAR_VM_CREATED 0x1000 // cvar was created exclusively in one of the VMs. +#define CVAR_PROTECTED 0x2000 // prevent modifying this var from VMs or the server +#define CVAR_ALTERNATE_SYSTEMINFO 0x1000000 +// These flags are only returned by the Cvar_Flags() function +#define CVAR_MODIFIED 0x40000000 // Cvar was modified +#define CVAR_NONEXISTENT 0x80000000 // Cvar doesn't exist. + +#define MAX_CVAR_VALUE_STRING 256 + +typedef int cvarHandle_t; + +// the modules that run in the virtual machine can't access the cvar_t directly, +// so they must ask for structured updates +typedef struct { + cvarHandle_t handle; + int modificationCount; + float value; + int integer; + char string[MAX_CVAR_VALUE_STRING]; +} vmCvar_t; + + // the game guarantees that no string from the network will ever // exceed MAX_STRING_CHARS #define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString @@ -182,6 +281,7 @@ typedef int clipHandle_t; #define BIG_INFO_KEY 8192 #define BIG_INFO_VALUE 8192 +#define MAX_NEWS_STRING 10000 #define MAX_QPATH 64 // max length of a quake game pathname #ifdef PATH_MAX @@ -193,7 +293,7 @@ typedef int clipHandle_t; #define MAX_NAME_LENGTH 32 // max length of a client name #define MAX_HOSTNAME_LENGTH 80 // max length of a host name -#define MAX_SAY_TEXT 150 +#define MAX_SAY_TEXT 800 // paramters for command buffer stuffing typedef enum { @@ -203,7 +303,6 @@ typedef enum { EXEC_APPEND // add to end of the command buffer (normal case) } cbufExec_t; - // // these aren't needed by any of the VMs. put in another header? // @@ -225,37 +324,20 @@ typedef enum { // parameters to the main Error routine typedef enum { - ERR_FATAL, // exit the entire game with a popup window - ERR_DROP, // print to console and disconnect from game - ERR_SERVERDISCONNECT, // don't kill server - ERR_DISCONNECT, // client disconnected from the server - ERR_NEED_CD // pop up the need-cd dialog + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_SERVERDISCONNECT, // don't kill server + ERR_DISCONNECT, // client disconnected from the server + ERR_RECONNECT } errorParm_t; // font rendering values used by ui and cgame - -#define PROP_GAP_WIDTH 3 -#define PROP_SPACE_WIDTH 8 -#define PROP_HEIGHT 27 -#define PROP_SMALL_SIZE_SCALE 0.75 - +// #define BLINK_DIVISOR 200 #define PULSE_DIVISOR 75 -#define UI_LEFT 0x00000000 // default -#define UI_CENTER 0x00000001 -#define UI_RIGHT 0x00000002 -#define UI_FORMATMASK 0x00000007 -#define UI_SMALLFONT 0x00000010 -#define UI_BIGFONT 0x00000020 // default -#define UI_GIANTFONT 0x00000040 -#define UI_DROPSHADOW 0x00000800 -#define UI_BLINK 0x00001000 -#define UI_INVERSE 0x00002000 -#define UI_PULSE 0x00004000 - -#if defined(_DEBUG) && !defined(BSPC) +#if !defined(NDEBUG) && !defined(BSPC) #define HUNK_DEBUG #endif @@ -267,19 +349,11 @@ typedef enum { #ifdef HUNK_DEBUG #define Hunk_Alloc( size, preference ) Hunk_AllocDebug(size, preference, #size, __FILE__, __LINE__) -void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ); +void *Hunk_AllocDebug( int size, ha_pref preference, const char *label, const char *file, int line ); #else void *Hunk_Alloc( int size, ha_pref preference ); #endif -#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__APPLE__) -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 -// custom Snd_Memset implementation for glibc memset bug workaround -void Snd_Memset (void* dest, const int val, const size_t count); -#else -#define Snd_Memset Com_Memset -#endif - #define Com_Memset memset #define Com_Memcpy memcpy @@ -304,10 +378,6 @@ typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; typedef vec_t vec5_t[5]; -typedef int fixed4_t; -typedef int fixed8_t; -typedef int fixed16_t; - #ifndef M_PI #define M_PI 3.14159265358979323846f // matches value in gcc v2 math.h #endif @@ -348,192 +418,37 @@ extern vec4_t colorYellow; extern vec4_t colorMagenta; extern vec4_t colorCyan; extern vec4_t colorWhite; -extern vec4_t colorGray; -extern vec4_t colorOrange; -extern vec4_t colorRoseBud; -extern vec4_t colorPaleGreen; -extern vec4_t colorPaleGolden; -extern vec4_t colorColumbiaBlue; -extern vec4_t colorPaleTurquoise; -extern vec4_t colorPaleVioletRed; -extern vec4_t colorPalacePaleWhite; -extern vec4_t colorOlive; -extern vec4_t colorTomato; -extern vec4_t colorLime; -extern vec4_t colorLemon; -extern vec4_t colorBlueBerry; -extern vec4_t colorTurquoise; -extern vec4_t colorWildWatermelon; -extern vec4_t colorSaltpan; -extern vec4_t colorGrayChateau; -extern vec4_t colorRust; -extern vec4_t colorCopperGreen; -extern vec4_t colorGold; -extern vec4_t colorSteelBlue; -extern vec4_t colorSteelGray; -extern vec4_t colorBronze; -extern vec4_t colorSilver; -extern vec4_t colorDarkGray; -extern vec4_t colorDarkOrange; -extern vec4_t colorDarkGreen; -extern vec4_t colorRedOrange; -extern vec4_t colorForestGreen; -extern vec4_t colorBrightSun; -extern vec4_t colorMediumSlateBlue; -extern vec4_t colorCeleste; -extern vec4_t colorIronstone; -extern vec4_t colorTimberwolf; -extern vec4_t colorOnyx; -extern vec4_t colorRosewood; -extern vec4_t colorKokoda; -extern vec4_t colorPorsche; -extern vec4_t colorCloudBurst; -extern vec4_t colorBlueDiane; -extern vec4_t colorRope; -extern vec4_t colorBlonde; -extern vec4_t colorSmokeyBlack; -extern vec4_t colorAmericanRose; -extern vec4_t colorNeonGreen; -extern vec4_t colorNeonYellow; -extern vec4_t colorUltramarine; -extern vec4_t colorTurquoiseBlue; -extern vec4_t colorDarkMagenta; -extern vec4_t colorMagicMint; -extern vec4_t colorLightGray; -extern vec4_t colorLightSalmon; -extern vec4_t colorLightGreen; +extern vec4_t colorLtGrey; +extern vec4_t colorMdGrey; +extern vec4_t colorDkGrey; #define Q_COLOR_ESCAPE '^' -#define Q_IsColorString(p) ( p && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE ) +#define Q_IsColorString(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && isalnum(*((p)+1))) // ^[0-9a-zA-Z] -#define COLOR_BLACK '0' -#define COLOR_RED '1' -#define COLOR_GREEN '2' +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' #define COLOR_YELLOW '3' -#define COLOR_BLUE '4' -#define COLOR_CYAN '5' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' #define COLOR_MAGENTA '6' -#define COLOR_WHITE '7' -#define COLOR_GRAY '8' -#define COLOR_ORANGE '9' -#define COLOR_ROSE_BUD 'a' -#define COLOR_PALE_GREEN 'b' -#define COLOR_PALE_GOLDEN 'c' -#define COLOR_COLUMBIA_BLUE 'd' -#define COLOR_PALE_TURQUOISE 'e' -#define COLOR_PALE_VIOLET_RED 'f' -#define COLOR_PALACE_PALE_WHITE 'g' -#define COLOR_OLIVE 'h' -#define COLOR_TOMATO 'i' -#define COLOR_LIME 'j' -#define COLOR_LEMON 'k' -#define COLOR_BLUE_BERRY 'l' -#define COLOR_TURQUOISE 'm' -#define COLOR_WILD_WATERMELON 'n' -#define COLOR_SALTPAN 'o' -#define COLOR_GRAY_CHATEAU 'p' -#define COLOR_RUST 'q' -#define COLOR_COPPER_GREEN 'r' -#define COLOR_GOLD 's' -#define COLOR_STEEL_BLUE 't' -#define COLOR_STEEL_GRAY 'u' -#define COLOR_BRONZE 'v' -#define COLOR_SILVER 'w' -#define COLOR_DARK_GRAY 'x' -#define COLOR_DARK_ORANGE 'y' -#define COLOR_DARK_GREEN 'z' -#define COLOR_RED_ORANGE 'A' -#define COLOR_FOREST_GREEN 'B' -#define COLOR_BRIGHT_SUN 'C' -#define COLOR_MEDIUM_SLATE_BLUE 'D' -#define COLOR_CELESTE 'E' -#define COLOR_IRONSTONE 'F' -#define COLOR_TIMBERWOLF 'G' -#define COLOR_ONYX 'H' -#define COLOR_ROSEWOOD 'I' -#define COLOR_KOKODA 'J' -#define COLOR_PORSCHE 'K' -#define COLOR_CLOUD_BURST 'L' -#define COLOR_BLUE_DIANE 'M' -#define COLOR_ROPE 'N' -#define COLOR_BLONDE 'O' -#define COLOR_SMOKEY_BLACK 'P' -#define COLOR_AMERICAN_ROSE 'Q' -#define COLOR_NEON_GREEN 'R' -#define COLOR_NEON_YELLOW 'S' -#define COLOR_ULTRAMARINE 'T' -#define COLOR_TURQUOISE_BLUE 'U' -#define COLOR_DARK_MAGENTA 'V' -#define COLOR_MAGIC_MINT 'W' -#define COLOR_LIGHT_GRAY 'X' -#define COLOR_LIGHT_SALMON 'Y' -#define COLOR_LIGHT_GREEN 'Z' -#define ColorIndex(c) (((((c) >= '0') && ((c) <= '9')) ? ((c) - '0') : ((((c) >= 'a') && ((c) <= 'z')) ? ((c) - 'a' + 10) : ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 36) : 7)))) - -#define S_COLOR_BLACK "^0" -#define S_COLOR_RED "^1" -#define S_COLOR_GREEN "^2" -#define S_COLOR_YELLOW "^3" -#define S_COLOR_BLUE "^4" -#define S_COLOR_CYAN "^5" -#define S_COLOR_MAGENTA "^6" -#define S_COLOR_WHITE "^7" -#define S_COLOR_GRAY '^8' -#define S_COLOR_ORANGE '^9' -#define S_COLOR_ROSE_BUD '^a' -#define S_COLOR_PALE_GREEN '^b' -#define S_COLOR_PALE_GOLDEN '^c' -#define S_COLOR_COLUMBIA_BLUE '^d' -#define S_COLOR_PALE_TURQUOISE '^e' -#define S_COLOR_PALE_VIOLET_RED '^f' -#define S_COLOR_PALACE_PALE_WHITE '^g' -#define S_COLOR_OLIVE '^h' -#define S_COLOR_TOMATO '^i' -#define S_COLOR_LIME '^j' -#define S_COLOR_LEMON '^k' -#define S_COLOR_BLUE_BERRY '^l' -#define S_COLOR_TURQUOISE '^m' -#define S_COLOR_WILD_WATERMELON '^n' -#define S_COLOR_SALTPAN '^o' -#define S_COLOR_GRAY_CHATEAU '^p' -#define S_COLOR_RUST '^q' -#define S_COLOR_COPPER_GREEN '^r' -#define S_COLOR_GOLD '^s' -#define S_COLOR_STEEL_BLUE '^t' -#define S_COLOR_STEEL_GRAY '^u' -#define S_COLOR_BRONZE '^v' -#define S_COLOR_SILVER '^w' -#define S_COLOR_DARK_GRAY '^x' -#define S_COLOR_DARK_ORANGE '^y' -#define S_COLOR_DARK_GREEN '^z' -#define S_COLOR_RED_ORANGE '^A' -#define S_COLOR_FOREST_GREEN '^B' -#define S_COLOR_BRIGHT_SUN '^C' -#define S_COLOR_MEDIUM_SLATE_BLUE '^D' -#define S_COLOR_CELESTE '^E' -#define S_COLOR_IRONSTONE '^F' -#define S_COLOR_TIMBERWOLF '^G' -#define S_COLOR_ONYX '^H' -#define S_COLOR_ROSEWOOD '^I' -#define S_COLOR_KOKODA '^J' -#define S_COLOR_PORSCHE '^K' -#define S_COLOR_CLOUD_BURST '^L' -#define S_COLOR_BLUE_DIANE '^M' -#define S_COLOR_ROPE '^N' -#define S_COLOR_BLONDE '^O' -#define S_COLOR_SMOKEY_BLACK '^P' -#define S_COLOR_AMERICAN_ROSE '^Q' -#define S_COLOR_NEON_GREEN '^R' -#define S_COLOR_NEON_YELLOW '^S' -#define S_COLOR_ULTRAMARINE '^T' -#define S_COLOR_TURQUOISE_BLUE '^U' -#define S_COLOR_DARK_MAGENTA '^V' -#define S_COLOR_MAGIC_MINT '^W' -#define S_COLOR_LIGHT_GRAY '^X' -#define S_COLOR_LIGHT_SALMON '^Y' -#define S_COLOR_LIGHT_GREEN '^Z' - -extern vec4_t g_color_table[62]; +#define COLOR_WHITE '7' +#define ColorIndexForNumber(c) ((c) & 0x07) +#define ColorIndex(c) (ColorIndexForNumber((c) - '0')) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +#define INDENT_MARKER '\v' +void Q_StripIndentMarker(char *string); + +extern vec4_t g_color_table[8]; #define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b #define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a @@ -550,23 +465,57 @@ extern vec3_t axisDefault[3]; #define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) -#if idppc +int Q_isnan(float x); + +#if idx64 + extern long qftolsse(float f); + extern int qvmftolsse(void); + extern void qsnapvectorsse(vec3_t vec); + + #define Q_ftol qftolsse + #define Q_SnapVector qsnapvectorsse + +#elif id386 + extern long QDECL qftolx87(float f); + extern long QDECL qftolsse(float f); + extern int QDECL qvmftolx87(void); + extern int QDECL qvmftolsse(void); + extern void QDECL qsnapvectorx87(vec3_t vec); + extern void QDECL qsnapvectorsse(vec3_t vec); + extern long (QDECL *Q_ftol)(float f); + extern void (QDECL *Q_SnapVector)(vec3_t vec); +#else + // Q_ftol must expand to a function name so the pluggable renderer can take + // its address + #define Q_ftol lrintf + #define Q_SnapVector(vec)\ + do\ + {\ + vec3_t *temp = (vec3_t*)(vec);\ + \ + (*temp)[0] = round((*temp)[0]);\ + (*temp)[1] = round((*temp)[1]);\ + (*temp)[2] = round((*temp)[2]);\ + } while(0) +#endif + +#if idppc static ID_INLINE float Q_rsqrt( float number ) { - float x = 0.5f * number; - float y; -#ifdef __GNUC__ - asm("frsqrte %0,%1" : "=f" (y) : "f" (number)); + float x = 0.5f * number; + float y; +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y) : "f" (number)); #else - y = __frsqrte( number ); + y = __frsqrte( number ); #endif - return y * (1.5f - (x * y * y)); - } + return y * (1.5f - (x * y * y)); +} -#ifdef __GNUC__ +#ifdef __GNUC__ static ID_INLINE float Q_fabs(float x) { float abs_x; - + asm("fabs %0,%1" : "=f" (abs_x) : "f" (x)); return abs_x; } @@ -588,28 +537,15 @@ signed short ClampShort( int i ); int DirToByte( vec3_t dir ); void ByteToDir( int b, vec3_t dir ); -#if 1 - #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) #define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) #define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) #define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) #define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) #define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) -#define VectorLerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ +#define VectorLerp2( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\ - (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2])) - -#else - -#define DotProduct(x,y) _DotProduct(x,y) -#define VectorSubtract(a,b,c) _VectorSubtract(a,b,c) -#define VectorAdd(a,b,c) _VectorAdd(a,b,c) -#define VectorCopy(a,b) _VectorCopy(a,b) -#define VectorScale(v, s, o) _VectorScale(v,s,o) -#define VectorMA(v, s, b, o) _VectorMA(v,s,b,o) - -#endif + (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2])) #ifdef Q3_VM #ifdef VectorCopy @@ -628,9 +564,18 @@ typedef struct { #define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z)) #define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) #define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define Vector4Lerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ + (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\ + (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2]),\ + (r)[3]=(s)[3]+(f)*((e)[3]-(s)[3])) + +#define SnapVector(v) ( (v)[0] = (int)(v)[0],\ + (v)[1] = (int)(v)[1],\ + (v)[2] = (int)(v)[2] ) + +#define Byte4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) -#define SnapVector(v) {v[0]=((int)(v[0]));v[1]=((int)(v[1]));v[2]=((int)(v[2]));} -// just in case you do't want to use the macros +// just in case you don't want to use the macros vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ); void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ); void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ); @@ -651,12 +596,11 @@ void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) { if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) { return 0; - } + } return 1; } -static ID_INLINE int VectorCompareEpsilon( - const vec3_t v1, const vec3_t v2, float epsilon ) +static ID_INLINE int VectorCompareEpsilon( const vec3_t v1, const vec3_t v2, float epsilon ) { vec3_t d; @@ -728,7 +672,7 @@ vec_t VectorLengthSquared( const vec3_t v ); vec_t Distance( const vec3_t p1, const vec3_t p2 ); vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ); - + void VectorNormalizeFast( vec3_t v ); void VectorInverse( vec3_t v ); @@ -762,6 +706,13 @@ void AxisCopy( vec3_t in[3], vec3_t out[3] ); void SetPlaneSignbits( struct cplane_s *out ); int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs, + const vec3_t mins2, const vec3_t maxs2); +qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs, + const vec3_t origin, vec_t radius); +qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs, + const vec3_t origin); + float AngleMod(float a); float LerpAngle (float from, float to, float frac); float AngleSubtract( float a1, float a2 ); @@ -784,7 +735,6 @@ void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]); void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out ); void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); void PerpendicularVector( vec3_t dst, const vec3_t src ); -int Q_isnan( float x ); void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up ); @@ -818,7 +768,9 @@ vec_t DistanceBetweenLineSegments( float Com_Clamp( float min, float max, float value ); char *COM_SkipPath( char *pathname ); +const char *COM_GetExtension( const char *name ); void COM_StripExtension(const char *in, char *out, int destsize); +qboolean COM_CompareExtension(const char *in, const char *ext); void COM_DefaultExtension( char *path, int maxSize, const char *extension ); void COM_BeginParseSession( const char *name ); @@ -828,7 +780,6 @@ char *COM_ParseExt( char **data_p, qboolean allowLineBreak ); int COM_Compress( char *data_p ); void COM_ParseError( char *format, ... ) __attribute__ ((format (printf, 1, 2))); void COM_ParseWarning( char *format, ... ) __attribute__ ((format (printf, 1, 2))); -//int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ); #define MAX_TOKENLENGTH 1024 @@ -854,33 +805,32 @@ typedef struct pc_token_s void COM_MatchToken( char**buf_p, char *match ); -void SkipBracedSection (char **program); +qboolean SkipBracedSection (char **program, int depth); void SkipRestOfLine ( char **data ); void Parse1DMatrix (char **buf_p, int x, float *m); void Parse2DMatrix (char **buf_p, int y, int x, float *m); void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m); +int Com_HexStrToInt( const char *str ); -void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); +int QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); -char *Com_SkipTokens( char *s, int numTokens, char *sep ); -char *Com_SkipCharset( char *s, char *sep ); +char *Com_SkipTokens( char *s, int numTokens, const char *sep ); +char *Com_SkipCharset( char *s, const char *sep ); void Com_RandomBytes( byte *string, int len ); -// mode parm for FS_FOpenFile -typedef enum { - FS_READ, - FS_WRITE, - FS_APPEND, - FS_APPEND_SYNC -} fsMode_t; +typedef struct +{ + unsigned int hi; + unsigned int lo; +} clientList_t; -typedef enum { - FS_SEEK_CUR, - FS_SEEK_END, - FS_SEEK_SET -} fsOrigin_t; +qboolean Com_ClientListContains( const clientList_t *list, int clientNum ); +void Com_ClientListAdd( clientList_t *list, int clientNum ); +void Com_ClientListRemove( clientList_t *list, int clientNum ); +char *Com_ClientListString( const clientList_t *list ); +void Com_ClientListParse( clientList_t *list, const char *s ); //============================================= @@ -888,7 +838,8 @@ int Q_isprint( int c ); int Q_islower( int c ); int Q_isupper( int c ); int Q_isalpha( int c ); -int Q_isdigit( int c ); +qboolean Q_isanumber( const char *s ); +qboolean Q_isintegral( float f ); // portable case insensitive compare int Q_stricmp (const char *s1, const char *s2); @@ -896,7 +847,7 @@ int Q_strncmp (const char *s1, const char *s2, int n); int Q_stricmpn (const char *s1, const char *s2, int n); char *Q_strlwr( char *s1 ); char *Q_strupr( char *s1 ); -char *Q_strrchr( const char* string, int c ); +const char *Q_stristr( const char *s, const char *find); // buffer size safe library replacements void Q_strncpyz( char *dest, const char *src, int destsize ); @@ -906,37 +857,17 @@ void Q_strcat( char *dest, int size, const char *src ); int Q_PrintStrlen( const char *string ); // removes color sequences from string char *Q_CleanStr( char *string ); +// parse "\n" into '\n' +void Q_ParseNewlines( char *dest, const char *src, int destsize ); +// Count the number of char tocount encountered in string +int Q_CountChar(const char *string, char tocount); -//============================================= -// 64-bit integers for global rankings interface -// implemented as a struct for qvm compatibility -typedef struct -{ - byte b0; - byte b1; - byte b2; - byte b3; - byte b4; - byte b5; - byte b6; - byte b7; -} qint64; +#define rc(x) va("%s^7", x) ///< shortcut for color reset after printing variable //============================================= -/* -short BigShort(short l); -short LittleShort(short l); -int BigLong (int l); -int LittleLong (int l); -qint64 BigLong64 (qint64 l); -qint64 LittleLong64 (qint64 l); -float BigFloat (const float *l); -float LittleFloat (const float *l); - -void Swap_Init (void); -*/ -char * QDECL va(char *format, ...) __attribute__ ((format (printf, 1, 2))); + +const char * QDECL va(const char *format, ...) __attribute__ ((format (printf, 1, 2))); #define TRUNCATE_LENGTH 64 void Com_TruncateLongString( char *buffer, const char *s ); @@ -948,78 +879,32 @@ void Com_TruncateLongString( char *buffer, const char *s ); // char *Info_ValueForKey( const char *s, const char *key ); void Info_RemoveKey( char *s, const char *key ); -void Info_RemoveKey_big( char *s, const char *key ); +void Info_RemoveKey_Big( char *s, const char *key ); void Info_SetValueForKey( char *s, const char *key, const char *value ); void Info_SetValueForKey_Big( char *s, const char *key, const char *value ); qboolean Info_Validate( const char *s ); void Info_NextPair( const char **s, char *key, char *value ); // this is only here so the functions in q_shared.c and bg_*.c can link -void QDECL Com_Error( int level, const char *error, ... ) __attribute__ ((format (printf, 2, 3))); +void QDECL Com_Error( int level, const char *error, ... ) __attribute__ ((noreturn, format(printf, 2, 3))); void QDECL Com_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2))); /* -========================================================== +============================================================== -CVARS (console variables) +VoIP -Many variables can be used for cheating purposes, so when -cheats is zero, force all unspecified variables to their -default values. -========================================================== +============================================================== */ -#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc - // used for system variables, not for player - // specific configurations -#define CVAR_USERINFO 2 // sent to server on connect or change -#define CVAR_SERVERINFO 4 // sent in response to front end requests -#define CVAR_SYSTEMINFO 8 // these cvars will be duplicated on all clients -#define CVAR_INIT 16 // don't allow change from console at all, - // but can be set from the command line -#define CVAR_LATCH 32 // will only change when C code next does - // a Cvar_Get(), so it can't be changed - // without proper initialization. modified - // will be set, even though the value hasn't - // changed yet -#define CVAR_ROM 64 // display only, cannot be set by user at all -#define CVAR_USER_CREATED 128 // created by a set command -#define CVAR_TEMP 256 // can be set even when cheats are disabled, but is not archived -#define CVAR_CHEAT 512 // can not be changed if cheats are disabled -#define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued - -#define CVAR_SERVER_CREATED 2048 // cvar was created by a server the client connected to. -#define CVAR_NONEXISTENT 0xFFFFFFFF // Cvar doesn't exist. - -// nothing outside the Cvar_*() functions should modify these fields! -typedef struct cvar_s { - char *name; - char *string; - char *resetString; // cvar_restart will reset to this value - char *latchedString; // for CVAR_LATCH vars - int flags; - qboolean modified; // set each time the cvar is changed - int modificationCount; // incremented each time the cvar is changed - float value; // atof( string ) - int integer; // atoi( string ) - struct cvar_s *next; - struct cvar_s *hashNext; -} cvar_t; - -#define MAX_CVAR_VALUE_STRING 256 - -typedef int cvarHandle_t; +// if you change the count of flags be sure to also change VOIP_FLAGNUM +#define VOIP_SPATIAL 0x01 // spatialized voip message +#define VOIP_DIRECT 0x02 // non-spatialized voip message -// the modules that run in the virtual machine can't access the cvar_t directly, -// so they must ask for structured updates -typedef struct { - cvarHandle_t handle; - int modificationCount; - float value; - int integer; - char string[MAX_CVAR_VALUE_STRING]; -} vmCvar_t; +// number of flags voip knows. You will have to bump protocol version number if you +// change this. +#define VOIP_FLAGCNT 2 /* ============================================================== @@ -1029,7 +914,7 @@ COLLISION DETECTION ============================================================== */ -#include "surfaceflags.h" // shared with the q3map utility +#include "qcommon/surfaceflags.h" // shared with the q3map utility // plane types are used to speed some tests // 0-2 are axial planes @@ -1070,7 +955,8 @@ typedef enum { // a trace is returned when a box is swept through the world typedef struct { qboolean allsolid; // if true, plane is not valid - qboolean startsolid; // if true, the initial point was in a solid area +// FIXME: startsolid is supposed to be bool + int /*qboolean*/ startsolid; // if true, the initial point was in a solid area float fraction; // time completed, 1.0 = didn't hit anything vec3_t endpos; // final position cplane_t plane; // surface normal at impact, transformed to world space @@ -1083,15 +969,12 @@ typedef struct { // trace->entityNum can also be 0 to (MAX_GENTITIES-1) // or ENTITYNUM_NONE, ENTITYNUM_WORLD - -// markfragments are returned by CM_MarkFragments() +// markfragments are returned by R_MarkFragments() typedef struct { int firstPoint; int numPoints; } markFragment_t; - - typedef struct { vec3_t origin; vec3_t axis[3]; @@ -1104,7 +987,7 @@ typedef struct { // if none of the catchers are active, bound key strings will be executed #define KEYCATCH_CONSOLE 0x0001 #define KEYCATCH_UI 0x0002 -#define KEYCATCH_MESSAGE 0x0004 +#define KEYCATCH_MESSAGE 0x0004 #define KEYCATCH_CGAME 0x0008 @@ -1146,6 +1029,7 @@ typedef enum { #define GENTITYNUM_BITS 10 // don't need to send any more #define MAX_GENTITIES (1< + +=========================================================================== +*/ +// qcommon.h -- definitions common between client and server, but not game.or ref modules +#ifndef _QCOMMON_H_ +#define _QCOMMON_H_ + +#include + +#include "cm_public.h" + +//Ignore __attribute__ on non-gcc platforms +#ifndef __GNUC__ +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +struct netadr_t; +struct msg_t; + +/* +============================================================== + +PROTOCOL + +============================================================== +*/ + +#define PROTOCOL_VERSION 71 + +// maintain a list of compatible protocols for demo playing +// NOTE: that stuff only works with two digits protocols +extern int demo_protocols[]; + +// override on command line, config files etc. +#ifndef MASTER_SERVER_NAME +#define MASTER_SERVER_NAME "master.tremulous.net" +#endif + +#define PORT_MASTER 30700 +#define PORT_SERVER 30720 +#define ALT1PORT_MASTER 30700 +#define ALT1PORT_SERVER 30721 +#define ALT2PORT_MASTER 30710 +#define ALT2PORT_SERVER 30722 +#define NUM_SERVER_PORTS 4 // broadcast scan this many ports after + // PORT_SERVER so a single machine can + // run multiple servers + + +// the svc_strings[] array in cl_parse.c should mirror this +// +// server to client +// +enum svc_ops_e { + svc_bad, + svc_nop, + svc_gamestate, + svc_configstring, // [short] [string] only in gamestate messages + svc_baseline, // only in gamestate messages + svc_serverCommand, // [string] to be executed by client game module + svc_download, // [short] size [size bytes] + svc_snapshot, + svc_EOF, + +// new commands, supported only by ioquake3 protocol but not legacy + svc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved. + svc_voipOpus, // +}; + + +// +// client to server +// +enum clc_ops_e { + clc_bad, + clc_nop, + clc_move, // [[usercmd_t] + clc_moveNoDelta, // [[usercmd_t] + clc_clientCommand, // [string] message + clc_EOF, + +// new commands, supported only by ioquake3 protocol but not legacy + clc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved. + clc_voipOpus, // +}; + +//#include "cvar.h" + +typedef struct cvar_s cvar_t; + +/* +============================================================== + +Edit fields and command line history/completion + +============================================================== +*/ + +#define MAX_EDIT_LINE 256 +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; +} field_t; + +void Field_Clear( field_t *edit ); +void Field_AutoComplete( field_t *edit ); +void Field_CompleteKeyname( void ); +void Field_CompleteFilename( const char *dir, const char *ext, bool stripExt, bool allowNonPureFilesOnDisk ); +void Field_CompleteCommand( char *cmd, bool doCommands, bool doCvars ); +void Field_CompletePlayerName( const char **names, int count ); +void Field_CompleteList( char *listJson ); + +/* +============================================================== + +MISC + +============================================================== +*/ + +// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated) +#define MAXPRINTMSG 4096 + + +typedef enum { + // SE_NONE must be zero + SE_NONE = 0, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are relative signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE // evPtr is a char* +} sysEventType_t; + +typedef struct { + int evTime; + sysEventType_t evType; + int evValue, evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void *evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); +int Com_EventLoop( void ); +sysEvent_t Com_GetSystemEvent( void ); + +char *CopyString( const char *in ); +void Info_Print( const char *s ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *)); +void Com_EndRedirect( void ); + +//#ifndef __Q_SHARED_H +void QDECL Com_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL Com_Error( int code, const char *fmt, ... ) __attribute__ ((noreturn, format(printf, 2, 3))); +//#endif +void QDECL Com_DPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); +void Engine_Exit(const char* p ) __attribute__ ((noreturn)); +void Com_Quit_f( void ) __attribute__ ((noreturn)); +void Com_GameRestart(int checksumFeed, bool disconnect); + +int Com_Milliseconds( void ); // will be journaled properly +char *Com_MD5File(const char *filename, int length, const char *prefix, int prefix_len); +int Com_Filter(const char* filter, char *name, int casesensitive); +int Com_FilterPath(const char *filter, char *name, int casesensitive); +int Com_RealTime(qtime_t *qtime); +bool Com_SafeMode( void ); +void Com_RunAndTimeServerPacket(struct netadr_t *evFrom, struct msg_t *buf); + +bool Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum); + +void Com_StartupVariable( const char *match ); +// checks for and removes command line "+set var arg" constructs +// if match is NULL, all set commands will be executed, otherwise +// only a set with the exact name. Only used during startup. + +bool Com_PlayerNameToFieldString( char *str, int length, const char *name ); +bool Com_FieldStringToPlayerName( char *name, int length, const char *rawname ); +int QDECL Com_strCompare( const void *a, const void *b ); + + +extern cvar_t *com_developer; +extern cvar_t *com_dedicated; +extern cvar_t *com_speeds; +extern cvar_t *com_timescale; +extern cvar_t *com_sv_running; +extern cvar_t *com_cl_running; +extern cvar_t *com_version; +extern cvar_t *com_buildScript; // for building release pak files +extern cvar_t *com_journal; +extern cvar_t *com_cameraMode; +extern cvar_t *com_ansiColor; +extern cvar_t *com_unfocused; +extern cvar_t *com_maxfpsUnfocused; +extern cvar_t *com_minimized; +extern cvar_t *com_maxfpsMinimized; +extern cvar_t *com_altivec; +extern cvar_t *com_homepath; + +// both client and server must agree to pause +extern cvar_t *cl_paused; +extern cvar_t *sv_paused; + +extern cvar_t *cl_packetdelay; +extern cvar_t *sv_packetdelay; + +extern cvar_t *com_gamename; + +// com_speeds times +extern int time_game; +extern int time_frontend; +extern int time_backend; // renderer backend time + +extern int com_frameTime; + +extern bool com_errorEntered; +extern bool com_fullyInitialized; + +extern fileHandle_t com_journalFile; +extern fileHandle_t com_journalDataFile; + +typedef enum { + TAG_FREE, + TAG_GENERAL, + TAG_BOTLIB, + TAG_RENDERER, + TAG_SMALL, + TAG_STATIC +} memtag_t; + +/* + +--- low memory ---- +server vm +server clipmap +---mark--- +renderer initialization (shaders, etc) +UI vm +cgame vm +renderer map +renderer models + +---free--- + +temp file loading +--- high memory --- + +*/ + +#if !defined(NDEBUG) && !defined(BSPC) + #define ZONE_DEBUG +#endif + +#ifdef ZONE_DEBUG +#define Z_TagMalloc(size, tag) Z_TagMallocDebug(size, tag, #size, __FILE__, __LINE__) +#define Z_Malloc(size) Z_MallocDebug(size, #size, __FILE__, __LINE__) +#define S_Malloc(size) S_MallocDebug(size, #size, __FILE__, __LINE__) +void *Z_TagMallocDebug( int size, int tag, const char *label, const char *file, int line ); // NOT 0 filled memory +void *Z_MallocDebug( int size, const char *label, const char *file, int line ); // returns 0 filled memory +void *S_MallocDebug( int size, const char *label, const char *file, int line ); // returns 0 filled memory +#else +void *Z_TagMalloc( int size, int tag ); // NOT 0 filled memory +void *Z_Malloc( int size ); // returns 0 filled memory +void *S_Malloc( int size ); // NOT 0 filled memory only for small allocations +#endif +void Z_Free( void *ptr ); +void Z_FreeTags( int tag ); +int Z_AvailableMemory( void ); +void Z_LogHeap( void ); + +void Hunk_Clear( void ); +void Hunk_ClearToMark( void ); +void Hunk_SetMark( void ); +bool Hunk_CheckMark( void ); +void Hunk_ClearTempMemory( void ); +void *Hunk_AllocateTempMemory( int size ); +void Hunk_FreeTempMemory( void *buf ); +int Hunk_MemoryRemaining( void ); +void Hunk_Log( void); + +void Com_TouchMemory( void ); + +// commandLine should not include the executable name (argv[0]) +void Com_Init( char *commandLine ); +void Com_Frame( void ); +void Com_Shutdown( void ); + + +/* +============================================================== + +CLIENT / SERVER SYSTEMS + +============================================================== +*/ + +// +// client interface +// +void CL_InitKeyCommands( void ); +// the keyboard binding interface must be setup before execing +// config files, but the rest of client startup will happen later + +void CL_Init( void ); +void CL_Disconnect( bool showMainMenu ); +void CL_Shutdown(const char *finalmsg, bool disconnect, bool quit); +void CL_Frame( int msec ); +bool CL_GameCommand( void ); +void CL_KeyEvent (int key, bool down, unsigned time); + +void CL_CharEvent( int key ); +// char events are for field typing, not game control + +void CL_MouseEvent( int dx, int dy, int time ); + +void CL_JoystickEvent( int axis, int value, int time ); + +void CL_PacketEvent( struct netadr_t from, struct msg_t *msg ); + +void CL_ConsolePrint( const char *text ); + +void CL_MapLoading( void ); +// do a screen update before starting to load a map +// when the server is going to load a new map, the entire hunk +// will be cleared, so the client must shutdown cgame, ui, and +// the renderer + +void CL_ForwardCommandToServer( const char *string ); +// adds the current command line as a clc_clientCommand 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 CL_FlushMemory( void ); +// dump all memory on an error + +void CL_ShutdownAll(bool shutdownRef); +// shutdown client + +void CL_StartHunkUsers( bool rendererOnly ); +// start all the client stuff using the hunk + +void Key_KeynameCompletion( void(*callback)(const char *s) ); +// for keyname autocompletion + +void Key_WriteBindings( fileHandle_t f ); +// for writing the config files + +void S_ClearSoundBuffer( void ); +// call before filesystem access + +void SCR_DebugGraph (float value); // FIXME: move logging to common? + +// +// server interface +// +void SV_Init( void ); +void SV_Shutdown( const char *finalmsg ); +void SV_Frame( int msec ); +void SV_PacketEvent( struct netadr_t from, struct msg_t *msg ); +int SV_FrameMsec(void); +bool SV_GameCommand( void ); +int SV_SendQueuedPackets(void); + +// +// UI interface +// +bool UI_GameCommand( void ); + +/* +============================================================== + +NON-PORTABLE SYSTEM SERVICES + +============================================================== +*/ + +bool Parse_AddGlobalDefine(char *string); +int Parse_LoadSourceHandle(const char *filename); +bool Parse_FreeSourceHandle(int handle); +bool Parse_ReadTokenHandle(int handle, pc_token_t *pc_token); +bool Parse_SourceFileAndLine(int handle, char *filename, int *line); + +// flags for sv_allowDownload and cl_allowDownload +#define DLF_ENABLE 1 +#define DLF_NO_REDIRECT 2 +#define DLF_NO_UDP 4 +#define DLF_NO_DISCONNECT 8 + +#endif // _QCOMMON_H_ diff --git a/src/qcommon/qfiles.h b/src/qcommon/qfiles.h index 7e901b7..91bcf04 100644 --- a/src/qcommon/qfiles.h +++ b/src/qcommon/qfiles.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,19 +17,21 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ + +// qfiles.h: quake file formats + #ifndef __QFILES_H__ #define __QFILES_H__ -// -// qfiles.h: quake file formats // This file must be identical in the quake and utils directories -// -//Ignore __attribute__ on non-gcc platforms +#include "q_shared.h" + +// Ignore __attribute__ on non-gcc platforms #ifndef __GNUC__ #ifndef __attribute__ #define __attribute__(x) @@ -36,12 +39,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif // surface geometry should not exceed these limits -#define SHADER_MAX_VERTEXES 1000 -#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) - +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6 * SHADER_MAX_VERTEXES) // the maximum size of game relative pathnames -#define MAX_QPATH 64 +#define MAX_QPATH 64 /* ======================================================================== @@ -51,69 +53,25 @@ QVM files ======================================================================== */ -#define VM_MAGIC 0x12721444 -#define VM_MAGIC_VER2 0x12721445 +#define VM_MAGIC 0x12721444 +#define VM_MAGIC_VER2 0x12721445 typedef struct { - int vmMagic; + int vmMagic; - int instructionCount; + int instructionCount; - int codeOffset; - int codeLength; + int codeOffset; + int codeLength; - int dataOffset; - int dataLength; - int litLength; // ( dataLength - litLength ) should be byteswapped on load - int bssLength; // zero filled memory appended to datalength + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength - //!!! below here is VM_MAGIC_VER2 !!! - int jtrgLength; // number of jump table targets + //!!! below here is VM_MAGIC_VER2 !!! + int jtrgLength; // number of jump table targets } vmHeader_t; - -/* -======================================================================== - -PCX files are used for 8 bit images - -======================================================================== -*/ - -typedef struct { - char manufacturer; - char version; - char encoding; - char bits_per_pixel; - unsigned short xmin,ymin,xmax,ymax; - unsigned short hres,vres; - unsigned char palette[48]; - char reserved; - char color_planes; - unsigned short bytes_per_line; - unsigned short palette_type; - char filler[58]; - unsigned char data; // unbounded -} pcx_t; - - -/* -======================================================================== - -TGA files are used for 24/32 bit images - -======================================================================== -*/ - -typedef struct _TargaHeader { - unsigned char id_length, colormap_type, image_type; - unsigned short colormap_index, colormap_length; - unsigned char colormap_size; - unsigned short x_origin, y_origin, width, height; - unsigned char pixel_size, attributes; -} TargaHeader; - - - /* ======================================================================== @@ -122,195 +80,112 @@ typedef struct _TargaHeader { ======================================================================== */ -#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') -#define MD3_VERSION 15 +#define MD3_IDENT (('3' << 24) + ('P' << 16) + ('D' << 8) + 'I') +#define MD3_VERSION 15 // limits -#define MD3_MAX_LODS 3 -#define MD3_MAX_TRIANGLES 8192 // per surface -#define MD3_MAX_VERTS 4096 // per surface -#define MD3_MAX_SHADERS 256 // per surface -#define MD3_MAX_FRAMES 1024 // per model -#define MD3_MAX_SURFACES 32 // per model -#define MD3_MAX_TAGS 16 // per frame +#define MD3_MAX_LODS 3 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame // vertex scales -#define MD3_XYZ_SCALE (1.0/64) +#define MD3_XYZ_SCALE (1.0 / 64) typedef struct md3Frame_s { - vec3_t bounds[2]; - vec3_t localOrigin; - float radius; - char name[16]; + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; } md3Frame_t; typedef struct md3Tag_s { - char name[MAX_QPATH]; // tag name - vec3_t origin; - vec3_t axis[3]; + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; } md3Tag_t; /* ** md3Surface_t ** -** CHUNK SIZE -** header sizeof( md3Surface_t ) -** shaders sizeof( md3Shader_t ) * numShaders -** triangles[0] sizeof( md3Triangle_t ) * numTriangles -** st sizeof( md3St_t ) * numVerts -** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames */ typedef struct { - int ident; // + int ident; // - char name[MAX_QPATH]; // polyset name + char name[MAX_QPATH]; // polyset name - int flags; - int numFrames; // all surfaces in a model should have the same + int flags; + int numFrames; // all surfaces in a model should have the same - int numShaders; // all surfaces in a model should have the same - int numVerts; + int numShaders; // all surfaces in a model should have the same + int numVerts; - int numTriangles; - int ofsTriangles; + int numTriangles; + int ofsTriangles; - int ofsShaders; // offset from start of md3Surface_t - int ofsSt; // texture coords are common for all frames - int ofsXyzNormals; // numVerts * numFrames + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames - int ofsEnd; // next surface follows + int ofsEnd; // next surface follows } md3Surface_t; typedef struct { - char name[MAX_QPATH]; - int shaderIndex; // for in-game use + char name[MAX_QPATH]; + int shaderIndex; // for in-game use } md3Shader_t; typedef struct { - int indexes[3]; + int indexes[3]; } md3Triangle_t; typedef struct { - float st[2]; + float st[2]; } md3St_t; typedef struct { - short xyz[3]; - short normal; + short xyz[3]; + short normal; } md3XyzNormal_t; typedef struct { - int ident; - int version; + int ident; + int version; - char name[MAX_QPATH]; // model name + char name[MAX_QPATH]; // model name - int flags; + int flags; - int numFrames; - int numTags; - int numSurfaces; + int numFrames; + int numTags; + int numSurfaces; - int numSkins; + int numSkins; - int ofsFrames; // offset for first frame - int ofsTags; // numFrames * numTags - int ofsSurfaces; // first surface, others follow + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow - int ofsEnd; // end of file + int ofsEnd; // end of file } md3Header_t; /* ============================================================================== -MD4 file format +MDR file format ============================================================================== */ -#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I') -#define MD4_VERSION 1 -#define MD4_MAX_BONES 128 - -typedef struct { - int boneIndex; // these are indexes into the boneReferences, - float boneWeight; // not the global per-frame bone list - vec3_t offset; -} md4Weight_t; - -typedef struct { - vec3_t normal; - vec2_t texCoords; - int numWeights; - md4Weight_t weights[1]; // variable sized -} md4Vertex_t; - -typedef struct { - int indexes[3]; -} md4Triangle_t; - -typedef struct { - int ident; - - char name[MAX_QPATH]; // polyset name - char shader[MAX_QPATH]; - int shaderIndex; // for in-game use - - int ofsHeader; // this will be a negative number - - int numVerts; - int ofsVerts; - - int numTriangles; - int ofsTriangles; - - // Bone references are a set of ints representing all the bones - // present in any vertex weights for this surface. This is - // needed because a model may have surfaces that need to be - // drawn at different sort times, and we don't want to have - // to re-interpolate all the bones for each surface. - int numBoneReferences; - int ofsBoneReferences; - - int ofsEnd; // next surface follows -} md4Surface_t; - -typedef struct { - float matrix[3][4]; -} md4Bone_t; - -typedef struct { - vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame - vec3_t localOrigin; // midpoint of bounds, used for sphere cull - float radius; // dist from localOrigin to corner - md4Bone_t bones[1]; // [numBones] -} md4Frame_t; - -typedef struct { - int numSurfaces; - int ofsSurfaces; // first surface, others follow - int ofsEnd; // next lod follows -} md4LOD_t; - -typedef struct { - int ident; - int version; - - char name[MAX_QPATH]; // model name - - // frames and bones are shared by all levels of detail - int numFrames; - int numBones; - int ofsBoneNames; // char name[ MAX_QPATH ] - int ofsFrames; // md4Frame_t[numFrames] - - // each level of detail has completely separate sets of surfaces - int numLODs; - int ofsLODs; - - int ofsEnd; // end of file -} md4Header_t; - /* * Here are the definitions for Ravensoft's model format of md4. Raven stores their * playermodels in .mdr files, in some games, which are pretty much like the md4 @@ -326,116 +201,108 @@ typedef struct { * - Thilo Schulz (arny@ats.s.bawue.de) */ -// If you want to enable support for Raven's .mdr / md4 format, uncomment the next -// line. -//#define RAVENMD4 - -#ifdef RAVENMD4 - -#define MDR_IDENT (('5'<<24)+('M'<<16)+('D'<<8)+'R') -#define MDR_VERSION 2 -#define MDR_MAX_BONES 128 +#define MDR_IDENT (('5' << 24) + ('M' << 16) + ('D' << 8) + 'R') +#define MDR_VERSION 2 +#define MDR_MAX_BONES 128 typedef struct { - int boneIndex; // these are indexes into the boneReferences, - float boneWeight; // not the global per-frame bone list - vec3_t offset; + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; } mdrWeight_t; typedef struct { - vec3_t normal; - vec2_t texCoords; - int numWeights; - mdrWeight_t weights[1]; // variable sized + vec3_t normal; + vec2_t texCoords; + int numWeights; + mdrWeight_t weights[1]; // variable sized } mdrVertex_t; typedef struct { - int indexes[3]; + int indexes[3]; } mdrTriangle_t; typedef struct { - int ident; + int ident; - char name[MAX_QPATH]; // polyset name - char shader[MAX_QPATH]; - int shaderIndex; // for in-game use + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use - int ofsHeader; // this will be a negative number + int ofsHeader; // this will be a negative number - int numVerts; - int ofsVerts; + int numVerts; + int ofsVerts; - int numTriangles; - int ofsTriangles; + int numTriangles; + int ofsTriangles; - // Bone references are a set of ints representing all the bones - // present in any vertex weights for this surface. This is - // needed because a model may have surfaces that need to be - // drawn at different sort times, and we don't want to have - // to re-interpolate all the bones for each surface. - int numBoneReferences; - int ofsBoneReferences; + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; - int ofsEnd; // next surface follows + int ofsEnd; // next surface follows } mdrSurface_t; typedef struct { - float matrix[3][4]; + float matrix[3][4]; } mdrBone_t; typedef struct { - vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame - vec3_t localOrigin; // midpoint of bounds, used for sphere cull - float radius; // dist from localOrigin to corner - char name[16]; - mdrBone_t bones[1]; // [numBones] + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + mdrBone_t bones[1]; // [numBones] } mdrFrame_t; typedef struct { - unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple + unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple } mdrCompBone_t; typedef struct { - vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame - vec3_t localOrigin; // midpoint of bounds, used for sphere cull - float radius; // dist from localOrigin to corner - mdrCompBone_t bones[1]; // [numBones] + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + mdrCompBone_t bones[1]; // [numBones] } mdrCompFrame_t; typedef struct { - int numSurfaces; - int ofsSurfaces; // first surface, others follow - int ofsEnd; // next lod follows + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows } mdrLOD_t; typedef struct { - int boneIndex; - char name[32]; + int boneIndex; + char name[32]; } mdrTag_t; typedef struct { - int ident; - int version; + int ident; + int version; - char name[MAX_QPATH]; // model name + char name[MAX_QPATH]; // model name - // frames and bones are shared by all levels of detail - int numFrames; - int numBones; - int ofsFrames; // mdrFrame_t[numFrames] + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // mdrFrame_t[numFrames] - // each level of detail has completely separate sets of surfaces - int numLODs; - int ofsLODs; + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; - int numTags; - int ofsTags; + int numTags; + int ofsTags; - int ofsEnd; // end of file + int ofsEnd; // end of file } mdrHeader_t; -#endif - /* ============================================================================== @@ -444,183 +311,172 @@ typedef struct { ============================================================================== */ +#define BSP_IDENT (('P' << 24) + ('S' << 16) + ('B' << 8) + 'I') +// little-endian "IBSP" -#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') - // little-endian "IBSP" - -#define BSP_VERSION 46 - +#define BSP_VERSION 46 // there shouldn't be any problem with increasing these values at the // expense of more memory allocation in the utilities -#define MAX_MAP_MODELS 0x400 -#define MAX_MAP_BRUSHES 0x8000 -#define MAX_MAP_ENTITIES 0x800 -#define MAX_MAP_ENTSTRING 0x40000 -#define MAX_MAP_SHADERS 0x400 - -#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! -#define MAX_MAP_FOGS 0x100 -#define MAX_MAP_PLANES 0x20000 -#define MAX_MAP_NODES 0x20000 -#define MAX_MAP_BRUSHSIDES 0x20000 -#define MAX_MAP_LEAFS 0x20000 -#define MAX_MAP_LEAFFACES 0x20000 -#define MAX_MAP_LEAFBRUSHES 0x40000 -#define MAX_MAP_PORTALS 0x20000 -#define MAX_MAP_LIGHTING 0x800000 -#define MAX_MAP_LIGHTGRID 0x800000 -#define MAX_MAP_VISIBILITY 0x200000 - -#define MAX_MAP_DRAW_SURFS 0x20000 -#define MAX_MAP_DRAW_VERTS 0x80000 -#define MAX_MAP_DRAW_INDEXES 0x80000 - +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 // key / value pair sizes in the entities lump -#define MAX_KEY 32 -#define MAX_VALUE 1024 +#define MAX_KEY 32 +#define MAX_VALUE 1024 // the editor uses these predefined yaw angles to orient entities up or down -#define ANGLE_UP -1 -#define ANGLE_DOWN -2 +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 -#define LIGHTMAP_WIDTH 128 -#define LIGHTMAP_HEIGHT 128 +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 -#define MAX_WORLD_COORD ( 128*1024 ) -#define MIN_WORLD_COORD ( -128*1024 ) -#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) +#define MAX_WORLD_COORD (128 * 1024) +#define MIN_WORLD_COORD (-128 * 1024) +#define WORLD_SIZE (MAX_WORLD_COORD - MIN_WORLD_COORD) //============================================================================= - typedef struct { - int fileofs, filelen; + int fileofs, filelen; } lump_t; -#define LUMP_ENTITIES 0 -#define LUMP_SHADERS 1 -#define LUMP_PLANES 2 -#define LUMP_NODES 3 -#define LUMP_LEAFS 4 -#define LUMP_LEAFSURFACES 5 -#define LUMP_LEAFBRUSHES 6 -#define LUMP_MODELS 7 -#define LUMP_BRUSHES 8 -#define LUMP_BRUSHSIDES 9 -#define LUMP_DRAWVERTS 10 -#define LUMP_DRAWINDEXES 11 -#define LUMP_FOGS 12 -#define LUMP_SURFACES 13 -#define LUMP_LIGHTMAPS 14 -#define LUMP_LIGHTGRID 15 -#define LUMP_VISIBILITY 16 -#define HEADER_LUMPS 17 - -typedef struct { - int ident; - int version; - - lump_t lumps[HEADER_LUMPS]; +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; } dheader_t; typedef struct { - float mins[3], maxs[3]; - int firstSurface, numSurfaces; - int firstBrush, numBrushes; + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; } dmodel_t; typedef struct { - char shader[MAX_QPATH]; - int surfaceFlags; - int contentFlags; + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; } dshader_t; // planes x^1 is allways the opposite of plane x typedef struct { - float normal[3]; - float dist; + float normal[3]; + float dist; } dplane_t; typedef struct { - int planeNum; - int children[2]; // negative numbers are -(leafs+1), not nodes - int mins[3]; // for frustom culling - int maxs[3]; + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; } dnode_t; typedef struct { - int cluster; // -1 = opaque cluster (do I still store these?) - int area; + int cluster; // -1 = opaque cluster (do I still store these?) + int area; - int mins[3]; // for frustum culling - int maxs[3]; + int mins[3]; // for frustum culling + int maxs[3]; - int firstLeafSurface; - int numLeafSurfaces; + int firstLeafSurface; + int numLeafSurfaces; - int firstLeafBrush; - int numLeafBrushes; + int firstLeafBrush; + int numLeafBrushes; } dleaf_t; typedef struct { - int planeNum; // positive plane side faces out of the leaf - int shaderNum; + int planeNum; // positive plane side faces out of the leaf + int shaderNum; } dbrushside_t; typedef struct { - int firstSide; - int numSides; - int shaderNum; // the shader that determines the contents flags + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags } dbrush_t; typedef struct { - char shader[MAX_QPATH]; - int brushNum; - int visibleSide; // the brush side that ray tests need to clip against (-1 == none) + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) } dfog_t; typedef struct { - vec3_t xyz; - float st[2]; - float lightmap[2]; - vec3_t normal; - byte color[4]; + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; } drawVert_t; -#define drawVert_t_cleared(x) drawVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}} +#define drawVert_t_cleared(x) drawVert_t(x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}} -typedef enum { - MST_BAD, - MST_PLANAR, - MST_PATCH, - MST_TRIANGLE_SOUP, - MST_FLARE -} mapSurfaceType_t; +typedef enum { MST_BAD, MST_PLANAR, MST_PATCH, MST_TRIANGLE_SOUP, MST_FLARE } mapSurfaceType_t; typedef struct { - int shaderNum; - int fogNum; - int surfaceType; + int shaderNum; + int fogNum; + int surfaceType; - int firstVert; - int numVerts; + int firstVert; + int numVerts; - int firstIndex; - int numIndexes; + int firstIndex; + int numIndexes; - int lightmapNum; - int lightmapX, lightmapY; - int lightmapWidth, lightmapHeight; + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; - vec3_t lightmapOrigin; - vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds - int patchWidth; - int patchHeight; + int patchWidth; + int patchHeight; } dsurface_t; - #endif diff --git a/src/qcommon/surfaceflags.h b/src/qcommon/surfaceflags.h index 31ece5c..f47006d 100644 --- a/src/qcommon/surfaceflags.h +++ b/src/qcommon/surfaceflags.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // @@ -60,10 +61,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define CONTENTS_TRIGGER 0x40000000 #define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava) -//TA: custominfoparms below +// custominfoparms below #define CONTENTS_NOALIENBUILD 0x1000 //disallow alien building -#define CONTENTS_NOHUMANBUILD 0x2000 //disallow alien building -#define CONTENTS_NOBUILD 0x4000 //disallow alien building +#define CONTENTS_NOHUMANBUILD 0x2000 //disallow human building +#define CONTENTS_NOBUILD 0x4000 //disallow building #define SURF_NODAMAGE 0x1 // never give falling damage #define SURF_SLICK 0x2 // effects game physics @@ -85,7 +86,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies) #define SURF_DUST 0x40000 // leave a dust trail when walking on this surface -//TA: custominfoparms below +// custominfoparms below #define SURF_NOALIENBUILD 0x80000 //disallow alien building -#define SURF_NOHUMANBUILD 0x100000 //disallow alien building -#define SURF_NOBUILD 0x200000 //disallow alien building +#define SURF_NOHUMANBUILD 0x100000 //disallow human building +#define SURF_NOBUILD 0x200000 //disallow building diff --git a/src/qcommon/unzip.cpp b/src/qcommon/unzip.cpp new file mode 100644 index 0000000..8564ece --- /dev/null +++ b/src/qcommon/unzip.cpp @@ -0,0 +1,1951 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project + + Copyright (C) 1998-2010 Gilles Vollant + http://www.winimage.com/zLibDll/minizip.html + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson + http://result42.com + Modifications for AES, PKWARE disk spanning + Copyright (C) 2010-2014 Nathan Moinvaziri + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. + + Mar 8th, 2016 - Lucio Cosmo + Fixed support for 64bit builds for archives with "PKWARE" password. + Changed long, unsigned long, unsigned to unsigned int in + access functions to crctables and pkeys +*/ + +#define NOUNCRYPT +#include "unzip.h" + +#include +#include +#include +#include + +#include "q_shared.h" +#include "qcommon.h" + +#include "zconf.h" +#include "zlib.h" + +#ifdef HAVE_AES +# define AES_METHOD (99) +# define AES_PWVERIFYSIZE (2) +# define AES_MAXSALTLENGTH (16) +# define AES_AUTHCODESIZE (10) +# define AES_HEADERSIZE (11) +# define AES_KEYSIZE(mode) (64 + (mode * 64)) + +# include "aes/aes.h" +# include "aes/fileenc.h" +#endif +#ifndef NOUNCRYPT +# include "crypt.h" +#endif + +#ifndef static +# define static static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +#define DISKHEADERMAGIC (0x08074b50) +#define LOCALHEADERMAGIC (0x04034b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) +#define ZIP64ENDHEADERMAGIC (0x06064b50) +#define ZIP64ENDLOCHEADERMAGIC (0x07064b50) + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZECENTRALHEADERLOCATOR (0x14) /* 20 */ +#define SIZEZIPLOCALHEADER (0x1e) + +#ifndef BUFREADCOMMENT +# define BUFREADCOMMENT (0x400) +#endif + +#ifndef UNZ_BUFSIZE +# define UNZ_BUFSIZE (64 * 1024) +#endif +#ifndef UNZ_MAXFILENAMEINZIP +# define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Z_Malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Z_Free(p);} +#endif + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info64_internal_s +{ + ZPOS64_T offset_curfile; /* relative offset of static header 8 bytes */ + ZPOS64_T byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx) */ +#ifdef HAVE_AES + uLong aes_encryption_mode; + uLong aes_compression_method; + uLong aes_version; +#endif +} unz_file_info64_internal; + +/* file_in_zip_read_info_s contain internal information about a file in zipfile */ +typedef struct +{ + Bytef *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif +#ifdef HAVE_AES + fcrypt_ctx aes_ctx; +#endif + + ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek */ + uLong stream_initialised; /* flag set if stream structure is initialised */ + + ZPOS64_T offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + ZPOS64_T pos_local_extrafield; /* position in the static extra field in read */ + ZPOS64_T total_out_64; + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */ + ZPOS64_T rest_read_uncompressed; /* number of byte to be obtained after decomp */ + + zlib_filefunc64_32_def z_filefunc; + + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + ZPOS64_T byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx) */ + int raw; +} file_in_zip64_read_info_s; + +/* unz64_s contain internal information about the zipfile */ +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structure of the current zipfile */ + voidpf filestream_with_CD; /* io structure of the disk with the central directory */ + unz_global_info64 gi; /* public global information */ + ZPOS64_T byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx)*/ + ZPOS64_T num_file; /* number of the current file in the zipfile*/ + ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/ + ZPOS64_T current_file_ok; /* flag about the usability of the current file*/ + ZPOS64_T central_pos; /* position of the beginning of the central dir*/ + uLong number_disk; /* number of the current disk, used for spanning ZIP*/ + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info64 cur_file_info; /* public info about the current file in zip*/ + unz_file_info64_internal cur_file_info_internal; + /* private info about it*/ + file_in_zip64_read_info_s* pfile_in_zip_read; + /* structure about the current file if we are decompressing it */ + int isZip64; /* is the current file zip64 */ +#ifndef NOUNCRYPT + unsigned int keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned int* pcrc_32_tab; +#endif +} unz64_s; + +/* Translate date/time from Dos format to tm_unz (readable more easily) */ +static void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm) +{ + ZPOS64_T uDate = (ZPOS64_T)(ulDosDate>>16); + + ptm->tm_mday = (uInt)(uDate&0x1f); + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1); + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980); + ptm->tm_hour = (uInt)((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt)((ulDosDate&0x7E0)/0x20); + ptm->tm_sec = (uInt)(2*(ulDosDate&0x1f)); + +#define unz64local_in_range(min, max, value) ((min) <= (value) && (value) <= (max)) + if (!unz64local_in_range(0, 11, ptm->tm_mon) || + !unz64local_in_range(1, 31, ptm->tm_mday) || + !unz64local_in_range(0, 23, ptm->tm_hour) || + !unz64local_in_range(0, 59, ptm->tm_min) || + !unz64local_in_range(0, 59, ptm->tm_sec)) + /* Invalid date stored, so don't return it. */ + memset(ptm, 0, sizeof(tm_unz)); +#undef unz64local_in_range +} + +/* Read a byte from a gz_stream; Return EOF for end of file. */ +static int unz64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); +static int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def, filestream, &c, 1); + if (err == 1) + { + *pi = (int)c; + return UNZ_OK; + } + *pi = 0; + if (ZERROR64(*pzlib_filefunc_def, filestream)) + return UNZ_ERRNO; + return UNZ_EOF; +} + +static int unz64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); +static int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX) +{ + uLong x; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x = (uLong)i; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((uLong)i)<<8; + + if (err == UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +static int unz64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); +static int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX) +{ + uLong x; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x = (uLong)i; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((uLong)i)<<8; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((uLong)i)<<16; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x += ((uLong)i)<<24; + + if (err == UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +static int unz64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); +static int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) +{ + ZPOS64_T x; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x = (ZPOS64_T)i; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<8; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<16; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<24; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<32; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<40; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<48; + if (err == UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def, filestream, &i); + x |= ((ZPOS64_T)i)<<56; + + if (err == UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +/* Locate the Central directory of a zip file (at the end, just before the global comment) */ +static ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); +static ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T file_size; + ZPOS64_T back_read = 4; + ZPOS64_T max_back = 0xffff; /* maximum size of global comment */ + ZPOS64_T pos_found = 0; + uLong read_size; + ZPOS64_T read_pos; + int i; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT + 4); + if (buf == NULL) + return 0; + + if (ZSEEK64(*pzlib_filefunc_def, filestream, 0, ZLIB_FILEFUNC_SEEK_END) != 0) + { + TRYFREE(buf); + return 0; + } + + file_size = ZTELL64(*pzlib_filefunc_def, filestream); + + if (max_back > file_size) + max_back = file_size; + + while (back_read < max_back) + { + if (back_read + BUFREADCOMMENT > max_back) + back_read = max_back; + else + back_read += BUFREADCOMMENT; + + read_pos = file_size - back_read; + read_size = ((BUFREADCOMMENT + 4) < (file_size - read_pos)) ? + (BUFREADCOMMENT + 4) : (uLong)(file_size - read_pos); + + if (ZSEEK64(*pzlib_filefunc_def, filestream, read_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + break; + if (ZREAD64(*pzlib_filefunc_def, filestream, buf, read_size) != read_size) + break; + + for (i = (int)read_size-3; (i--) > 0;) + if (((*(buf+i)) == (ENDHEADERMAGIC & 0xff)) && + ((*(buf+i+1)) == (ENDHEADERMAGIC >> 8 & 0xff)) && + ((*(buf+i+2)) == (ENDHEADERMAGIC >> 16 & 0xff)) && + ((*(buf+i+3)) == (ENDHEADERMAGIC >> 24 & 0xff))) + { + pos_found = read_pos+i; + break; + } + + if (pos_found != 0) + break; + } + TRYFREE(buf); + return pos_found; +} + +/* Locate the Central directory 64 of a zipfile (at the end, just before the global comment) */ +static ZPOS64_T unz64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, + const ZPOS64_T endcentraloffset)); +static ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, + const ZPOS64_T endcentraloffset) +{ + ZPOS64_T offset; + uLong uL; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def, filestream, endcentraloffset - SIZECENTRALHEADERLOCATOR, ZLIB_FILEFUNC_SEEK_SET) != 0) + return 0; + + /* read locator signature */ + if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK) + return 0; + if (uL != ZIP64ENDLOCHEADERMAGIC) + return 0; + /* number of the disk with the start of the zip64 end of central directory */ + if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK) + return 0; + /* relative offset of the zip64 end of central directory record */ + if (unz64local_getLong64(pzlib_filefunc_def, filestream, &offset) != UNZ_OK) + return 0; + /* total number of disks */ + if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK) + return 0; + /* Goto end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def, filestream, offset, ZLIB_FILEFUNC_SEEK_SET) != 0) + return 0; + /* the signature */ + if (unz64local_getLong(pzlib_filefunc_def, filestream, &uL) != UNZ_OK) + return 0; + if (uL != ZIP64ENDHEADERMAGIC) + return 0; + + return offset; +} + +static unzFile unzOpenInternal(const void *path, zlib_filefunc64_32_def* pzlib_filefunc64_32_def) +{ + unz64_s us; + unz64_s *s; + ZPOS64_T central_pos; + ZPOS64_T central_pos64; + uLong uL; + ZPOS64_T uL64; + voidpf filestream = NULL; + ZPOS64_T number_entry_CD; + int err = UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + us.filestream = NULL; + us.filestream_with_CD = NULL; + us.z_filefunc.zseek32_file = NULL; + us.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def == NULL) + fill_fopen64_filefunc(&us.z_filefunc.zfile_func64); + else + us.z_filefunc = *pzlib_filefunc64_32_def; + + us.filestream = ZOPEN64(us.z_filefunc, path, ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING); + + if (us.filestream == NULL) + return NULL; + + us.filestream_with_CD = us.filestream; + us.isZip64 = 0; + + /* Search for end of central directory header */ + central_pos = unz64local_SearchCentralDir(&us.z_filefunc, us.filestream); + if (central_pos) + { + if (ZSEEK64(us.z_filefunc, us.filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + err = UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + /* number of this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + us.number_disk = uL; + /* number of the disk with the start of the central directory */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,& uL) != UNZ_OK) + err = UNZ_ERRNO; + us.gi.number_disk_with_CD = uL; + /* total number of entries in the central directory on this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + us.gi.number_entry = uL; + /* total number of entries in the central directory */ + if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + number_entry_CD = uL; + if (number_entry_CD != us.gi.number_entry) + err = UNZ_BADZIPFILE; + /* size of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + us.size_central_dir = uL; + /* offset of start of central directory with respect to the starting disk number */ + if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + us.offset_central_dir = uL; + /* zipfile comment length */ + if (unz64local_getShort(&us.z_filefunc, us.filestream, &us.gi.size_comment) != UNZ_OK) + err = UNZ_ERRNO; + + if (err == UNZ_OK) + { + /* Search for Zip64 end of central directory header */ + central_pos64 = unz64local_SearchCentralDir64(&us.z_filefunc, us.filestream, central_pos); + if (central_pos64) + { + central_pos = central_pos64; + us.isZip64 = 1; + + if (ZSEEK64(us.z_filefunc, us.filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + err = UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + /* size of zip64 end of central directory record */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream, &uL64) != UNZ_OK) + err = UNZ_ERRNO; + /* version made by */ + if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + /* version needed to extract */ + if (unz64local_getShort(&us.z_filefunc, us.filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + /* number of this disk */ + if (unz64local_getLong(&us.z_filefunc, us.filestream, &us.number_disk) != UNZ_OK) + err = UNZ_ERRNO; + /* number of the disk with the start of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream, &us.gi.number_disk_with_CD) != UNZ_OK) + err = UNZ_ERRNO; + /* total number of entries in the central directory on this disk */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream, &us.gi.number_entry) != UNZ_OK) + err = UNZ_ERRNO; + /* total number of entries in the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream, &number_entry_CD) != UNZ_OK) + err = UNZ_ERRNO; + if (number_entry_CD != us.gi.number_entry) + err = UNZ_BADZIPFILE; + /* size of the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream, &us.size_central_dir) != UNZ_OK) + err = UNZ_ERRNO; + /* offset of start of central directory with respect to the starting disk number */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream, &us.offset_central_dir) != UNZ_OK) + err = UNZ_ERRNO; + } + else if ((us.gi.number_entry == 0xffff) || (us.size_central_dir == 0xffff) || (us.offset_central_dir == 0xffffffff)) + err = UNZ_BADZIPFILE; + } + } + else + err = UNZ_ERRNO; + + if ((err == UNZ_OK) && (central_pos < us.offset_central_dir + us.size_central_dir)) + err = UNZ_BADZIPFILE; + + if (err != UNZ_OK) + { + ZCLOSE64(us.z_filefunc, us.filestream); + return NULL; + } + + if (us.gi.number_disk_with_CD == 0) + { + /* If there is only one disk open another stream so we don't have to seek between the CD + and the file headers constantly */ + filestream = ZOPEN64(us.z_filefunc, path, ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING); + if (filestream != NULL) + us.filestream = filestream; + } + + /* Hack for zip files that have no respect for zip64 + if ((central_pos > 0xffffffff) && (us.offset_central_dir < 0xffffffff)) + us.offset_central_dir = central_pos - us.size_central_dir;*/ + + us.byte_before_the_zipfile = central_pos - (us.offset_central_dir + us.size_central_dir); + us.central_pos = central_pos; + us.pfile_in_zip_read = NULL; + + s = (unz64_s*)ALLOC(sizeof(unz64_s)); + if (s != NULL) + { + *s = us; + unzGoToFirstFile((unzFile)s); + } + return (unzFile)s; +} + +extern unzFile ZEXPORT unzOpen2(const char *path, zlib_filefunc_def* pzlib_filefunc32_def) +{ + if (pzlib_filefunc32_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill, pzlib_filefunc32_def); + return unzOpenInternal(path, &zlib_filefunc64_32_def_fill); + } + return unzOpenInternal(path, NULL); +} + +extern unzFile ZEXPORT unzOpen2_64(const void *path, zlib_filefunc64_def* pzlib_filefunc_def) +{ + if (pzlib_filefunc_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; + zlib_filefunc64_32_def_fill.ztell32_file = NULL; + zlib_filefunc64_32_def_fill.zseek32_file = NULL; + return unzOpenInternal(path, &zlib_filefunc64_32_def_fill); + } + return unzOpenInternal(path, NULL); +} + +extern unzFile ZEXPORT unzOpen(const char *path) +{ + return unzOpenInternal(path, NULL); +} + +extern unzFile ZEXPORT unzOpen64(const void *path) +{ + return unzOpenInternal(path, NULL); +} + +extern int ZEXPORT unzClose(unzFile file) +{ + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if ((s->filestream != NULL) && (s->filestream != s->filestream_with_CD)) + ZCLOSE64(s->z_filefunc, s->filestream); + if (s->filestream_with_CD != NULL) + ZCLOSE64(s->z_filefunc, s->filestream_with_CD); + + s->filestream = NULL; + s->filestream_with_CD = NULL; + TRYFREE(s); + return UNZ_OK; +} + +/* Goto to the next available disk for spanned archives */ +static int unzGoToNextDisk OF((unzFile file)); +static int unzGoToNextDisk(unzFile file) +{ + unz64_s* s; + uLong number_disk_next = 0; + + s = (unz64_s*)file; + if (s == NULL) + return UNZ_PARAMERROR; + number_disk_next = s->number_disk; + + if ((s->pfile_in_zip_read != NULL) && (s->pfile_in_zip_read->rest_read_uncompressed > 0)) + /* We are currently reading a file and we need the next sequential disk */ + number_disk_next += 1; + else + /* Goto the disk for the current file */ + number_disk_next = s->cur_file_info.disk_num_start; + + if (number_disk_next != s->number_disk) + { + /* Switch disks */ + if ((s->filestream != NULL) && (s->filestream != s->filestream_with_CD)) + ZCLOSE64(s->z_filefunc, s->filestream); + + if (number_disk_next == s->gi.number_disk_with_CD) + { + s->filestream = s->filestream_with_CD; + } + else + { + s->filestream = ZOPENDISK64(s->z_filefunc, s->filestream_with_CD, number_disk_next, + ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING); + } + + if (s->filestream == NULL) + return UNZ_ERRNO; + + s->number_disk = number_disk_next; + } + + return UNZ_OK; +} + +extern int ZEXPORT unzGetGlobalInfo(unzFile file, unz_global_info* pglobal_info32) +{ + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + /* to do : check if number_entry is not truncated */ + pglobal_info32->number_entry = (uLong)s->gi.number_entry; + pglobal_info32->size_comment = s->gi.size_comment; + pglobal_info32->number_disk_with_CD = s->gi.number_disk_with_CD; + return UNZ_OK; +} + +extern int ZEXPORT unzGetGlobalInfo64(unzFile file, unz_global_info64* pglobal_info) +{ + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + *pglobal_info = s->gi; + return UNZ_OK; +} + +extern int ZEXPORT unzGetGlobalComment(unzFile file, char *comment, uLong comment_size) +{ + unz64_s* s; + uLong bytes_to_read = comment_size; + if (file == NULL) + return (int)UNZ_PARAMERROR; + s = (unz64_s*)file; + + if (bytes_to_read > s->gi.size_comment) + bytes_to_read = s->gi.size_comment; + + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, s->central_pos + 22, ZLIB_FILEFUNC_SEEK_SET) != 0) + return UNZ_ERRNO; + + if (bytes_to_read>0) + { + *comment = 0; + if (ZREAD64(s->z_filefunc, s->filestream_with_CD, comment, bytes_to_read) != bytes_to_read) + return UNZ_ERRNO; + } + + if ((comment != NULL) && (comment_size > s->gi.size_comment)) + *(comment+s->gi.size_comment) = 0; + return (int)bytes_to_read; +} + +/* Get Info about the current file in the zipfile, with internal only info */ +static int unz64local_GetCurrentFileInfoInternal(unzFile file, unz_file_info64 *pfile_info, + unz_file_info64_internal *pfile_info_internal, char *filename, uLong filename_size, void *extrafield, + uLong extrafield_size, char *comment, uLong comment_size) +{ + unz64_s* s; + unz_file_info64 file_info; + unz_file_info64_internal file_info_internal; + ZPOS64_T bytes_to_read; + int err = UNZ_OK; + uLong uMagic; + long lSeek = 0; + ZPOS64_T current_pos = 0; + uLong acc = 0; + uLong uL; + ZPOS64_T uL64; + + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, + s->pos_in_central_dir + s->byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) + err = UNZ_ERRNO; + + /* Check the magic */ + if (err == UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uMagic) != UNZ_OK) + err = UNZ_ERRNO; + else if (uMagic != CENTRALHEADERMAGIC) + err = UNZ_BADZIPFILE; + } + + /* Read central directory header */ + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.version) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.version_needed) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.flag) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.compression_method) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.dosDate) != UNZ_OK) + err = UNZ_ERRNO; + unz64local_DosDateToTmuDate(file_info.dosDate, &file_info.tmu_date); + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.crc) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + file_info.compressed_size = uL; + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + file_info.uncompressed_size = uL; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.size_filename) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.size_file_extra) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.size_file_comment) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.disk_num_start) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &file_info.internal_fa) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.external_fa) != UNZ_OK) + err = UNZ_ERRNO; + /* Relative offset of static header */ + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + + file_info.size_file_extra_internal = 0; + file_info.disk_offset = uL; + file_info_internal.offset_curfile = uL; +#ifdef HAVE_AES + file_info_internal.aes_compression_method = 0; + file_info_internal.aes_encryption_mode = 0; + file_info_internal.aes_version = 0; +#endif + + lSeek += file_info.size_filename; + + if ((err == UNZ_OK) && (filename != NULL)) + { + if (file_info.size_filename < filename_size) + { + *(filename+file_info.size_filename) = 0; + bytes_to_read = file_info.size_filename; + } + else + bytes_to_read = filename_size; + + if ((file_info.size_filename > 0) && (filename_size > 0)) + if (ZREAD64(s->z_filefunc, s->filestream_with_CD,filename, (uLong)bytes_to_read) != bytes_to_read) + err = UNZ_ERRNO; + lSeek -= (uLong)bytes_to_read; + } + + /* Read extrafield */ + if ((err == UNZ_OK) && (extrafield != NULL)) + { + if (file_info.size_file_extra < extrafield_size) + bytes_to_read = file_info.size_file_extra; + else + bytes_to_read = extrafield_size; + + if (lSeek != 0) + { + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, lSeek, ZLIB_FILEFUNC_SEEK_CUR) == 0) + lSeek=0; + else + err = UNZ_ERRNO; + } + + if ((file_info.size_file_extra > 0) && (extrafield_size > 0)) + if (ZREAD64(s->z_filefunc, s->filestream_with_CD, extrafield, (uLong)bytes_to_read) != bytes_to_read) + err = UNZ_ERRNO; + lSeek += file_info.size_file_extra - (uLong)bytes_to_read; + } + else + lSeek += file_info.size_file_extra; + + if ((err == UNZ_OK) && (file_info.size_file_extra != 0)) + { + if (lSeek != 0) + { + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, lSeek, ZLIB_FILEFUNC_SEEK_CUR) == 0) + lSeek=0; + else + err = UNZ_ERRNO; + } + + /* We are going to parse the extra field so we need to move back */ + current_pos = ZTELL64(s->z_filefunc, s->filestream_with_CD); + if (current_pos < file_info.size_file_extra) + err = UNZ_ERRNO; + current_pos -= file_info.size_file_extra; + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, current_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + err = UNZ_ERRNO; + + while((err != UNZ_ERRNO) && (acc < file_info.size_file_extra)) + { + uLong headerid; + uLong datasize; + + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &headerid) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &datasize) != UNZ_OK) + err = UNZ_ERRNO; + + /* ZIP64 extra fields */ + if (headerid == 0x0001) + { + /* Subtract size of ZIP64 field, since ZIP64 is handled internally */ + file_info.size_file_extra_internal += 2 + 2 + datasize; + + if (file_info.uncompressed_size == 0xffffffff) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream_with_CD, &file_info.uncompressed_size) != UNZ_OK) + err = UNZ_ERRNO; + } + if (file_info.compressed_size == 0xffffffff) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream_with_CD, &file_info.compressed_size) != UNZ_OK) + err = UNZ_ERRNO; + } + if (file_info_internal.offset_curfile == 0xffffffff) + { + /* Relative Header offset */ + if (unz64local_getLong64(&s->z_filefunc, s->filestream_with_CD, &uL64) != UNZ_OK) + err = UNZ_ERRNO; + file_info_internal.offset_curfile = uL64; + file_info.disk_offset = uL64; + } + if (file_info.disk_num_start == 0xffffffff) + { + /* Disk Start Number */ + if (unz64local_getLong(&s->z_filefunc, s->filestream_with_CD, &file_info.disk_num_start) != UNZ_OK) + err = UNZ_ERRNO; + } + } +#ifdef HAVE_AES + /* AES header */ + else if (headerid == 0x9901) + { + /* Subtract size of AES field, since AES is handled internally */ + file_info.size_file_extra_internal += 2 + 2 + datasize; + + /* Verify version info */ + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + /* Support AE-1 and AE-2 */ + if (uL != 1 && uL != 2) + err = UNZ_ERRNO; + file_info_internal.aes_version = uL; + if (unz64local_getByte(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + if ((char)uL != 'A') + err = UNZ_ERRNO; + if (unz64local_getByte(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + if ((char)uL != 'E') + err = UNZ_ERRNO; + /* Get AES encryption strength and actual compression method */ + if (unz64local_getByte(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + file_info_internal.aes_encryption_mode = uL; + if (unz64local_getShort(&s->z_filefunc, s->filestream_with_CD, &uL) != UNZ_OK) + err = UNZ_ERRNO; + file_info_internal.aes_compression_method = uL; + } +#endif + else + { + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD,datasize, ZLIB_FILEFUNC_SEEK_CUR) != 0) + err = UNZ_ERRNO; + } + + acc += 2 + 2 + datasize; + } + } + + if (file_info.disk_num_start == s->gi.number_disk_with_CD) + file_info_internal.byte_before_the_zipfile = s->byte_before_the_zipfile; + else + file_info_internal.byte_before_the_zipfile = 0; + + if ((err == UNZ_OK) && (comment != NULL)) + { + if (file_info.size_file_comment < comment_size) + { + *(comment+file_info.size_file_comment) = 0; + bytes_to_read = file_info.size_file_comment; + } + else + bytes_to_read = comment_size; + + if (lSeek != 0) + { + if (ZSEEK64(s->z_filefunc, s->filestream_with_CD, lSeek, ZLIB_FILEFUNC_SEEK_CUR) != 0) + err = UNZ_ERRNO; + } + + if ((file_info.size_file_comment > 0) && (comment_size > 0)) + if (ZREAD64(s->z_filefunc, s->filestream_with_CD, comment, (uLong)bytes_to_read) != bytes_to_read) + err = UNZ_ERRNO; + lSeek += file_info.size_file_comment - (uLong)bytes_to_read; + } + else + lSeek += file_info.size_file_comment; + + if ((err == UNZ_OK) && (pfile_info != NULL)) + *pfile_info = file_info; + + if ((err == UNZ_OK) && (pfile_info_internal != NULL)) + *pfile_info_internal = file_info_internal; + + return err; +} + +extern int ZEXPORT unzGetCurrentFileInfo(unzFile file, unz_file_info * pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char* comment, uLong comment_size) +{ + unz_file_info64 file_info64; + int err; + + err = unz64local_GetCurrentFileInfoInternal(file, &file_info64, NULL, filename, filename_size, + extrafield, extrafield_size, comment, comment_size); + + if ((err == UNZ_OK) && (pfile_info != NULL)) + { + pfile_info->version = file_info64.version; + pfile_info->version_needed = file_info64.version_needed; + pfile_info->flag = file_info64.flag; + pfile_info->compression_method = file_info64.compression_method; + pfile_info->dosDate = file_info64.dosDate; + pfile_info->crc = file_info64.crc; + + pfile_info->size_filename = file_info64.size_filename; + pfile_info->size_file_extra = file_info64.size_file_extra - file_info64.size_file_extra_internal; + pfile_info->size_file_comment = file_info64.size_file_comment; + + pfile_info->disk_num_start = file_info64.disk_num_start; + pfile_info->internal_fa = file_info64.internal_fa; + pfile_info->external_fa = file_info64.external_fa; + + pfile_info->tmu_date = file_info64.tmu_date, + + pfile_info->compressed_size = (uLong)file_info64.compressed_size; + pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size; + + } + return err; +} + +extern int ZEXPORT unzGetCurrentFileInfo64(unzFile file, unz_file_info64 * pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char* comment, uLong comment_size) +{ + return unz64local_GetCurrentFileInfoInternal(file, pfile_info, NULL, filename, filename_size, + extrafield, extrafield_size, comment,comment_size); +} + +/* Read the static header of the current zipfile. Check the coherency of the static header and info in the + end of central directory about this file store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) */ +static int unz64local_CheckCurrentFileCoherencyHeader(unz64_s* s, uInt* piSizeVar, ZPOS64_T *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic, uL, uFlags; + uLong size_filename; + uLong size_extra_field; + int err = UNZ_OK; + int compression_method = 0; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + err = unzGoToNextDisk((unzFile)s); + if (err != UNZ_OK) + return err; + + if (ZSEEK64(s->z_filefunc, s->filestream, s->cur_file_info_internal.offset_curfile + + s->cur_file_info_internal.byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) + return UNZ_ERRNO; + + if (err == UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream, &uMagic) != UNZ_OK) + err = UNZ_ERRNO; + else if (uMagic != LOCALHEADERMAGIC) + err = UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream, &uFlags) != UNZ_OK) + err = UNZ_ERRNO; + if (unz64local_getShort(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (uL != s->cur_file_info.compression_method)) + err = UNZ_BADZIPFILE; + + compression_method = (int)s->cur_file_info.compression_method; +#ifdef HAVE_AES + if (compression_method == AES_METHOD) + compression_method = (int)s->cur_file_info_internal.aes_compression_method; +#endif + + if ((err == UNZ_OK) && (compression_method != 0) && +#ifdef HAVE_BZIP2 + (compression_method != Z_BZIP2ED) && +#endif + (compression_method != Z_DEFLATED)) + err = UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* date/time */ + err = UNZ_ERRNO; + if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* crc */ + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (uL != s->cur_file_info.crc) && ((uFlags & 8) == 0)) + err = UNZ_BADZIPFILE; + if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* size compr */ + err = UNZ_ERRNO; + else if ((uL != 0xffffffff) && (err == UNZ_OK) && (uL != s->cur_file_info.compressed_size) && ((uFlags & 8) == 0)) + err = UNZ_BADZIPFILE; + if (unz64local_getLong(&s->z_filefunc, s->filestream, &uL) != UNZ_OK) /* size uncompr */ + err = UNZ_ERRNO; + else if ((uL != 0xffffffff) && (err == UNZ_OK) && (uL != s->cur_file_info.uncompressed_size) && ((uFlags & 8) == 0)) + err = UNZ_BADZIPFILE; + if (unz64local_getShort(&s->z_filefunc, s->filestream, &size_filename) != UNZ_OK) + err = UNZ_ERRNO; + else if ((err == UNZ_OK) && (size_filename != s->cur_file_info.size_filename)) + err = UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unz64local_getShort(&s->z_filefunc, s->filestream, &size_extra_field) != UNZ_OK) + err = UNZ_ERRNO; + *poffset_local_extrafield = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3(unzFile file, int* method, int* level, int raw, const char* password) +{ + int err = UNZ_OK; + int compression_method; + uInt iSizeVar; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + ZPOS64_T offset_local_extrafield; + uInt size_local_extrafield; +#ifndef NOUNCRYPT + char source[12]; +#else + if (password != NULL) + return UNZ_PARAMERROR; +#endif + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unz64local_CheckCurrentFileCoherencyHeader(s, &iSizeVar, &offset_local_extrafield, &size_local_extrafield) != UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s)); + if (pfile_in_zip_read_info == NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer = (Bytef*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield = 0; + pfile_in_zip_read_info->raw = raw; + + if (pfile_in_zip_read_info->read_buffer == NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised = 0; + + compression_method = (int)s->cur_file_info.compression_method; +#ifdef HAVE_AES + if (compression_method == AES_METHOD) + compression_method = (int)s->cur_file_info_internal.aes_compression_method; +#endif + + if (method != NULL) + *method = compression_method; + + if (level != NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((compression_method != 0) && +#ifdef HAVE_BZIP2 + (compression_method != Z_BZIP2ED) && +#endif + (compression_method != Z_DEFLATED)) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_BADZIPFILE; + } + + pfile_in_zip_read_info->crc32_wait = s->cur_file_info.crc; + pfile_in_zip_read_info->crc32 = 0; + pfile_in_zip_read_info->total_out_64 = 0; + pfile_in_zip_read_info->compression_method = compression_method; + pfile_in_zip_read_info->filestream = s->filestream; + pfile_in_zip_read_info->z_filefunc = s->z_filefunc; + if (s->number_disk == s->gi.number_disk_with_CD) + pfile_in_zip_read_info->byte_before_the_zipfile = s->byte_before_the_zipfile; + else + pfile_in_zip_read_info->byte_before_the_zipfile = 0; + pfile_in_zip_read_info->stream.total_out = 0; + pfile_in_zip_read_info->stream.total_in = 0; + pfile_in_zip_read_info->stream.next_in = NULL; + + if (!raw) + { + if (compression_method == Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0; + pfile_in_zip_read_info->bstream.bzfree = (free_func)0; + pfile_in_zip_read_info->bstream.opaque = (voidpf)0; + pfile_in_zip_read_info->bstream.state = (voidpf)0; + + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err = BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised = Z_BZIP2ED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } +#else + pfile_in_zip_read_info->raw = 1; +#endif + } + else if (compression_method == Z_DEFLATED) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)s; + pfile_in_zip_read_info->stream.next_in = 0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err = inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised = Z_DEFLATED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + } + + pfile_in_zip_read_info->rest_read_compressed = s->cur_file_info.compressed_size; + pfile_in_zip_read_info->rest_read_uncompressed = s->cur_file_info.uncompressed_size; + pfile_in_zip_read_info->pos_in_zipfile = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + iSizeVar; + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + +#ifndef NOUNCRYPT + s->pcrc_32_tab = NULL; + + if ((password != NULL) && ((s->cur_file_info.flag & 1) != 0)) + { + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET) != 0) + return UNZ_INTERNALERROR; +#ifdef HAVE_AES + if (s->cur_file_info.compression_method == AES_METHOD) + { + unsigned char passverify_archive[AES_PWVERIFYSIZE]; + unsigned char passverify_password[AES_PWVERIFYSIZE]; + unsigned char saltvalue[AES_MAXSALTLENGTH]; + uInt saltlength; + + if ((s->cur_file_info_internal.aes_encryption_mode < 1) || + (s->cur_file_info_internal.aes_encryption_mode > 3)) + return UNZ_INTERNALERROR; + + saltlength = SALT_LENGTH(s->cur_file_info_internal.aes_encryption_mode); + + if (ZREAD64(s->z_filefunc, s->filestream, saltvalue, saltlength) != saltlength) + return UNZ_INTERNALERROR; + if (ZREAD64(s->z_filefunc, s->filestream, passverify_archive, AES_PWVERIFYSIZE) != AES_PWVERIFYSIZE) + return UNZ_INTERNALERROR; + + fcrypt_init(s->cur_file_info_internal.aes_encryption_mode, password, strlen(password), saltvalue, + passverify_password, &s->pfile_in_zip_read->aes_ctx); + + if (memcmp(passverify_archive, passverify_password, AES_PWVERIFYSIZE) != 0) + return UNZ_BADPASSWORD; + + s->pfile_in_zip_read->rest_read_compressed -= saltlength + AES_PWVERIFYSIZE; + s->pfile_in_zip_read->rest_read_compressed -= AES_AUTHCODESIZE; + + s->pfile_in_zip_read->pos_in_zipfile += saltlength + AES_PWVERIFYSIZE; + } + else +#endif + { + int i; + s->pcrc_32_tab = (const unsigned int*)get_crc_table(); + init_keys(password, s->keys, s->pcrc_32_tab); + + if (ZREAD64(s->z_filefunc, s->filestream, source, 12) < 12) + return UNZ_INTERNALERROR; + + for (i = 0; i < 12; i++) + zdecode(s->keys, s->pcrc_32_tab, source[i]); + + s->pfile_in_zip_read->rest_read_compressed -= 12; + + s->pfile_in_zip_read->pos_in_zipfile += 12; + } + } +#endif + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile(unzFile file) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword(unzFile file, const char* password) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2(unzFile file, int* method, int* level, int raw) +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/* Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if some bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ +extern int ZEXPORT unzReadCurrentFile(unzFile file, voidp buf, unsigned len) +{ + int err = UNZ_OK; + uInt read = 0; + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + + if (s->pfile_in_zip_read == NULL) + return UNZ_PARAMERROR; + if (s->pfile_in_zip_read->read_buffer == NULL) + return UNZ_END_OF_LIST_OF_FILE; + if (len == 0) + return 0; + + s->pfile_in_zip_read->stream.next_out = (Bytef*)buf; + s->pfile_in_zip_read->stream.avail_out = (uInt)len; + + if (s->pfile_in_zip_read->raw) + { + if (len > s->pfile_in_zip_read->rest_read_compressed + s->pfile_in_zip_read->stream.avail_in) + s->pfile_in_zip_read->stream.avail_out = (uInt)s->pfile_in_zip_read->rest_read_compressed + + s->pfile_in_zip_read->stream.avail_in; + } + else + { + if (len > s->pfile_in_zip_read->rest_read_uncompressed) + s->pfile_in_zip_read->stream.avail_out = (uInt)s->pfile_in_zip_read->rest_read_uncompressed; + } + + while (s->pfile_in_zip_read->stream.avail_out > 0) + { + if (s->pfile_in_zip_read->stream.avail_in == 0) + { + uLong bytes_to_read = UNZ_BUFSIZE; + uLong bytes_not_read = 0; + uLong bytes_read = 0; + uLong total_bytes_read = 0; + + if (s->pfile_in_zip_read->stream.next_in != NULL) + bytes_not_read = s->pfile_in_zip_read->read_buffer + UNZ_BUFSIZE - + s->pfile_in_zip_read->stream.next_in; + bytes_to_read -= bytes_not_read; + if (bytes_not_read > 0) + memcpy(s->pfile_in_zip_read->read_buffer, s->pfile_in_zip_read->stream.next_in, bytes_not_read); + if (s->pfile_in_zip_read->rest_read_compressed < bytes_to_read) + bytes_to_read = (uInt)s->pfile_in_zip_read->rest_read_compressed; + + while (total_bytes_read != bytes_to_read) + { + if (ZSEEK64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream, + s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET) != 0) + return UNZ_ERRNO; + + bytes_read = ZREAD64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream, + s->pfile_in_zip_read->read_buffer + bytes_not_read + total_bytes_read, + bytes_to_read - total_bytes_read); + + total_bytes_read += bytes_read; + s->pfile_in_zip_read->pos_in_zipfile += bytes_read; + + if (bytes_read == 0) + { + if (ZERROR64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream)) + return UNZ_ERRNO; + + err = unzGoToNextDisk(file); + if (err != UNZ_OK) + return err; + + s->pfile_in_zip_read->pos_in_zipfile = 0; + s->pfile_in_zip_read->filestream = s->filestream; + } + } + +#ifndef NOUNCRYPT + if ((s->cur_file_info.flag & 1) != 0) + { +#ifdef HAVE_AES + if (s->cur_file_info.compression_method == AES_METHOD) + { + fcrypt_decrypt(s->pfile_in_zip_read->read_buffer, bytes_to_read, &s->pfile_in_zip_read->aes_ctx); + } + else +#endif + if (s->pcrc_32_tab != NULL) + { + uInt i; + for(i = 0; i < total_bytes_read; i++) + s->pfile_in_zip_read->read_buffer[i] = + zdecode(s->keys, s->pcrc_32_tab, s->pfile_in_zip_read->read_buffer[i]); + } + } +#endif + + s->pfile_in_zip_read->rest_read_compressed -= total_bytes_read; + s->pfile_in_zip_read->stream.next_in = (Bytef*)s->pfile_in_zip_read->read_buffer; + s->pfile_in_zip_read->stream.avail_in = (uInt)(bytes_not_read + total_bytes_read); + } + + if ((s->pfile_in_zip_read->compression_method == 0) || (s->pfile_in_zip_read->raw)) + { + uInt copy, i; + + if ((s->pfile_in_zip_read->stream.avail_in == 0) && + (s->pfile_in_zip_read->rest_read_compressed == 0)) + return (read == 0) ? UNZ_EOF : read; + + if (s->pfile_in_zip_read->stream.avail_out < s->pfile_in_zip_read->stream.avail_in) + copy = s->pfile_in_zip_read->stream.avail_out; + else + copy = s->pfile_in_zip_read->stream.avail_in; + + for (i = 0; i < copy; i++) + *(s->pfile_in_zip_read->stream.next_out+i) = + *(s->pfile_in_zip_read->stream.next_in+i); + + s->pfile_in_zip_read->total_out_64 = s->pfile_in_zip_read->total_out_64 + copy; + s->pfile_in_zip_read->rest_read_uncompressed -= copy; + s->pfile_in_zip_read->crc32 = crc32(s->pfile_in_zip_read->crc32, + s->pfile_in_zip_read->stream.next_out, copy); + + s->pfile_in_zip_read->stream.avail_in -= copy; + s->pfile_in_zip_read->stream.avail_out -= copy; + s->pfile_in_zip_read->stream.next_out += copy; + s->pfile_in_zip_read->stream.next_in += copy; + s->pfile_in_zip_read->stream.total_out += copy; + read += copy; + } + else if (s->pfile_in_zip_read->compression_method == Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + uLong total_out_before, total_out_after; + const Bytef *buf_before; + uLong out_bytes; + + s->pfile_in_zip_read->bstream.next_in = (char*)s->pfile_in_zip_read->stream.next_in; + s->pfile_in_zip_read->bstream.avail_in = s->pfile_in_zip_read->stream.avail_in; + s->pfile_in_zip_read->bstream.total_in_lo32 = (uInt)s->pfile_in_zip_read->stream.total_in; + s->pfile_in_zip_read->bstream.total_in_hi32 = s->pfile_in_zip_read->stream.total_in >> 32; + + s->pfile_in_zip_read->bstream.next_out = (char*)s->pfile_in_zip_read->stream.next_out; + s->pfile_in_zip_read->bstream.avail_out = s->pfile_in_zip_read->stream.avail_out; + s->pfile_in_zip_read->bstream.total_out_lo32 = (uInt)s->pfile_in_zip_read->stream.total_out; + s->pfile_in_zip_read->bstream.total_out_hi32 = s->pfile_in_zip_read->stream.total_out >> 32; + + total_out_before = s->pfile_in_zip_read->bstream.total_out_lo32 + + (((uLong)s->pfile_in_zip_read->bstream.total_out_hi32) << 32); + buf_before = (const Bytef *)s->pfile_in_zip_read->bstream.next_out; + + err = BZ2_bzDecompress(&s->pfile_in_zip_read->bstream); + + total_out_after = s->pfile_in_zip_read->bstream.total_out_lo32 + + (((uLong)s->pfile_in_zip_read->bstream.total_out_hi32) << 32); + + out_bytes = total_out_after-total_out_before; + + s->pfile_in_zip_read->total_out_64 = s->pfile_in_zip_read->total_out_64 + out_bytes; + s->pfile_in_zip_read->rest_read_uncompressed -= out_bytes; + s->pfile_in_zip_read->crc32 = crc32(s->pfile_in_zip_read->crc32,buf_before, (uInt)(out_bytes)); + + read += (uInt)(total_out_after - total_out_before); + + s->pfile_in_zip_read->stream.next_in = (Bytef*)s->pfile_in_zip_read->bstream.next_in; + s->pfile_in_zip_read->stream.avail_in = s->pfile_in_zip_read->bstream.avail_in; + s->pfile_in_zip_read->stream.total_in = s->pfile_in_zip_read->bstream.total_in_lo32; + s->pfile_in_zip_read->stream.next_out = (Bytef*)s->pfile_in_zip_read->bstream.next_out; + s->pfile_in_zip_read->stream.avail_out = s->pfile_in_zip_read->bstream.avail_out; + s->pfile_in_zip_read->stream.total_out = s->pfile_in_zip_read->bstream.total_out_lo32; + + if (err == BZ_STREAM_END) + return (read == 0) ? UNZ_EOF : read; + if (err != BZ_OK) + break; +#endif + } + else + { + ZPOS64_T total_out_before, total_out_after; + const Bytef *buf_before; + ZPOS64_T out_bytes; + int flush=Z_SYNC_FLUSH; + + total_out_before = s->pfile_in_zip_read->stream.total_out; + buf_before = s->pfile_in_zip_read->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err = inflate(&s->pfile_in_zip_read->stream,flush); + + if ((err >= 0) && (s->pfile_in_zip_read->stream.msg != NULL)) + err = Z_DATA_ERROR; + + total_out_after = s->pfile_in_zip_read->stream.total_out; + out_bytes = total_out_after-total_out_before; + + s->pfile_in_zip_read->total_out_64 += out_bytes; + s->pfile_in_zip_read->rest_read_uncompressed -= out_bytes; + s->pfile_in_zip_read->crc32 = + crc32(s->pfile_in_zip_read->crc32,buf_before, (uInt)(out_bytes)); + + read += (uInt)(total_out_after - total_out_before); + + if (err == Z_STREAM_END) + return (read == 0) ? UNZ_EOF : read; + if (err != Z_OK) + break; + } + } + + if (err == Z_OK) + return read; + return err; +} + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64(unzFile file) +{ + unz64_s* s; + s = (unz64_s*)file; + if (file == NULL) + return 0; /* UNZ_PARAMERROR */ + if (s->pfile_in_zip_read == NULL) + return 0; /* UNZ_PARAMERROR */ + return s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile; +} + +extern int ZEXPORT unzGetLocalExtrafield(unzFile file, voidp buf, unsigned len) +{ + unz64_s* s; + uInt read_now; + ZPOS64_T size_to_read; + + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (s->pfile_in_zip_read == NULL) + return UNZ_PARAMERROR; + + size_to_read = s->pfile_in_zip_read->size_local_extrafield - s->pfile_in_zip_read->pos_local_extrafield; + + if (buf == NULL) + return (int)size_to_read; + + if (len > size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now == 0) + return 0; + + if (ZSEEK64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream, + s->pfile_in_zip_read->offset_local_extrafield + s->pfile_in_zip_read->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET) != 0) + return UNZ_ERRNO; + + if (ZREAD64(s->pfile_in_zip_read->z_filefunc, s->pfile_in_zip_read->filestream, buf, read_now) != read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +extern int ZEXPORT unzCloseCurrentFile(unzFile file) +{ + int err = UNZ_OK; + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info == NULL) + return UNZ_PARAMERROR; + +#ifdef HAVE_AES + if (s->cur_file_info.compression_method == AES_METHOD) + { + unsigned char authcode[AES_AUTHCODESIZE]; + unsigned char rauthcode[AES_AUTHCODESIZE]; + + if (ZREAD64(s->z_filefunc, s->filestream, authcode, AES_AUTHCODESIZE) != AES_AUTHCODESIZE) + return UNZ_ERRNO; + + if (fcrypt_end(rauthcode, &s->pfile_in_zip_read->aes_ctx) != AES_AUTHCODESIZE) + err = UNZ_CRCERROR; + if (memcmp(authcode, rauthcode, AES_AUTHCODESIZE) != 0) + err = UNZ_CRCERROR; + } + /* AES zip version AE-1 will expect a valid crc as well */ + if ((s->cur_file_info.compression_method != AES_METHOD) || + (s->cur_file_info_internal.aes_version == 0x0001)) +#endif + { + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err = UNZ_CRCERROR; + } + } + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED) + inflateEnd(&pfile_in_zip_read_info->stream); +#ifdef HAVE_BZIP2 + else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED) + BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream); +#endif + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read = NULL; + + return err; +} + +extern int ZEXPORT unzGoToFirstFile2(unzFile file, unz_file_info64 *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size) +{ + int err = UNZ_OK; + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + s->pos_in_central_dir = s->offset_central_dir; + s->num_file = 0; + err = unz64local_GetCurrentFileInfoInternal(file, &s->cur_file_info, &s->cur_file_info_internal, + filename,filename_size, extrafield,extrafield_size, comment,comment_size); + s->current_file_ok = (err == UNZ_OK); + if ((err == UNZ_OK) && (pfile_info != NULL)) + memcpy(pfile_info, &s->cur_file_info, sizeof(unz_file_info64)); + return err; +} + +extern int ZEXPORT unzGoToFirstFile(unzFile file) +{ + return unzGoToFirstFile2(file, NULL, NULL, 0, NULL, 0, NULL, 0); +} + +extern int ZEXPORT unzGoToNextFile2(unzFile file, unz_file_info64 *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size) +{ + unz64_s* s; + int err; + + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1 == s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment; + s->num_file++; + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, + filename, filename_size, extrafield,extrafield_size, comment,comment_size); + s->current_file_ok = (err == UNZ_OK); + if ((err == UNZ_OK) && (pfile_info != NULL)) + memcpy(pfile_info, &s->cur_file_info, sizeof(unz_file_info64)); + return err; +} + +extern int ZEXPORT unzGoToNextFile(unzFile file) +{ + return unzGoToNextFile2(file, NULL, NULL, 0, NULL, 0, NULL, 0); +} + +extern int ZEXPORT unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func) +{ + unz64_s* s; + int err; + unz_file_info64 cur_file_info_saved; + unz_file_info64_internal cur_file_info_internal_saved; + ZPOS64_T num_file_saved; + ZPOS64_T pos_in_central_dir_saved; + char current_filename[UNZ_MAXFILENAMEINZIP+1]; + + if (file == NULL) + return UNZ_PARAMERROR; + if (strlen(filename) >= UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_file_saved = s->num_file; + pos_in_central_dir_saved = s->pos_in_central_dir; + cur_file_info_saved = s->cur_file_info; + cur_file_info_internal_saved = s->cur_file_info_internal; + + err = unzGoToFirstFile2(file, NULL, current_filename, sizeof(current_filename)-1, NULL, 0, NULL, 0); + + while (err == UNZ_OK) + { + if (filename_compare_func != NULL) + err = filename_compare_func(file, current_filename, filename); + else + err = strcmp(current_filename, filename); + if (err == 0) + return UNZ_OK; + err = unzGoToNextFile2(file, NULL, current_filename, sizeof(current_filename)-1, NULL, 0, NULL, 0); + } + + /* We failed, so restore the state of the 'current file' to where we were. */ + s->num_file = num_file_saved; + s->pos_in_central_dir = pos_in_central_dir_saved; + s->cur_file_info = cur_file_info_saved; + s->cur_file_info_internal = cur_file_info_internal_saved; + return err; +} + +extern int ZEXPORT unzGetFilePos(unzFile file, unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + int err = unzGetFilePos64(file,&file_pos64); + if (err == UNZ_OK) + { + file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory; + file_pos->num_of_file = (uLong)file_pos64.num_of_file; + } + return err; +} + +extern int ZEXPORT unzGoToFilePos(unzFile file, unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + + if (file_pos == NULL) + return UNZ_PARAMERROR; + file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; + file_pos64.num_of_file = file_pos->num_of_file; + return unzGoToFilePos64(file,&file_pos64); +} + +extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos) +{ + unz64_s* s; + + if (file == NULL || file_pos == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos) +{ + unz64_s* s; + int err; + + if (file == NULL || file_pos == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal,NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern uLong ZEXPORT unzGetOffset(unzFile file) +{ + ZPOS64_T offset64; + + if (file == NULL) + return 0; /* UNZ_PARAMERROR; */ + offset64 = unzGetOffset64(file); + return (uLong)offset64; +} + +extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file) +{ + unz64_s* s; + + if (file == NULL) + return 0; /* UNZ_PARAMERROR; */ + s = (unz64_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file == s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern int ZEXPORT unzSetOffset(unzFile file, uLong pos) +{ + return unzSetOffset64(file, pos); +} + +extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos) +{ + unz64_s* s; + int err; + + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + + err = unz64local_GetCurrentFileInfoInternal(file, &s->cur_file_info, &s->cur_file_info_internal, NULL, 0, NULL, 0, NULL, 0); + + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern z_off_t ZEXPORT unztell(unzFile file) +{ + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (s->pfile_in_zip_read == NULL) + return UNZ_PARAMERROR; + return (z_off_t)s->pfile_in_zip_read->stream.total_out; +} + +extern ZPOS64_T ZEXPORT unztell64(unzFile file) +{ + unz64_s* s; + if (file == NULL) + return (ZPOS64_T)-1; + s = (unz64_s*)file; + if (s->pfile_in_zip_read == NULL) + return (ZPOS64_T)-1; + return s->pfile_in_zip_read->total_out_64; +} + +extern int ZEXPORT unzseek(unzFile file, z_off_t offset, int origin) +{ + return unzseek64(file, (ZPOS64_T)offset, origin); +} + +extern int ZEXPORT unzseek64(unzFile file, ZPOS64_T offset, int origin) +{ + unz64_s* s; + ZPOS64_T stream_pos_begin; + ZPOS64_T stream_pos_end; + int isWithinBuffer; + ZPOS64_T position; + + if (file == NULL) + return UNZ_PARAMERROR; + + s = (unz64_s*)file; + + if (s->pfile_in_zip_read == NULL) + return UNZ_ERRNO; + if (s->pfile_in_zip_read->compression_method != 0) + return UNZ_ERRNO; + + if (origin == SEEK_SET) + position = offset; + else if (origin == SEEK_CUR) + position = s->pfile_in_zip_read->total_out_64 + offset; + else if (origin == SEEK_END) + position = s->cur_file_info.compressed_size + offset; + else + return UNZ_PARAMERROR; + + if (position > s->cur_file_info.compressed_size) + return UNZ_PARAMERROR; + + stream_pos_end = s->pfile_in_zip_read->pos_in_zipfile; + stream_pos_begin = stream_pos_end; + + if (stream_pos_begin > UNZ_BUFSIZE) + stream_pos_begin -= UNZ_BUFSIZE; + else + stream_pos_begin = 0; + + isWithinBuffer = s->pfile_in_zip_read->stream.avail_in != 0 && + (s->pfile_in_zip_read->rest_read_compressed != 0 || s->cur_file_info.compressed_size < UNZ_BUFSIZE) && + position >= stream_pos_begin && position < stream_pos_end; + + if (isWithinBuffer) + { + s->pfile_in_zip_read->stream.next_in += position - s->pfile_in_zip_read->total_out_64; + s->pfile_in_zip_read->stream.avail_in = (uInt)(stream_pos_end - position); + } + else + { + s->pfile_in_zip_read->stream.avail_in = 0; + s->pfile_in_zip_read->stream.next_in = 0; + + s->pfile_in_zip_read->pos_in_zipfile = s->pfile_in_zip_read->offset_local_extrafield + position; + s->pfile_in_zip_read->rest_read_compressed = s->cur_file_info.compressed_size - position; + } + + s->pfile_in_zip_read->rest_read_uncompressed -= (position - s->pfile_in_zip_read->total_out_64); + s->pfile_in_zip_read->stream.total_out = (uLong)position; + s->pfile_in_zip_read->total_out_64 = position; + + return UNZ_OK; +} + +extern int ZEXPORT unzeof(unzFile file) +{ + unz64_s* s; + if (file == NULL) + return UNZ_PARAMERROR; + s = (unz64_s*)file; + if (s->pfile_in_zip_read == NULL) + return UNZ_PARAMERROR; + if (s->pfile_in_zip_read->rest_read_uncompressed == 0) + return 1; + return 0; +} diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h new file mode 100644 index 0000000..09e930a --- /dev/null +++ b/src/qcommon/unzip.h @@ -0,0 +1,321 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project + + Copyright (C) 1998-2010 Gilles Vollant + http://www.winimage.com/zLibDll/minizip.html + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson + http://result42.com + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef _UNZ_H +#define _UNZ_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zconf.h" +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) +#define UNZ_BADPASSWORD (-106) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info64_s +{ + ZPOS64_T number_entry; /* total number of entries in the central dir on this disk */ + uLong number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info64; + +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in the central dir on this disk */ + uLong number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info64_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + ZPOS64_T compressed_size; /* compressed size 8 bytes */ + ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; + ZPOS64_T disk_offset; + uLong size_file_extra_internal; +} unz_file_info64; + +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; + uLong disk_offset; +} unz_file_info; + +/***************************************************************************/ +/* Opening and close a zip file */ + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +extern unzFile ZEXPORT unzOpen64 OF((const void *path)); +/* Open a Zip file. + + path should contain the full pathname (by example, on a Windows XP computer + "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip". + return NULL if zipfile cannot be opened or doesn't exist + return unzFile handle if no error + + NOTE: The "64" function take a const void* pointer, because the path is just the value passed to the + open64_file_func callback. Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path + is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* does not describe the reality */ + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, zlib_filefunc_def* pzlib_filefunc_def)); +/* Open a Zip file, like unzOpen, but provide a set of file low level API for read/write operations */ +extern unzFile ZEXPORT unzOpen2_64 OF((const void *path, zlib_filefunc64_def* pzlib_filefunc_def)); +/* Open a Zip file, like unz64Open, but provide a set of file low level API for read/write 64-bit operations */ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* Close a ZipFile opened with unzOpen. If there is files inside the .Zip opened with unzOpenCurrentFile, + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + + return UNZ_OK if there is no error */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, unz_global_info *pglobal_info)); +extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, unz_global_info64 *pglobal_info)); +/* Write info about the ZipFile in the *pglobal_info structure. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, char *comment, uLong comment_size)); +/* Get the global comment string of the ZipFile, in the comment buffer. + + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 */ + +/***************************************************************************/ +/* Reading the content of the current zipfile, you can open it, read data from it, and close it + (you can close it before reading all the file) */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* Open for reading data the current file in the zipfile. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, const char* password)); +/* Open for reading data the current file in the zipfile. + password is a crypting password + + return UNZ_OK if no error */ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, int* method, int* level, int raw)); +/* Same as unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 *method will receive method of compression, *level will receive level of compression + + NOTE: you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL */ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, int* method, int* level, int raw, const char* password)); +/* Same as unzOpenCurrentFile, but takes extra parameter password for encrypted files */ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, voidp buf, unsigned len)); +/* Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, unz_file_info *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size)); +extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, unz_file_info64 *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size)); +/* Get Info about the current file + + pfile_info if != NULL, the *pfile_info structure will contain somes info about the current file + filename if != NULL, the file name string will be copied in filename + filename_size is the size of the filename buffer + extrafield if != NULL, the extra field information from the central header will be copied in to + extrafield_size is the size of the extraField buffer + comment if != NULL, the comment string of the file will be copied in to + comment_size is the size of the comment buffer */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file)); + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, voidp buf, unsigned len)); +/* Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf == NULL, it return the size of the local extra field + if buf != NULL, len is the size of the buffer, the extra header is copied in buf. + + return number of bytes copied in buf, or (if <0) the error code */ + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* Close the file in zip opened with unzOpenCurrentFile + + return UNZ_CRCERROR if all the file was read but the CRC is not good */ + +/***************************************************************************/ +/* Browse the directory of the zipfile */ + +typedef int (*unzFileNameComparer)(unzFile file, const char *filename1, const char *filename2); +typedef int (*unzIteratorFunction)(unzFile file); +typedef int (*unzIteratorFunction2)(unzFile file, unz_file_info64 *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size); + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* Set the current file of the zipfile to the first file. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzGoToFirstFile2 OF((unzFile file, unz_file_info64 *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size)); +/* Set the current file of the zipfile to the first file and retrieves the current info on success. + Not as seek intensive as unzGoToFirstFile + unzGetCurrentFileInfo. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* Set the current file of the zipfile to the next file. + + return UNZ_OK if no error + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */ + +extern int ZEXPORT unzGoToNextFile2 OF((unzFile file, unz_file_info64 *pfile_info, char *filename, + uLong filename_size, void *extrafield, uLong extrafield_size, char *comment, uLong comment_size)); +/* Set the current file of the zipfile to the next file and retrieves the current + info on success. Does less seeking around than unzGotoNextFile + unzGetCurrentFileInfo. + + return UNZ_OK if no error + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */ + +extern int ZEXPORT unzLocateFile OF((unzFile file, const char *filename, unzFileNameComparer filename_compare_func)); +/* Try locate the file szFileName in the zipfile. For custom filename comparison pass in comparison function. + + return UNZ_OK if the file is found (it becomes the current file) + return UNZ_END_OF_LIST_OF_FILE if the file is not found */ + +/***************************************************************************/ +/* Raw access to zip file */ + +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos OF((unzFile file, unz_file_pos* file_pos)); +extern int ZEXPORT unzGoToFilePos OF((unzFile file, unz_file_pos* file_pos)); + +typedef struct unz64_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */ + ZPOS64_T num_of_file; /* # of file */ +} unz64_file_pos; + +extern int ZEXPORT unzGetFilePos64 OF((unzFile file, unz64_file_pos* file_pos)); +extern int ZEXPORT unzGoToFilePos64 OF((unzFile file, const unz64_file_pos* file_pos)); + +extern uLong ZEXPORT unzGetOffset OF((unzFile file)); +extern ZPOS64_T ZEXPORT unzGetOffset64 OF((unzFile file)); +/* Get the current file offset */ + +extern int ZEXPORT unzSetOffset OF((unzFile file, uLong pos)); +extern int ZEXPORT unzSetOffset64 OF((unzFile file, ZPOS64_T pos)); +/* Set the current file offset */ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); +extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file)); +/* return current position in uncompressed data */ + +extern int ZEXPORT unzseek OF((unzFile file, z_off_t offset, int origin)); +extern int ZEXPORT unzseek64 OF((unzFile file, ZPOS64_T offset, int origin)); +/* Seek within the uncompressed data if compression method is storage */ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* return 1 if the end of file was reached, 0 elsewhere */ + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* _UNZ_H */ diff --git a/src/qcommon/vm.cpp b/src/qcommon/vm.cpp new file mode 100644 index 0000000..ab36a33 --- /dev/null +++ b/src/qcommon/vm.cpp @@ -0,0 +1,1020 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// vm.c -- virtual machine + +/* + + +intermix code and data +symbol table + +a dll has one imported function: VM_SystemCall +and one exported function: Perform + + +*/ + +#include "vm.h" +#include "vm_local.h" + +#include "sys/sys_shared.h" + +#include "cmd.h" +#include "cvar.h" +#include "files.h" + +vm_t *currentVM = NULL; +vm_t *lastVM = NULL; +int vm_debugLevel; + +// used by Com_Error to get rid of running vm's before longjmp +static int forced_unload; + +#define MAX_VM 3 +vm_t vmTable[MAX_VM]; + + +void VM_VmInfo_f( void ); +void VM_VmProfile_f( void ); + + + +#if 0 // 64bit! +// converts a VM pointer to a C pointer and +// checks to make sure that the range is acceptable +void *VM_VM2C( vmptr_t p, int length ) { + return (void *)p; +} +#endif + +void VM_Debug( int level ) { + vm_debugLevel = level; +} + +/* +============== +VM_Init +============== +*/ +void VM_Init( void ) { + Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 + Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 + Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 + + Cmd_AddCommand ("vmprofile", VM_VmProfile_f ); + Cmd_AddCommand ("vminfo", VM_VmInfo_f ); + + ::memset( vmTable, 0, sizeof( vmTable ) ); +} + + +/* +=============== +VM_ValueToSymbol + +Assumes a program counter value +=============== +*/ +const char *VM_ValueToSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static char text[MAX_TOKEN_CHARS]; + + sym = vm->symbols; + if ( !sym ) { + return "NO SYMBOLS"; + } + + // find the symbol + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } + + if ( value == sym->symValue ) { + return sym->symName; + } + + Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); + + return text; +} + +/* +=============== +VM_ValueToFunctionSymbol + +For profiling, find the symbol behind this value +=============== +*/ +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static vmSymbol_t nullSym; + + sym = vm->symbols; + if ( !sym ) { + return &nullSym; + } + + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } + + return sym; +} + + +/* +=============== +VM_SymbolToValue +=============== +*/ +int VM_SymbolToValue( vm_t *vm, const char *symbol ) { + vmSymbol_t *sym; + + for ( sym = vm->symbols ; sym ; sym = sym->next ) { + if ( !strcmp( symbol, sym->symName ) ) { + return sym->symValue; + } + } + return 0; +} + + +/* +===================== +VM_SymbolForCompiledPointer +===================== +*/ +#if 0 // 64bit! +const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) { + int i; + + if ( code < (void *)vm->codeBase ) { + return "Before code block"; + } + if ( code >= (void *)(vm->codeBase + vm->codeLength) ) { + return "After code block"; + } + + // find which original instruction it is after + for ( i = 0 ; i < vm->codeLength ; i++ ) { + if ( (void *)vm->instructionPointers[i] > code ) { + break; + } + } + i--; + + // now look up the bytecode instruction pointer + return VM_ValueToSymbol( vm, i ); +} +#endif + + + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} + +/* +=============== +VM_LoadSymbols +=============== +*/ +void VM_LoadSymbols( vm_t *vm ) { + union { + char *c; + void *v; + } mapfile; + char *text_p, *token; + char name[MAX_QPATH]; + char symbols[MAX_QPATH]; + vmSymbol_t **prev, *sym; + int count; + int value; + int chars; + int segment; + int numInstructions; + + // don't load symbols if not developer + if ( !com_developer->integer ) { + return; + } + + COM_StripExtension(vm->name, name, sizeof(name)); + Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name ); + FS_ReadFile( symbols, &mapfile.v ); + if ( !mapfile.c ) { + Com_Printf( "Couldn't load symbol file: %s\n", symbols ); + return; + } + + numInstructions = vm->instructionCount; + + // parse the symbols + text_p = mapfile.c; + prev = &vm->symbols; + count = 0; + + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + segment = ParseHex( token ); + if ( segment ) { + COM_Parse( &text_p ); + COM_Parse( &text_p ); + continue; // only load code segment values + } + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + value = ParseHex( token ); + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + chars = strlen( token ); + sym = (vmSymbol_t*)Hunk_Alloc( sizeof( *sym ) + chars, h_high ); + *prev = sym; + prev = &sym->next; + sym->next = NULL; + + // convert value from an instruction number to a code offset + if ( value >= 0 && value < numInstructions ) { + value = vm->instructionPointers[value]; + } + + sym->symValue = value; + Q_strncpyz( sym->symName, token, chars + 1 ); + + count++; + } + + vm->numSymbols = count; + Com_Printf( "%i symbols parsed from %s\n", count, symbols ); + FS_FreeFile( mapfile.v ); +} + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + + The syscall mechanism relies on stack manipulation to get its args. + This is likely due to C's inability to pass "..." parameters to + a function in one clean chunk. On PowerPC Linux, these parameters + are not necessarily passed on the stack, so while (&arg[0] == arg) + is true, (&arg[1] == 2nd function parameter) is not necessarily + accurate, as arg's value might have been stored to the stack or + other piece of scratch memory to give it a valid address, but the + next parameter might still be sitting in a register. + + Quake's syscall system also assumes that the stack grows downward, + and that any needed types can be squeezed, safely, into a signed int. + + This hack below copies all needed values for an argument to a + array in memory, so that Quake can get the correct values. This can + also be used on systems where the stack grows upwards, as the + presumably standard and safe stdargs.h macros are used. + + As for having enough space in a signed int for your datatypes, well, + it might be better to wait for DOOM 3 before you start porting. :) + + The original code, while probably still inherently dangerous, seems + to work well enough for the platforms it already works on. Rather + than add the performance hit for those platforms, the original code + is still in use there. + + For speed, we just grab 15 arguments, and don't worry about exactly + how many the syscall actually needs; the extra is thrown away. + +============ +*/ +__attribute__((no_sanitize_address)) +intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) { +#if !id386 || defined __clang__ + // rcg010206 - see commentary above + intptr_t args[MAX_VMSYSCALL_ARGS]; + int i; + va_list ap; + + args[0] = arg; + + va_start(ap, arg); + for (i = 1; i < ARRAY_LEN(args); i++) + args[i] = va_arg(ap, intptr_t); + va_end(ap); + + return currentVM->systemCall( args ); +#else // original id code + return currentVM->systemCall( &arg ); +#endif +} + + +/* +================= +VM_LoadQVM + +Load a .qvm file +================= +*/ +vmHeader_t *VM_LoadQVM( vm_t *vm, bool alloc, bool unpure) +{ + int dataLength; + int i; + char filename[MAX_QPATH]; + union { + vmHeader_t *h; + void *v; + } header; + + // load the image + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s...\n", filename ); + + FS_ReadFileDir(filename, vm->searchPath, unpure, &header.v); + + if ( !header.h ) { + Com_Printf( "Failed.\n" ); + VM_Free( vm ); + + Com_Printf(S_COLOR_YELLOW "Warning: Couldn't open VM file %s\n", filename); + + return NULL; + } + + // show where the qvm was loaded from + FS_Which(filename, vm->searchPath); + + if( LittleLong( header.h->vmMagic ) == VM_MAGIC_VER2 ) { + Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" ); + + // byte swap the header + for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) { + ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] ); + } + + // validate + if ( header.h->jtrgLength < 0 + || header.h->bssLength < 0 + || header.h->dataLength < 0 + || header.h->litLength < 0 + || header.h->codeLength <= 0 ) + { + VM_Free(vm); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename); + return NULL; + } + } else if( LittleLong( header.h->vmMagic ) == VM_MAGIC ) { + // byte swap the header + // sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size + for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) { + ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] ); + } + + // validate + if ( header.h->bssLength < 0 + || header.h->dataLength < 0 + || header.h->litLength < 0 + || header.h->codeLength <= 0 ) + { + VM_Free(vm); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename); + return NULL; + } + } else { + VM_Free( vm ); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: %s does not have a recognisable " + "magic number in its header\n", filename); + return NULL; + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header.h->dataLength + header.h->litLength + + header.h->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + if(alloc) + { + // allocate zero filled space for initialized and uninitialized data + vm->dataBase = (unsigned char*)Hunk_Alloc(dataLength, h_high); + vm->dataMask = dataLength - 1; + } + else + { + // clear the data, but make sure we're not clearing more than allocated + if(vm->dataMask + 1 != dataLength) + { + VM_Free(vm); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: Data region size of %s not matching after " + "VM_Restart()\n", filename); + return NULL; + } + + ::memset(vm->dataBase, 0, dataLength); + } + + // copy the intialized data + ::memcpy( vm->dataBase, (byte *)header.h + header.h->dataOffset, + header.h->dataLength + header.h->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header.h->dataLength ; i += 4 ) { + *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); + } + + if(header.h->vmMagic == VM_MAGIC_VER2) + { + int previousNumJumpTableTargets = vm->numJumpTableTargets; + + header.h->jtrgLength &= ~0x03; + + vm->numJumpTableTargets = header.h->jtrgLength >> 2; + Com_Printf("Loading %d jump table targets\n", vm->numJumpTableTargets); + + if(alloc) + { + vm->jumpTableTargets = (unsigned char*)Hunk_Alloc(header.h->jtrgLength, h_high); + } + else + { + if(vm->numJumpTableTargets != previousNumJumpTableTargets) + { + VM_Free(vm); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: Jump table size of %s not matching after " + "VM_Restart()\n", filename); + return NULL; + } + + ::memset(vm->jumpTableTargets, 0, header.h->jtrgLength); + } + + ::memcpy(vm->jumpTableTargets, (byte *) header.h + header.h->dataOffset + + header.h->dataLength + header.h->litLength, header.h->jtrgLength); + + // byte swap the longs + for ( i = 0 ; i < header.h->jtrgLength ; i += 4 ) { + *(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) ); + } + } + + return header.h; +} + +/* +================= +VM_Restart + +Reload the data, but leave everything else in place +This allows a server to do a map_restart without changing memory allocation + +We need to make sure that servers can access unpure QVMs (not contained in any pak) +even if the client is pure, so take "unpure" as argument. +================= +*/ +vm_t *VM_Restart(vm_t *vm, bool unpure) +{ + vmHeader_t *header; + + // DLL's can't be restarted in place + if ( vm->dllHandle ) { + char name[MAX_QPATH]; + intptr_t (*systemCall)( intptr_t *parms ); + + systemCall = vm->systemCall; + Q_strncpyz( name, vm->name, sizeof( name ) ); + + VM_Free( vm ); + + vm = VM_Create( name, systemCall, VMI_NATIVE ); + return vm; + } + + // load the image + Com_Printf("VM_Restart()\n"); + + if(!(header = VM_LoadQVM(vm, false, unpure))) + { + Com_Error(ERR_DROP, "VM_Restart failed"); + return NULL; + } + + // free the original file + FS_FreeFile(header); + + return vm; +} + +/* +================ +VM_Create + +If image ends in .qvm it will be interpreted, otherwise +it will attempt to load as a system dll +================ +*/ +vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *), + vmInterpret_t interpret ) { + vm_t *vm; + vmHeader_t *header; + int i, remaining, retval; + char filename[MAX_OSPATH]; + void *startSearch = NULL; + + if ( !module || !module[0] || !systemCalls ) { + Com_Error( ERR_FATAL, "VM_Create: bad parms" ); + } + + remaining = Hunk_MemoryRemaining(); + + // see if we already have the VM + for ( i = 0 ; i < MAX_VM ; i++ ) { + if (!Q_stricmp(vmTable[i].name, module)) { + vm = &vmTable[i]; + return vm; + } + } + + // find a free vm + for ( i = 0 ; i < MAX_VM ; i++ ) { + if ( !vmTable[i].name[0] ) { + break; + } + } + + if ( i == MAX_VM ) { + Com_Error( ERR_FATAL, "VM_Create: no free vm_t" ); + } + + vm = &vmTable[i]; + + Q_strncpyz(vm->name, module, sizeof(vm->name)); + + do + { + retval = FS_FindVM(&startSearch, filename, sizeof(filename), module, (interpret == VMI_NATIVE)); + + if(retval == VMI_NATIVE) + { + Com_Printf("Try loading dll file %s\n", filename); + + vm->dllHandle = Sys_LoadGameDll(filename, &vm->entryPoint, VM_DllSyscall); + + if(vm->dllHandle) + { + vm->systemCall = systemCalls; + return vm; + } + + Com_Printf("Failed loading dll, trying next\n"); + } + else if(retval == VMI_COMPILED) + { + vm->searchPath = startSearch; + if((header = VM_LoadQVM(vm, true, false))) + break; + + // VM_Free overwrites the name on failed load + Q_strncpyz(vm->name, module, sizeof(vm->name)); + } + } while(retval >= 0); + + if(retval < 0) + return NULL; + + vm->systemCall = systemCalls; + + // allocate space for the jump targets, which will be filled in by the compile/prep functions + vm->instructionCount = header->instructionCount; + vm->instructionPointers = (intptr_t*)Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high); + + // copy or compile the instructions + vm->codeLength = header->codeLength; + + vm->compiled = false; + +#ifdef NO_VM_COMPILED + if(interpret >= VMI_COMPILED) { + Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n"); + interpret = VMI_BYTECODE; + } +#else + if(interpret != VMI_BYTECODE) + { + vm->compiled = true; + VM_Compile( vm, header ); + } +#endif + // VM_Compile may have reset vm->compiled if compilation failed + if (!vm->compiled) + { + VM_PrepareInterpreter( vm, header ); + } + + // free the original file + FS_FreeFile( header ); + + // load the map file + VM_LoadSymbols( vm ); + + // the stack is implicitly at the end of the image + vm->programStack = vm->dataMask + 1; + vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE; + + Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining()); + + return vm; +} + +/* +============== +VM_Free +============== +*/ +void VM_Free( vm_t *vm ) { + + if(!vm) { + return; + } + + if(vm->callLevel) { + if(!forced_unload) { + Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name ); + return; + } else { + Com_Printf( "forcefully unloading %s vm\n", vm->name ); + } + } + + if(vm->destroy) + vm->destroy(vm); + + if ( vm->dllHandle ) { + Sys_UnloadDll( vm->dllHandle ); + ::memset( vm, 0, sizeof( *vm ) ); + } +#if 0 // now automatically freed by hunk + if ( vm->codeBase ) { + Z_Free( vm->codeBase ); + } + if ( vm->dataBase ) { + Z_Free( vm->dataBase ); + } + if ( vm->instructionPointers ) { + Z_Free( vm->instructionPointers ); + } +#endif + ::memset( vm, 0, sizeof( *vm ) ); + + currentVM = NULL; + lastVM = NULL; +} + +void VM_Clear(void) { + int i; + for (i=0;icallLevel = 0; +} + +void *VM_ArgPtr( intptr_t intValue ) { + if ( !intValue ) { + return NULL; + } + // currentVM is missing on reconnect + if ( currentVM==NULL ) + return NULL; + + if ( currentVM->entryPoint ) { + return (void *)(currentVM->dataBase + intValue); + } + else { + return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); + } +} + +void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) { + if ( !intValue ) { + return NULL; + } + + // currentVM is missing on reconnect here as well? + if ( currentVM==NULL ) + return NULL; + + // + if ( vm->entryPoint ) { + return (void *)(vm->dataBase + intValue); + } + else { + return (void *)(vm->dataBase + (intValue & vm->dataMask)); + } +} + + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return value +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +__attribute__((no_sanitize_address)) +intptr_t QDECL VM_Call( vm_t *vm, int callnum, ... ) +{ + vm_t *oldVM; + intptr_t r; + + if(!vm || !vm->name[0]) + Com_Error(ERR_FATAL, "VM_Call with NULL vm"); + + oldVM = currentVM; + currentVM = vm; + lastVM = vm; + + if ( vm_debugLevel ) { + Com_Printf( "VM_Call( %d )\n", callnum ); + } + + ++vm->callLevel; + // if we have a dll loaded, call it directly + if ( vm->entryPoint ) { + //rcg010207 - see dissertation at top of VM_DllSyscall() in this file. + int args[MAX_VMMAIN_ARGS-1]; + va_list ap; + va_start(ap, callnum); + for (unsigned i = 0; i < ARRAY_LEN(args); i++) + { + args[i] = va_arg(ap, int); + } + va_end(ap); + + r = vm->entryPoint( callnum, args[0], args[1], args[2] ); + } else { +#if ( id386 || idsparc ) && !defined __clang__ // calling convention doesn't need conversion in some cases +#ifndef NO_VM_COMPILED + if ( vm->compiled ) + r = VM_CallCompiled( vm, (int*)&callnum ); + else +#endif + r = VM_CallInterpreted( vm, (int*)&callnum ); +#else + struct { + int callnum; + int args[MAX_VMMAIN_ARGS-1]; + } a; + va_list ap; + + a.callnum = callnum; + va_start(ap, callnum); + for (unsigned i = 0; i < ARRAY_LEN(a.args); i++) + { + a.args[i] = va_arg(ap, int); + } + va_end(ap); +#ifndef NO_VM_COMPILED + if ( vm->compiled ) + r = VM_CallCompiled( vm, &a.callnum ); + else +#endif + r = VM_CallInterpreted( vm, &a.callnum ); +#endif + } + --vm->callLevel; + + if ( oldVM != NULL ) + currentVM = oldVM; + return r; +} + +//================================================================= + +static int QDECL VM_ProfileSort( const void *a, const void *b ) { + vmSymbol_t *sa, *sb; + + sa = *(vmSymbol_t **)a; + sb = *(vmSymbol_t **)b; + + if ( sa->profileCount < sb->profileCount ) { + return -1; + } + if ( sa->profileCount > sb->profileCount ) { + return 1; + } + return 0; +} + +/* +============== +VM_VmProfile_f + +============== +*/ +void VM_VmProfile_f( void ) { + vm_t *vm; + vmSymbol_t **sorted, *sym; + int i; + double total; + + if ( !lastVM ) { + return; + } + + vm = lastVM; + + if ( !vm->numSymbols ) { + return; + } + + sorted = (vmSymbol_t**)Z_Malloc( vm->numSymbols * sizeof( *sorted ) ); + sorted[0] = vm->symbols; + total = sorted[0]->profileCount; + for ( i = 1 ; i < vm->numSymbols ; i++ ) { + sorted[i] = sorted[i-1]->next; + total += sorted[i]->profileCount; + } + + qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); + + for ( i = 0 ; i < vm->numSymbols ; i++ ) { + int perc; + + sym = sorted[i]; + + perc = 100 * (float) sym->profileCount / total; + Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); + sym->profileCount = 0; + } + + Com_Printf(" %9.0f total\n", total ); + + Z_Free( sorted ); +} + +/* +============== +VM_VmInfo_f + +============== +*/ +void VM_VmInfo_f( void ) { + vm_t *vm; + int i; + + Com_Printf( "Registered virtual machines:\n" ); + for ( i = 0 ; i < MAX_VM ; i++ ) { + vm = &vmTable[i]; + if ( !vm->name[0] ) { + break; + } + Com_Printf( "%s : ", vm->name ); + if ( vm->dllHandle ) { + Com_Printf( "native\n" ); + continue; + } + if ( vm->compiled ) { + Com_Printf( "compiled on load\n" ); + } else { + Com_Printf( "interpreted\n" ); + } + Com_Printf( " code length : %7i\n", vm->codeLength ); + Com_Printf( " table length: %7i\n", vm->instructionCount*4 ); + Com_Printf( " data length : %7i\n", vm->dataMask + 1 ); + } +} + +/* +=============== +VM_LogSyscalls + +Insert calls to this while debugging the vm compiler +=============== +*/ +void VM_LogSyscalls( int *args ) { + static int callnum; + static FILE *f; + + if ( !f ) { + f = fopen("syscalls.log", "w" ); + } + callnum++; + fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase), + args[0], args[1], args[2], args[3], args[4] ); +} + +/* +================= +VM_BlockCopy +Executes a block copy operation within currentVM data space +================= +*/ + +void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n) +{ + unsigned int dataMask = currentVM->dataMask; + + if ((dest & dataMask) != dest + || (src & dataMask) != src + || ((dest + n) & dataMask) != dest + n + || ((src + n) & dataMask) != src + n) + { + Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!"); + } + + ::memcpy(currentVM->dataBase + dest, currentVM->dataBase + src, n); +} diff --git a/src/qcommon/vm.h b/src/qcommon/vm.h new file mode 100644 index 0000000..26705af --- /dev/null +++ b/src/qcommon/vm.h @@ -0,0 +1,67 @@ +#ifndef QCOMMON_VM_H +#define QCOMMON_VM_H 1 + +#include "q_shared.h" + +/* +============================================================== + +VIRTUAL MACHINE + +============================================================== +*/ + +typedef struct vm_s vm_t; + +typedef enum { + VMI_NATIVE, + VMI_BYTECODE, + VMI_COMPILED +} vmInterpret_t; + +typedef enum { + TRAP_MEMSET = 100, + TRAP_MEMCPY, + TRAP_STRNCPY, + TRAP_SIN, + TRAP_COS, + TRAP_ATAN2, + TRAP_SQRT, + TRAP_MATRIXMULTIPLY, + TRAP_ANGLEVECTORS, + TRAP_PERPENDICULARVECTOR, + TRAP_FLOOR, + TRAP_CEIL, + + TRAP_TESTPRINTINT, + TRAP_TESTPRINTFLOAT +} sharedTraps_t; + +void VM_Init( void ); +vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *), vmInterpret_t interpret ); +// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm" + +void VM_Free( vm_t *vm ); +void VM_Clear(void); +void VM_Forced_Unload_Start(void); +void VM_Forced_Unload_Done(void); +void VM_ClearCallLevel(vm_t *vm); +vm_t *VM_Restart(vm_t *vm, bool unpure); + +intptr_t QDECL VM_Call( vm_t *vm, int callNum, ... ); + +void VM_Debug( int level ); + +void *VM_ArgPtr( intptr_t intValue ); +void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ); + +#define VMA(x) VM_ArgPtr(args[x]) +static ID_INLINE float _vmf(intptr_t x) +{ + floatint_t fi; + fi.i = (int) x; + return fi.f; +} +#define VMF(x) _vmf(args[x]) + +#endif diff --git a/src/qcommon/vm_interpreted.cpp b/src/qcommon/vm_interpreted.cpp new file mode 100644 index 0000000..08cdfcd --- /dev/null +++ b/src/qcommon/vm_interpreted.cpp @@ -0,0 +1,904 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "vm.h" +#include "vm_local.h" + +//#define DEBUG_VM +#ifdef DEBUG_VM +static char *opnames[256] = { + "OP_UNDEF", + + "OP_IGNORE", + + "OP_BREAK", + + "OP_ENTER", + "OP_LEAVE", + "OP_CALL", + "OP_PUSH", + "OP_POP", + + "OP_CONST", + + "OP_LOCAL", + + "OP_JUMP", + + //------------------- + + "OP_EQ", + "OP_NE", + + "OP_LTI", + "OP_LEI", + "OP_GTI", + "OP_GEI", + + "OP_LTU", + "OP_LEU", + "OP_GTU", + "OP_GEU", + + "OP_EQF", + "OP_NEF", + + "OP_LTF", + "OP_LEF", + "OP_GTF", + "OP_GEF", + + //------------------- + + "OP_LOAD1", + "OP_LOAD2", + "OP_LOAD4", + "OP_STORE1", + "OP_STORE2", + "OP_STORE4", + "OP_ARG", + + "OP_BLOCK_COPY", + + //------------------- + + "OP_SEX8", + "OP_SEX16", + + "OP_NEGI", + "OP_ADD", + "OP_SUB", + "OP_DIVI", + "OP_DIVU", + "OP_MODI", + "OP_MODU", + "OP_MULI", + "OP_MULU", + + "OP_BAND", + "OP_BOR", + "OP_BXOR", + "OP_BCOM", + + "OP_LSH", + "OP_RSHI", + "OP_RSHU", + + "OP_NEGF", + "OP_ADDF", + "OP_SUBF", + "OP_DIVF", + "OP_MULF", + + "OP_CVIF", + "OP_CVFI" +}; +#endif + +#if idppc + +//FIXME: these, um... look the same to me +#if defined(__GNUC__) +static ID_INLINE unsigned int loadWord(void *addr) { + unsigned int word; + + asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); + return word; +} +#else +static ID_INLINE unsigned int __lwbrx(register void *addr, + register int offset) { + register unsigned int word; + + asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset)); + return word; +} +#define loadWord(addr) __lwbrx(addr,0) +#endif + +#else + static ID_INLINE int loadWord(void *addr) { + int word; + memcpy(&word, addr, 4); + return LittleLong(word); + } +#endif + +const char *VM_Indent( vm_t *vm ) { + const char *string = " "; + if ( vm->callLevel > 20 ) { + return string; + } + return string + 2 * ( 20 - vm->callLevel ); +} + +void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { + int count; + + count = 0; + do { + Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); + programStack = *(int *)&vm->dataBase[programStack+4]; + programCounter = *(int *)&vm->dataBase[programStack]; + } while ( programCounter != -1 && ++count < 32 ); + +} + + +/* +==================== +VM_PrepareInterpreter +==================== +*/ +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { + int op; + int byte_pc; + int int_pc; + byte *code; + int instruction; + int *codeBase; + + vm->codeBase = (unsigned char*)Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned +// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); + + // we don't need to translate the instructions, but we still need + // to find each instructions starting point for jumps + int_pc = byte_pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + // Copy and expand instructions to words while building instruction table + while ( instruction < header->instructionCount ) { + vm->instructionPointers[ instruction ] = int_pc; + instruction++; + + op = (int)code[ byte_pc ]; + codeBase[int_pc] = op; + if(byte_pc > header->codeLength) + Com_Error(ERR_DROP, "VM_PrepareInterpreter: pc > header->codeLength"); + + byte_pc++; + int_pc++; + + // these are the only opcodes that aren't a single byte + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + case OP_BLOCK_COPY: + codeBase[int_pc] = loadWord(&code[byte_pc]); + byte_pc += 4; + int_pc++; + break; + case OP_ARG: + codeBase[int_pc] = (int)code[byte_pc]; + byte_pc++; + int_pc++; + break; + default: + break; + } + + } + int_pc = 0; + instruction = 0; + + // Now that the code has been expanded to int-sized opcodes, we'll translate instruction index + //into an index into codeBase[], which contains opcodes and operands. + while ( instruction < header->instructionCount ) { + op = codeBase[ int_pc ]; + instruction++; + int_pc++; + + switch ( op ) { + // These ops need to translate addresses in jumps from instruction index to int index + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + if(codeBase[int_pc] < 0 || codeBase[int_pc] > vm->instructionCount) + Com_Error(ERR_DROP, "VM_PrepareInterpreter: Jump to invalid instruction number"); + + // codeBase[pc] is the instruction index. Convert that into an offset into + //the int-aligned codeBase[] by the lookup table. + codeBase[int_pc] = vm->instructionPointers[codeBase[int_pc]]; + int_pc++; + break; + + // These opcodes have an operand that isn't an instruction index + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_BLOCK_COPY: + case OP_ARG: + int_pc++; + break; + + default: + break; + } + + } +} + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return stack +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ + +#define DEBUGSTR va("%s%i", VM_Indent(vm), opStackOfs) + +int VM_CallInterpreted( vm_t *vm, int *args ) { + byte stack[OPSTACK_SIZE + 15]; + int *opStack; + uint8_t opStackOfs; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + int *codeImage; + int v1; + int dataMask; + int arg; +#ifdef DEBUG_VM + vmSymbol_t *profileSymbol; +#endif + + // interpret the code + vm->currentlyInterpreting = true; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); + // uncomment this for debugging breakpoints + vm->breakFunction = 0; +#endif + // set up the stack frame + + image = vm->dataBase; + codeImage = (int *)vm->codeBase; + dataMask = vm->dataMask; + + programCounter = 0; + + programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); + + for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ ) + *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ]; + + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + VM_Debug(0); + + // leave a free spot at start of stack so + // that as long as opStack is valid, opStack-1 will + // not corrupt anything + opStack = (int*)PADP(stack, 16); + *opStack = 0xDEADBEEF; + opStackOfs = 0; + +// vm_debugLevel=2; + // main interpreter loop, will exit when a LEAVE instruction + // grabs the -1 program counter + +#define r2 codeImage[programCounter] + + while ( 1 ) { + int opcode, r0, r1; +// unsigned int r2; + +nextInstruction: + r0 = opStack[opStackOfs]; + r1 = opStack[(uint8_t) (opStackOfs - 1)]; +nextInstruction2: +#ifdef DEBUG_VM + if ( (unsigned)programCounter >= vm->codeLength ) { + Com_Error( ERR_DROP, "VM pc out of range" ); + return 0; + } + + if ( programStack <= vm->stackBottom ) { + Com_Error( ERR_DROP, "VM stack overflow" ); + return 0; + } + + if ( programStack & 3 ) { + Com_Error( ERR_DROP, "VM program stack misaligned" ); + return 0; + } + + if ( vm_debugLevel > 1 ) { + Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); + } + profileSymbol->profileCount++; +#endif + opcode = codeImage[ programCounter++ ]; + + switch ( opcode ) { +#ifdef DEBUG_VM + default: + Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load! + return 0; +#endif + case OP_BREAK: + vm->breakCount++; + goto nextInstruction2; + case OP_CONST: + opStackOfs++; + r1 = r0; + r0 = opStack[opStackOfs] = r2; + + programCounter += 1; + goto nextInstruction2; + case OP_LOCAL: + opStackOfs++; + r1 = r0; + r0 = opStack[opStackOfs] = r2+programStack; + + programCounter += 1; + goto nextInstruction2; + + case OP_LOAD4: +#ifdef DEBUG_VM + if(opStack[opStackOfs] & 3) + { + Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); + return 0; + } +#endif + r0 = opStack[opStackOfs] = *(int *) &image[r0 & dataMask & ~3 ]; + goto nextInstruction2; + case OP_LOAD2: + r0 = opStack[opStackOfs] = *(unsigned short *)&image[ r0&dataMask&~1 ]; + goto nextInstruction2; + case OP_LOAD1: + r0 = opStack[opStackOfs] = image[ r0&dataMask ]; + goto nextInstruction2; + + case OP_STORE4: + *(int *)&image[ r1&(dataMask & ~3) ] = r0; + opStackOfs -= 2; + goto nextInstruction; + case OP_STORE2: + *(short *)&image[ r1&(dataMask & ~1) ] = r0; + opStackOfs -= 2; + goto nextInstruction; + case OP_STORE1: + image[ r1&dataMask ] = r0; + opStackOfs -= 2; + goto nextInstruction; + + case OP_ARG: + // single byte offset from programStack + *(int *)&image[ (codeImage[programCounter] + programStack)&dataMask&~3 ] = r0; + opStackOfs--; + programCounter += 1; + goto nextInstruction; + + case OP_BLOCK_COPY: + VM_BlockCopy(r1, r0, r2); + programCounter += 1; + opStackOfs -= 2; + goto nextInstruction; + + case OP_CALL: + // save current program counter + *(int *)&image[ programStack ] = programCounter; + + // jump to the location on the stack + programCounter = r0; + opStackOfs--; + if ( programCounter < 0 ) { + // system call + int r; +// int temp; +#ifdef DEBUG_VM + int stomped; + + if ( vm_debugLevel ) { + Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); + } +#endif + // save the stack to allow recursive VM entry +// temp = vm->callLevel; + vm->programStack = programStack - 4; +#ifdef DEBUG_VM + stomped = *(int *)&image[ programStack + 4 ]; +#endif + *(int *)&image[ programStack + 4 ] = -1 - programCounter; + +//VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); + { + // the vm has ints on the stack, we expect + // pointers so we might have to convert it + if (sizeof(intptr_t) != sizeof(int)) { + intptr_t argarr[ MAX_VMSYSCALL_ARGS ]; + int *imagePtr = (int *)&image[ programStack ]; + int i; + for (i = 0; i < ARRAY_LEN(argarr); ++i) { + argarr[i] = *(++imagePtr); + } + r = vm->systemCall( argarr ); + } else { + intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; + r = vm->systemCall( argptr ); + } + } + +#ifdef DEBUG_VM + // this is just our stack frame pointer, only needed + // for debugging + *(int *)&image[ programStack + 4 ] = stomped; +#endif + + // save return value + opStackOfs++; + opStack[opStackOfs] = r; + programCounter = *(int *)&image[ programStack ]; +// vm->callLevel = temp; +#ifdef DEBUG_VM + if ( vm_debugLevel ) { + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + } else if ( (unsigned)programCounter >= vm->instructionCount ) { + Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" ); + return 0; + } else { + programCounter = vm->instructionPointers[ programCounter ]; + } + goto nextInstruction; + + // push and pop are only needed for discarded or bad function return values + case OP_PUSH: + opStackOfs++; + goto nextInstruction; + case OP_POP: + opStackOfs--; + goto nextInstruction; + + case OP_ENTER: +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); +#endif + // get size of stack frame + v1 = r2; + + programCounter += 1; + programStack -= v1; +#ifdef DEBUG_VM + // save old stack frame for debugging traces + *(int *)&image[programStack+4] = programStack + v1; + if ( vm_debugLevel ) { + Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); + if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { + // this is to allow setting breakpoints here in the debugger + vm->breakCount++; +// vm_debugLevel = 2; +// VM_StackTrace( vm, programCounter, programStack ); + } +// vm->callLevel++; + } +#endif + goto nextInstruction; + case OP_LEAVE: + // remove our stack frame + v1 = r2; + + programStack += v1; + + // grab the saved program counter + programCounter = *(int *)&image[ programStack ]; +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + if ( vm_debugLevel ) { +// vm->callLevel--; + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + // check for leaving the VM + if ( programCounter == -1 ) { + goto done; + } else if ( (unsigned)programCounter >= vm->codeLength ) { + Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" ); + return 0; + } + goto nextInstruction; + + /* + =================================================================== + BRANCHES + =================================================================== + */ + + case OP_JUMP: + if ( (unsigned)r0 >= vm->instructionCount ) + { + Com_Error( ERR_DROP, "VM program counter out of range in OP_JUMP" ); + return 0; + } + + programCounter = vm->instructionPointers[ r0 ]; + + opStackOfs--; + goto nextInstruction; + + case OP_EQ: + opStackOfs -= 2; + if ( r1 == r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_NE: + opStackOfs -= 2; + if ( r1 != r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTI: + opStackOfs -= 2; + if ( r1 < r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEI: + opStackOfs -= 2; + if ( r1 <= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTI: + opStackOfs -= 2; + if ( r1 > r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEI: + opStackOfs -= 2; + if ( r1 >= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTU: + opStackOfs -= 2; + if ( ((unsigned)r1) < ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEU: + opStackOfs -= 2; + if ( ((unsigned)r1) <= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTU: + opStackOfs -= 2; + if ( ((unsigned)r1) > ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEU: + opStackOfs -= 2; + if ( ((unsigned)r1) >= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_EQF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] == ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_NEF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] != ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] < ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 1))] <= ((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 2))]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] > ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] >= ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + + //=================================================================== + + case OP_NEGI: + opStack[opStackOfs] = -r0; + goto nextInstruction; + case OP_ADD: + opStackOfs--; + opStack[opStackOfs] = r1 + r0; + goto nextInstruction; + case OP_SUB: + opStackOfs--; + opStack[opStackOfs] = r1 - r0; + goto nextInstruction; + case OP_DIVI: + opStackOfs--; + opStack[opStackOfs] = r1 / r0; + goto nextInstruction; + case OP_DIVU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) / ((unsigned) r0); + goto nextInstruction; + case OP_MODI: + opStackOfs--; + opStack[opStackOfs] = r1 % r0; + goto nextInstruction; + case OP_MODU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) % ((unsigned) r0); + goto nextInstruction; + case OP_MULI: + opStackOfs--; + opStack[opStackOfs] = r1 * r0; + goto nextInstruction; + case OP_MULU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) * ((unsigned) r0); + goto nextInstruction; + + case OP_BAND: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) & ((unsigned) r0); + goto nextInstruction; + case OP_BOR: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) | ((unsigned) r0); + goto nextInstruction; + case OP_BXOR: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) ^ ((unsigned) r0); + goto nextInstruction; + case OP_BCOM: + opStack[opStackOfs] = ~((unsigned) r0); + goto nextInstruction; + + case OP_LSH: + opStackOfs--; + opStack[opStackOfs] = r1 << r0; + goto nextInstruction; + case OP_RSHI: + opStackOfs--; + opStack[opStackOfs] = r1 >> r0; + goto nextInstruction; + case OP_RSHU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) >> r0; + goto nextInstruction; + + case OP_NEGF: + ((float *) opStack)[opStackOfs] = -((float *) opStack)[opStackOfs]; + goto nextInstruction; + case OP_ADDF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] + ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + case OP_SUBF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] - ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + case OP_DIVF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] / ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + case OP_MULF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] * ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + + case OP_CVIF: + ((float *) opStack)[opStackOfs] = (float) opStack[opStackOfs]; + goto nextInstruction; + case OP_CVFI: + opStack[opStackOfs] = static_cast(((float *) opStack)[opStackOfs]); + goto nextInstruction; + case OP_SEX8: + opStack[opStackOfs] = (signed char) opStack[opStackOfs]; + goto nextInstruction; + case OP_SEX16: + opStack[opStackOfs] = (short) opStack[opStackOfs]; + goto nextInstruction; + } + } + +done: + vm->currentlyInterpreting = false; + + if (opStackOfs != 1 || *opStack != 0xDEADBEEF) + Com_Error(ERR_DROP, "Interpreter error: opStack[0] = %X, opStackOfs = %d", opStack[0], opStackOfs); + + vm->programStack = stackOnEntry; + + // return the result + return opStack[opStackOfs]; +} diff --git a/src/qcommon/vm_local.h b/src/qcommon/vm_local.h new file mode 100644 index 0000000..b4ce844 --- /dev/null +++ b/src/qcommon/vm_local.h @@ -0,0 +1,204 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "q_shared.h" +#include "qcommon.h" + +// Max number of arguments to pass from engine to vm's vmMain function. +// command number + 3 arguments +#define MAX_VMMAIN_ARGS 4 + +// Max number of arguments to pass from a vm to engine's syscall handler function for the vm. +// syscall number + 9 arguments +#define MAX_VMSYSCALL_ARGS 20 + +// don't change, this is hardcoded into x86 VMs, opStack protection relies +// on this +#define OPSTACK_SIZE 1024 +#define OPSTACK_MASK (OPSTACK_SIZE-1) + +// don't change +// Hardcoded in q3asm a reserved at end of bss +#define PROGRAM_STACK_SIZE 0x10000 +#define PROGRAM_STACK_MASK (PROGRAM_STACK_SIZE-1) + +typedef enum { + OP_UNDEF, + + OP_IGNORE, + + OP_BREAK, + + OP_ENTER, + OP_LEAVE, + OP_CALL, + OP_PUSH, + OP_POP, + + OP_CONST, + OP_LOCAL, + + OP_JUMP, + + //------------------- + + OP_EQ, + OP_NE, + + OP_LTI, + OP_LEI, + OP_GTI, + OP_GEI, + + OP_LTU, + OP_LEU, + OP_GTU, + OP_GEU, + + OP_EQF, + OP_NEF, + + OP_LTF, + OP_LEF, + OP_GTF, + OP_GEF, + + //------------------- + + OP_LOAD1, + OP_LOAD2, + OP_LOAD4, + OP_STORE1, + OP_STORE2, + OP_STORE4, // *(stack[top-1]) = stack[top] + OP_ARG, + + OP_BLOCK_COPY, + + //------------------- + + OP_SEX8, + OP_SEX16, + + OP_NEGI, + OP_ADD, + OP_SUB, + OP_DIVI, + OP_DIVU, + OP_MODI, + OP_MODU, + OP_MULI, + OP_MULU, + + OP_BAND, + OP_BOR, + OP_BXOR, + OP_BCOM, + + OP_LSH, + OP_RSHI, + OP_RSHU, + + OP_NEGF, + OP_ADDF, + OP_SUBF, + OP_DIVF, + OP_MULF, + + OP_CVIF, + OP_CVFI +} opcode_t; + + + +typedef int vmptr_t; + +typedef struct vmSymbol_s { + struct vmSymbol_s *next; + int symValue; + int profileCount; + char symName[1]; // variable sized +} vmSymbol_t; + +#define VM_OFFSET_PROGRAM_STACK 0 +#define VM_OFFSET_SYSTEM_CALL 4 + +struct vm_s { + // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES + // USED BY THE ASM CODE + int programStack; // the vm may be recursively entered + intptr_t (*systemCall)( intptr_t *parms ); + + //------------------------------------ + + char name[MAX_QPATH]; + void *searchPath; // hint for FS_ReadFileDir() + + // for dynamic linked modules + void *dllHandle; + intptr_t (QDECL *entryPoint)( int callNum, ... ); + void (*destroy)(vm_t* self); + + // for interpreted modules + bool currentlyInterpreting; + + bool compiled; + byte *codeBase; + int entryOfs; + int codeLength; + + intptr_t *instructionPointers; + int instructionCount; + + byte *dataBase; + int dataMask; + + int stackBottom; // if programStack < stackBottom, error + + int numSymbols; + struct vmSymbol_s *symbols; + + int callLevel; // counts recursive VM_Call + int breakFunction; // increment breakCount on function entry to this + int breakCount; + + byte *jumpTableTargets; + int numJumpTableTargets; +}; + + +extern vm_t *currentVM; +extern int vm_debugLevel; + +void VM_Compile( vm_t *vm, vmHeader_t *header ); +int VM_CallCompiled( vm_t *vm, int *args ); + +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ); +int VM_CallInterpreted( vm_t *vm, int *args ); + +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); +int VM_SymbolToValue( vm_t *vm, const char *symbol ); +const char *VM_ValueToSymbol( vm_t *vm, int value ); +void VM_LogSyscalls( int *args ); + +void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n); diff --git a/src/qcommon/vm_x86.cpp b/src/qcommon/vm_x86.cpp new file mode 100644 index 0000000..c3373aa --- /dev/null +++ b/src/qcommon/vm_x86.cpp @@ -0,0 +1,1840 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// vm_x86.c -- load time compiler and execution environment for x86 + +#include "vm.h" +#include "vm_local.h" + +#ifdef _WIN32 + #include +#else + #ifdef __FreeBSD__ + #include + #endif + + #include // for PROT_ stuff + + /* need this on NX enabled systems (i386 with PAE kernel or + * noexec32=on x86_64) */ + #define VM_X86_MMAP + + // workaround for systems that use the old MAP_ANON macro + #ifndef MAP_ANONYMOUS + #define MAP_ANONYMOUS MAP_ANON + #endif +#endif + +static void VM_Destroy_Compiled(vm_t* self); + +/* + + eax scratch + ebx/bl opStack offset + ecx scratch (required for shifts) + edx scratch (required for divisions) + esi program stack + edi opStack base +x86_64: + r8 vm->instructionPointers + r9 vm->dataBase + +*/ + +#define VMFREE_BUFFERS() do {Z_Free(buf); Z_Free(jused);} while(0) +static byte *buf = NULL; +static byte *jused = NULL; +static int jusedSize = 0; +static int compiledOfs = 0; +static byte *code = NULL; +static int pc = 0; + +#define FTOL_PTR + +static int instruction, pass; +static int lastConst = 0; +static int oc0, oc1, pop0, pop1; +static int jlabel; + +typedef enum +{ + LAST_COMMAND_NONE = 0, + LAST_COMMAND_MOV_STACK_EAX, + LAST_COMMAND_SUB_BL_1, + LAST_COMMAND_SUB_BL_2, +} ELastCommand; + +typedef enum +{ + VM_JMP_VIOLATION = 0, + VM_BLOCK_COPY = 1 +} ESysCallType; + +static ELastCommand LastCommand; + +static int iss8(int32_t v) +{ + return (SCHAR_MIN <= v && v <= SCHAR_MAX); +} + +#if 0 +static int isu8(uint32_t v) +{ + return (v <= UCHAR_MAX); +} +#endif + +static int NextConstant4(void) +{ + return (code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24)); +} + +static int Constant4( void ) { + int v; + + v = NextConstant4(); + pc += 4; + return v; +} + +static int Constant1( void ) { + int v; + + v = code[pc]; + pc += 1; + return v; +} + +static void Emit1( int v ) +{ + buf[ compiledOfs ] = v; + compiledOfs++; + + LastCommand = LAST_COMMAND_NONE; +} + +static void Emit2(int v) +{ + Emit1(v & 255); + Emit1((v >> 8) & 255); +} + + +static void Emit4(int v) +{ + Emit1(v & 0xFF); + Emit1((v >> 8) & 0xFF); + Emit1((v >> 16) & 0xFF); + Emit1((v >> 24) & 0xFF); +} + +static void EmitPtr(void *ptr) +{ + intptr_t v = (intptr_t) ptr; + + Emit4(v); +#if idx64 + Emit1((v >> 32) & 0xFF); + Emit1((v >> 40) & 0xFF); + Emit1((v >> 48) & 0xFF); + Emit1((v >> 56) & 0xFF); +#endif +} + +static int Hex( int c ) { + if ( c >= 'a' && c <= 'f' ) { + return 10 + c - 'a'; + } + if ( c >= 'A' && c <= 'F' ) { + return 10 + c - 'A'; + } + if ( c >= '0' && c <= '9' ) { + return c - '0'; + } + + VMFREE_BUFFERS(); + Com_Error( ERR_DROP, "Hex: bad char '%c'", c ); + + return 0; +} +static void EmitString( const char *string ) { + int c1, c2; + int v; + + while ( 1 ) { + c1 = string[0]; + c2 = string[1]; + + v = ( Hex( c1 ) << 4 ) | Hex( c2 ); + Emit1( v ); + + if ( !string[2] ) { + break; + } + string += 3; + } +} +static void EmitRexString(byte rex, const char *string) +{ +#if idx64 + if(rex) + Emit1(rex); +#endif + + EmitString(string); +} + + +#define MASK_REG(modrm, mask) \ + do { \ + EmitString("81"); \ + EmitString((modrm)); \ + Emit4((mask)); \ + } while(0) + +// add bl, bytes +#define STACK_PUSH(bytes) \ + do { \ + EmitString("80 C3"); \ + Emit1(bytes); \ + } while(0) + +// sub bl, bytes +#define STACK_POP(bytes) \ + do { \ + EmitString("80 EB"); \ + Emit1(bytes); \ + } while(0) + +static void EmitCommand(ELastCommand command) +{ + switch(command) + { + case LAST_COMMAND_MOV_STACK_EAX: + EmitString("89 04 9F"); // mov dword ptr [edi + ebx * 4], eax + break; + + case LAST_COMMAND_SUB_BL_1: + STACK_POP(1); // sub bl, 1 + break; + + case LAST_COMMAND_SUB_BL_2: + STACK_POP(2); // sub bl, 2 + break; + default: + break; + } + LastCommand = command; +} + +static void EmitPushStack(vm_t *vm) +{ + if (!jlabel) + { + if(LastCommand == LAST_COMMAND_SUB_BL_1) + { // sub bl, 1 + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + return; + } + if(LastCommand == LAST_COMMAND_SUB_BL_2) + { // sub bl, 2 + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + STACK_POP(1); // sub bl, 1 + return; + } + } + + STACK_PUSH(1); // add bl, 1 +} + +static void EmitMovEAXStack(vm_t *vm, int andit) +{ + if(!jlabel) + { + if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) + { // mov [edi + ebx * 4], eax + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + } + else if(pop1 == OP_CONST && buf[compiledOfs-7] == 0xC7 && buf[compiledOfs-6] == 0x04 && buf[compiledOfs - 5] == 0x9F) + { // mov [edi + ebx * 4], 0x12345678 + compiledOfs -= 7; + vm->instructionPointers[instruction - 1] = compiledOfs; + EmitString("B8"); // mov eax, 0x12345678 + + if(andit) + Emit4(lastConst & andit); + else + Emit4(lastConst); + + return; + } + else if(pop1 != OP_DIVI && pop1 != OP_DIVU && pop1 != OP_MULI && pop1 != OP_MULU && + pop1 != OP_STORE4 && pop1 != OP_STORE2 && pop1 != OP_STORE1) + { + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + } + } + else + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + + if(andit) + { + EmitString("25"); // and eax, 0x12345678 + Emit4(andit); + } +} + +void EmitMovECXStack(vm_t *vm) +{ + if(!jlabel) + { + if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) // mov [edi + ebx * 4], eax + { + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + EmitString("89 C1"); // mov ecx, eax + return; + } + if(pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1) + { + EmitString("89 C1"); // mov ecx, eax + return; + } + } + + EmitString("8B 0C 9F"); // mov ecx, dword ptr [edi + ebx * 4] +} + + +void EmitMovEDXStack(vm_t *vm, int andit) +{ + if(!jlabel) + { + if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) + { // mov dword ptr [edi + ebx * 4], eax + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + + EmitString("8B D0"); // mov edx, eax + } + else if(pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1) + { + EmitString("8B D0"); // mov edx, eax + } + else if(pop1 == OP_CONST && buf[compiledOfs-7] == 0xC7 && buf[compiledOfs-6] == 0x07 && buf[compiledOfs - 5] == 0x9F) + { // mov dword ptr [edi + ebx * 4], 0x12345678 + compiledOfs -= 7; + vm->instructionPointers[instruction - 1] = compiledOfs; + EmitString("BA"); // mov edx, 0x12345678 + + if(andit) + Emit4(lastConst & andit); + else + Emit4(lastConst); + + return; + } + else + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + + } + else + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + + if(andit) + MASK_REG("E2", andit); // and edx, 0x12345678 +} + +#define JUSED(x) \ + do { \ + if (x < 0 || x >= vm->instructionCount) { \ + VMFREE_BUFFERS(); \ + Com_Error( ERR_DROP, \ + "VM_CompileX86: jump target out of range at offset %d", pc ); \ + } \ + jused[x] = 1; \ + } while(0) + +#define SET_JMPOFS(x) do { buf[(x)] = compiledOfs - ((x) + 1); } while(0) + + +/* +================= +ErrJump +Error handler for jump/call to invalid instruction number +================= +*/ + +static void __attribute__((__noreturn__)) ErrJump(void) +{ + Com_Error(ERR_DROP, "program tried to execute code outside VM"); +} + +/* +================= +DoSyscall + +Assembler helper routines will write its arguments directly to global variables so as to +work around different calling conventions +================= +*/ + +int vm_syscallNum; +int vm_programStack; +int *vm_opStackBase; +uint8_t vm_opStackOfs; +intptr_t vm_arg; + +static void DoSyscall(void) +{ + vm_t *savedVM; + + // save currentVM so as to allow for recursive VM entry + savedVM = currentVM; + // modify VM stack pointer for recursive VM entry + currentVM->programStack = vm_programStack - 4; + + if(vm_syscallNum < 0) + { + int *data, *ret; +#if idx64 + int index; + intptr_t args[MAX_VMSYSCALL_ARGS]; +#endif + + data = (int *) (savedVM->dataBase + vm_programStack + 4); + ret = &vm_opStackBase[vm_opStackOfs + 1]; + +#if idx64 + args[0] = ~vm_syscallNum; + for(index = 1; index < ARRAY_LEN(args); index++) + args[index] = data[index]; + + *ret = savedVM->systemCall(args); +#else + data[0] = ~vm_syscallNum; + *ret = savedVM->systemCall((intptr_t *) data); +#endif + } + else + { + switch(vm_syscallNum) + { + case VM_JMP_VIOLATION: + ErrJump(); + break; + case VM_BLOCK_COPY: + if(vm_opStackOfs < 1) + Com_Error(ERR_DROP, "VM_BLOCK_COPY failed due to corrupted opStack"); + + VM_BlockCopy(vm_opStackBase[(vm_opStackOfs - 1)], vm_opStackBase[vm_opStackOfs], vm_arg); + break; + default: + Com_Error(ERR_DROP, "Unknown VM operation %d", vm_syscallNum); + break; + } + } + + currentVM = savedVM; +} + +/* +================= +EmitCallRel +Relative call to vm->codeBase + callOfs +================= +*/ + +void EmitCallRel(vm_t *vm, int callOfs) +{ + EmitString("E8"); // call 0x12345678 + Emit4(callOfs - compiledOfs - 4); +} + +/* +================= +EmitCallDoSyscall +Call to DoSyscall() +================= +*/ + +int EmitCallDoSyscall(vm_t *vm) +{ + // use edx register to store DoSyscall address + EmitRexString(0x48, "BA"); // mov edx, DoSyscall + EmitPtr((void*)DoSyscall); + + // Push important registers to stack as we can't really make + // any assumptions about calling conventions. + EmitString("51"); // push ebx + EmitString("56"); // push esi + EmitString("57"); // push edi +#if idx64 + EmitRexString(0x41, "50"); // push r8 + EmitRexString(0x41, "51"); // push r9 +#endif + + // write arguments to global vars + // syscall number + EmitString("A3"); // mov [0x12345678], eax + EmitPtr(&vm_syscallNum); + // vm_programStack value + EmitString("89 F0"); // mov eax, esi + EmitString("A3"); // mov [0x12345678], eax + EmitPtr(&vm_programStack); + // vm_opStackOfs + EmitString("88 D8"); // mov al, bl + EmitString("A2"); // mov [0x12345678], al + EmitPtr(&vm_opStackOfs); + // vm_opStackBase + EmitRexString(0x48, "89 F8"); // mov eax, edi + EmitRexString(0x48, "A3"); // mov [0x12345678], eax + EmitPtr(&vm_opStackBase); + // vm_arg + EmitString("89 C8"); // mov eax, ecx + EmitString("A3"); // mov [0x12345678], eax + EmitPtr(&vm_arg); + + // align the stack pointer to a 16-byte-boundary + EmitString("55"); // push ebp + EmitRexString(0x48, "89 E5"); // mov ebp, esp + EmitRexString(0x48, "83 E4 F0"); // and esp, 0xFFFFFFF0 + + // call the syscall wrapper function DoSyscall() + + EmitString("FF D2"); // call edx + + // reset the stack pointer to its previous value + EmitRexString(0x48, "89 EC"); // mov esp, ebp + EmitString("5D"); // pop ebp + +#if idx64 + EmitRexString(0x41, "59"); // pop r9 + EmitRexString(0x41, "58"); // pop r8 +#endif + EmitString("5F"); // pop edi + EmitString("5E"); // pop esi + EmitString("59"); // pop ebx + + EmitString("C3"); // ret + + return compiledOfs; +} + +/* +================= +EmitCallErrJump +Emit the code that triggers execution of the jump violation handler +================= +*/ + +static void EmitCallErrJump(vm_t *vm, int sysCallOfs) +{ + EmitString("B8"); // mov eax, 0x12345678 + Emit4(VM_JMP_VIOLATION); + + EmitCallRel(vm, sysCallOfs); +} + +/* +================= +EmitCallProcedure +VM OP_CALL procedure for call destinations obtained at runtime +================= +*/ + +int EmitCallProcedure(vm_t *vm, int sysCallOfs) +{ + int jmpSystemCall, jmpBadAddr; + int retval; + + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + STACK_POP(1); // sub bl, 1 + EmitString("85 C0"); // test eax, eax + + // Jump to syscall code, 1 byte offset should suffice + EmitString("7C"); // jl systemCall + jmpSystemCall = compiledOfs++; + + /************ Call inside VM ************/ + + EmitString("81 F8"); // cmp eax, vm->instructionCount + Emit4(vm->instructionCount); + + // Error jump if invalid jump target + EmitString("73"); // jae badAddr + jmpBadAddr = compiledOfs++; + +#if idx64 + EmitRexString(0x49, "FF 14 C0"); // call qword ptr [r8 + eax * 8] +#else + EmitString("FF 14 85"); // call dword ptr [vm->instructionPointers + eax * 4] + Emit4((intptr_t) vm->instructionPointers); +#endif + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + EmitString("C3"); // ret + + // badAddr: + SET_JMPOFS(jmpBadAddr); + EmitCallErrJump(vm, sysCallOfs); + + /************ System Call ************/ + + // systemCall: + SET_JMPOFS(jmpSystemCall); + retval = compiledOfs; + + EmitCallRel(vm, sysCallOfs); + + // have opStack reg point at return value + STACK_PUSH(1); // add bl, 1 + EmitString("C3"); // ret + + return retval; +} + +/* +================= +EmitJumpIns +Jump to constant instruction number +================= +*/ + +void EmitJumpIns(vm_t *vm, const char *jmpop, int cdest) +{ + JUSED(cdest); + + EmitString(jmpop); // j??? 0x12345678 + + // we only know all the jump addresses in the third pass + if(pass == 2) + Emit4(vm->instructionPointers[cdest] - compiledOfs - 4); + else + compiledOfs += 4; +} + +/* +================= +EmitCallIns +Call to constant instruction number +================= +*/ + +void EmitCallIns(vm_t *vm, int cdest) +{ + JUSED(cdest); + + EmitString("E8"); // call 0x12345678 + + // we only know all the jump addresses in the third pass + if(pass == 2) + Emit4(vm->instructionPointers[cdest] - compiledOfs - 4); + else + compiledOfs += 4; +} + +/* +================= +EmitCallConst +Call to constant instruction number or syscall +================= +*/ + +void EmitCallConst(vm_t *vm, int cdest, int callProcOfsSyscall) +{ + if(cdest < 0) + { + EmitString("B8"); // mov eax, cdest + Emit4(cdest); + + EmitCallRel(vm, callProcOfsSyscall); + } + else + EmitCallIns(vm, cdest); +} + +/* +================= +EmitBranchConditions +Emits x86 branch condition as given in op +================= +*/ +void EmitBranchConditions(vm_t *vm, int op) +{ + switch(op) + { + case OP_EQ: + EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678 + break; + case OP_NE: + EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678 + break; + case OP_LTI: + EmitJumpIns(vm, "0F 8C", Constant4()); // jl 0x12345678 + break; + case OP_LEI: + EmitJumpIns(vm, "0F 8E", Constant4()); // jle 0x12345678 + break; + case OP_GTI: + EmitJumpIns(vm, "0F 8F", Constant4()); // jg 0x12345678 + break; + case OP_GEI: + EmitJumpIns(vm, "0F 8D", Constant4()); // jge 0x12345678 + break; + case OP_LTU: + EmitJumpIns(vm, "0F 82", Constant4()); // jb 0x12345678 + break; + case OP_LEU: + EmitJumpIns(vm, "0F 86", Constant4()); // jbe 0x12345678 + break; + case OP_GTU: + EmitJumpIns(vm, "0F 87", Constant4()); // ja 0x12345678 + break; + case OP_GEU: + EmitJumpIns(vm, "0F 83", Constant4()); // jae 0x12345678 + break; + } +} + + +/* +================= +ConstOptimize +Constant values for immediately following instructions may be translated to immediate values +instead of opStack operations, which will save expensive operations on memory +================= +*/ + +static bool ConstOptimize(vm_t *vm, int callProcOfsSyscall) +{ + int v; + int op1; + + // we can safely perform optimizations only in case if + // we are 100% sure that next instruction is not a jump label + if (vm->jumpTableTargets && !jused[instruction]) + op1 = code[pc+4]; + else + return false; + + switch ( op1 ) { + + case OP_LOAD4: + EmitPushStack(vm); +#if idx64 + EmitRexString(0x41, "8B 81"); // mov eax, dword ptr [r9 + 0x12345678] + Emit4(Constant4() & vm->dataMask); +#else + EmitString("B8"); // mov eax, 0x12345678 + EmitPtr(vm->dataBase + (Constant4() & vm->dataMask)); + EmitString("8B 00"); // mov eax, dword ptr [eax] +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + + pc++; // OP_LOAD4 + instruction += 1; + return true; + + case OP_LOAD2: + EmitPushStack(vm); +#if idx64 + EmitRexString(0x41, "0F B7 81"); // movzx eax, word ptr [r9 + 0x12345678] + Emit4(Constant4() & vm->dataMask); +#else + EmitString("B8"); // mov eax, 0x12345678 + EmitPtr(vm->dataBase + (Constant4() & vm->dataMask)); + EmitString("0F B7 00"); // movzx eax, word ptr [eax] +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + + pc++; // OP_LOAD2 + instruction += 1; + return true; + + case OP_LOAD1: + EmitPushStack(vm); +#if idx64 + EmitRexString(0x41, "0F B6 81"); // movzx eax, byte ptr [r9 + 0x12345678] + Emit4(Constant4() & vm->dataMask); +#else + EmitString("B8"); // mov eax, 0x12345678 + EmitPtr(vm->dataBase + (Constant4() & vm->dataMask)); + EmitString("0F B6 00"); // movzx eax, byte ptr [eax] +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + + pc++; // OP_LOAD1 + instruction += 1; + return true; + + case OP_STORE4: + EmitMovEAXStack(vm, (vm->dataMask & ~3)); +#if idx64 + EmitRexString(0x41, "C7 04 01"); // mov dword ptr [r9 + eax], 0x12345678 + Emit4(Constant4()); +#else + EmitString("C7 80"); // mov dword ptr [eax + 0x12345678], 0x12345678 + Emit4((intptr_t) vm->dataBase); + Emit4(Constant4()); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + pc++; // OP_STORE4 + instruction += 1; + return true; + + case OP_STORE2: + EmitMovEAXStack(vm, (vm->dataMask & ~1)); +#if idx64 + Emit1(0x66); // mov word ptr [r9 + eax], 0x1234 + EmitRexString(0x41, "C7 04 01"); + Emit2(Constant4()); +#else + EmitString("66 C7 80"); // mov word ptr [eax + 0x12345678], 0x1234 + Emit4((intptr_t) vm->dataBase); + Emit2(Constant4()); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + + pc++; // OP_STORE2 + instruction += 1; + return true; + + case OP_STORE1: + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "C6 04 01"); // mov byte [r9 + eax], 0x12 + Emit1(Constant4()); +#else + EmitString("C6 80"); // mov byte ptr [eax + 0x12345678], 0x12 + Emit4((intptr_t) vm->dataBase); + Emit1(Constant4()); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + + pc++; // OP_STORE1 + instruction += 1; + return true; + + case OP_ADD: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 C0"); // add eax, 0x7F + Emit1(v); + } + else + { + EmitString("05"); // add eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc++; // OP_ADD + instruction += 1; + return true; + + case OP_SUB: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 E8"); // sub eax, 0x7F + Emit1(v); + } + else + { + EmitString("2D"); // sub eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc++; // OP_SUB + instruction += 1; + return true; + + case OP_MULI: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("6B C0"); // imul eax, 0x7F + Emit1(v); + } + else + { + EmitString("69 C0"); // imul eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + pc++; // OP_MULI + instruction += 1; + + return true; + + case OP_LSH: + v = NextConstant4(); + if(v < 0 || v > 31) + break; + + EmitMovEAXStack(vm, 0); + EmitString("C1 E0"); // shl eax, 0x12 + Emit1(v); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 5; // CONST + OP_LSH + instruction += 1; + return true; + + case OP_RSHI: + v = NextConstant4(); + if(v < 0 || v > 31) + break; + + EmitMovEAXStack(vm, 0); + EmitString("C1 F8"); // sar eax, 0x12 + Emit1(v); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 5; // CONST + OP_RSHI + instruction += 1; + return true; + + case OP_RSHU: + v = NextConstant4(); + if(v < 0 || v > 31) + break; + + EmitMovEAXStack(vm, 0); + EmitString("C1 E8"); // shr eax, 0x12 + Emit1(v); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 5; // CONST + OP_RSHU + instruction += 1; + return true; + + case OP_BAND: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 E0"); // and eax, 0x7F + Emit1(v); + } + else + { + EmitString("25"); // and eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 1; // OP_BAND + instruction += 1; + return true; + + case OP_BOR: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 C8"); // or eax, 0x7F + Emit1(v); + } + else + { + EmitString("0D"); // or eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 1; // OP_BOR + instruction += 1; + return true; + + case OP_BXOR: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 F0"); // xor eax, 0x7F + Emit1(v); + } + else + { + EmitString("35"); // xor eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 1; // OP_BXOR + instruction += 1; + return true; + + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + EmitMovEAXStack(vm, 0); + EmitCommand(LAST_COMMAND_SUB_BL_1); + EmitString("3D"); // cmp eax, 0x12345678 + Emit4(Constant4()); + + pc++; // OP_* + EmitBranchConditions(vm, op1); + instruction++; + + return true; + + case OP_EQF: + case OP_NEF: + if(NextConstant4()) + break; + pc += 5; // CONST + OP_EQF|OP_NEF + + EmitMovEAXStack(vm, 0); + EmitCommand(LAST_COMMAND_SUB_BL_1); + // floating point hack :) + EmitString("25"); // and eax, 0x7FFFFFFF + Emit4(0x7FFFFFFF); + if(op1 == OP_EQF) + EmitJumpIns(vm, "0F 84", Constant4()); // jz 0x12345678 + else + EmitJumpIns(vm, "0F 85", Constant4()); // jnz 0x12345678 + + instruction += 1; + return true; + + + case OP_JUMP: + EmitJumpIns(vm, "E9", Constant4()); // jmp 0x12345678 + + pc += 1; // OP_JUMP + instruction += 1; + return true; + + case OP_CALL: + v = Constant4(); + EmitCallConst(vm, v, callProcOfsSyscall); + + pc += 1; // OP_CALL + instruction += 1; + return true; + + default: + break; + } + + return false; +} + +#if idx64 + #define EAX "%%rax" + #define EBX "%%rbx" + #define ESP "%%rsp" + #define EDI "%%rdi" +#else + #define EAX "%%eax" + #define EBX "%%ebx" + #define ESP "%%esp" + #define EDI "%%edi" +#endif + +static int Q_VMftol(void) +{ + int retval; + + __asm__ volatile + ( + "movss (" EDI ", " EBX ", 4), %%xmm0\n" + "cvttss2si %%xmm0, %0\n" + : "=r" (retval) + : + : "%xmm0" + ); + + return retval; +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile(vm_t *vm, vmHeader_t *header) +{ + int op; + int maxLength; + int v; + int i; + int callProcOfsSyscall, callProcOfs, callDoSyscallOfs; + + jusedSize = header->instructionCount + 2; + + // allocate a very large temp buffer, we will shrink it later + maxLength = header->codeLength * 8 + 64; + buf = (byte*)Z_Malloc(maxLength); + jused = (byte*)Z_Malloc(jusedSize); + code = (byte*)Z_Malloc(header->codeLength+32); + + ::memset(jused, 0, jusedSize); + ::memset(buf, 0, maxLength); + + // copy code in larger buffer and put some zeros at the end + // so we can safely look ahead for a few instructions in it + // without a chance to get false-positive because of some garbage bytes + ::memset(code, 0, header->codeLength+32); + ::memcpy(code, (byte *)header + header->codeOffset, header->codeLength ); + + // ensure that the optimisation pass knows about all the jump + // table targets + pc = -1; // a bogus value to be printed in out-of-bounds error messages + for( i = 0; i < vm->numJumpTableTargets; i++ ) { + JUSED( *(int *)(vm->jumpTableTargets + ( i * sizeof( int ) ) ) ); + } + + // Start buffer with x86-VM specific procedures + compiledOfs = 0; + + callDoSyscallOfs = compiledOfs; + callProcOfs = EmitCallDoSyscall(vm); + callProcOfsSyscall = EmitCallProcedure(vm, callDoSyscallOfs); + vm->entryOfs = compiledOfs; + + for(pass=0; pass < 3; pass++) { + oc0 = -23423; + oc1 = -234354; + pop0 = -43435; + pop1 = -545455; + + // translate all instructions + pc = 0; + instruction = 0; + //code = (byte *)header + header->codeOffset; + compiledOfs = vm->entryOfs; + + LastCommand = LAST_COMMAND_NONE; + + while(instruction < header->instructionCount) + { + if(compiledOfs > maxLength - 16) + { + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: maxLength exceeded"); + } + + vm->instructionPointers[ instruction ] = compiledOfs; + + if ( !vm->jumpTableTargets ) + jlabel = 1; + else + jlabel = jused[ instruction ]; + + instruction++; + + if(pc > header->codeLength) + { + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: pc > header->codeLength"); + } + + op = code[ pc ]; + pc++; + switch ( op ) { + case 0: + break; + case OP_BREAK: + EmitString("CC"); // int 3 + break; + case OP_ENTER: + EmitString("81 EE"); // sub esi, 0x12345678 + Emit4(Constant4()); + break; + case OP_CONST: + if(ConstOptimize(vm, callProcOfsSyscall)) + break; + + EmitPushStack(vm); + EmitString("C7 04 9F"); // mov dword ptr [edi + ebx * 4], 0x12345678 + lastConst = Constant4(); + + Emit4(lastConst); + if(code[pc] == OP_JUMP) + JUSED(lastConst); + + break; + case OP_LOCAL: + EmitPushStack(vm); + EmitString("8D 86"); // lea eax, [0x12345678 + esi] + oc0 = oc1; + oc1 = Constant4(); + Emit4(oc1); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_ARG: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("8B D6"); // mov edx, esi + EmitString("81 C2"); // add edx, 0x12345678 + Emit4((Constant1() & 0xFF)); + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_CALL: + EmitCallRel(vm, callProcOfs); + break; + case OP_PUSH: + EmitPushStack(vm); + break; + case OP_POP: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_LEAVE: + v = Constant4(); + EmitString("81 C6"); // add esi, 0x12345678 + Emit4(v); + EmitString("C3"); // ret + break; + case OP_LOAD4: + if (code[pc] == OP_CONST && code[pc+5] == OP_ADD && code[pc+6] == OP_STORE4) + { + if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { + compiledOfs -= 12; + vm->instructionPointers[instruction - 1] = compiledOfs; + } + + pc++; // OP_CONST + v = Constant4(); + + EmitMovEDXStack(vm, vm->dataMask); + if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "FF 04 11"); // inc dword ptr [r9 + edx] +#else + EmitString("FF 82"); // inc dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { +#if idx64 + EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx] +#else + EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitString("05"); // add eax, v + Emit4(v); + + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + } + + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + pc++; // OP_ADD + pc++; // OP_STORE + instruction += 3; + break; + } + + if(code[pc] == OP_CONST && code[pc+5] == OP_SUB && code[pc+6] == OP_STORE4) + { + if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { + compiledOfs -= 12; + vm->instructionPointers[instruction - 1] = compiledOfs; + } + + pc++; // OP_CONST + v = Constant4(); + + EmitMovEDXStack(vm, vm->dataMask); + if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "FF 0C 11"); // dec dword ptr [r9 + edx] +#else + EmitString("FF 8A"); // dec dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { +#if idx64 + EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx] +#else + EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitString("2D"); // sub eax, v + Emit4(v); + + if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + } + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + pc++; // OP_SUB + pc++; // OP_STORE + instruction += 3; + break; + } + + if(buf[compiledOfs - 3] == 0x89 && buf[compiledOfs - 2] == 0x04 && buf[compiledOfs - 1] == 0x9F) + { + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + MASK_REG("E0", vm->dataMask); // and eax, 0x12345678 +#if idx64 + EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax] +#else + EmitString("8B 80"); // mov eax, dword ptr [eax + 0x1234567] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + } + + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax] +#else + EmitString("8B 80"); // mov eax, dword ptr [eax + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_LOAD2: + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "0F B7 04 01"); // movzx eax, word ptr [r9 + eax] +#else + EmitString("0F B7 80"); // movzx eax, word ptr [eax + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_LOAD1: + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "0F B6 04 01"); // movzx eax, byte ptr [r9 + eax] +#else + EmitString("0F B6 80"); // movzx eax, byte ptr [eax + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_STORE4: + EmitMovEAXStack(vm, 0); + EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] + MASK_REG("E2", vm->dataMask & ~3); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + case OP_STORE2: + EmitMovEAXStack(vm, 0); + EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] + MASK_REG("E2", vm->dataMask & ~1); // and edx, 0x12345678 +#if idx64 + Emit1(0x66); // mov word ptr [r9 + edx], eax + EmitRexString(0x41, "89 04 11"); +#else + EmitString("66 89 82"); // mov word ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + case OP_STORE1: + EmitMovEAXStack(vm, 0); + EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "88 04 11"); // mov byte ptr [r9 + edx], eax +#else + EmitString("88 82"); // mov byte ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + EmitMovEAXStack(vm, 0); + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("39 44 9F 04"); // cmp eax, dword ptr 4[edi + ebx * 4] + + EmitBranchConditions(vm, op); + break; + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("D9 44 9F 04"); // fld dword ptr 4[edi + ebx * 4] + EmitString("D8 5C 9F 08"); // fcomp dword ptr 8[edi + ebx * 4] + EmitString("DF E0"); // fnstsw ax + + switch(op) + { + case OP_EQF: + EmitString("F6 C4 40"); // test ah,0x40 + EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678 + break; + case OP_NEF: + EmitString("F6 C4 40"); // test ah,0x40 + EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678 + break; + case OP_LTF: + EmitString("F6 C4 01"); // test ah,0x01 + EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678 + break; + case OP_LEF: + EmitString("F6 C4 41"); // test ah,0x41 + EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678 + break; + case OP_GTF: + EmitString("F6 C4 41"); // test ah,0x41 + EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678 + break; + case OP_GEF: + EmitString("F6 C4 01"); // test ah,0x01 + EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678 + break; + } + break; + case OP_NEGI: + EmitMovEAXStack(vm, 0); + EmitString("F7 D8"); // neg eax + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + break; + case OP_ADD: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("01 44 9F FC"); // add dword ptr -4[edi + ebx * 4], eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_SUB: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("29 44 9F FC"); // sub dword ptr -4[edi + ebx * 4], eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_DIVI: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("99"); // cdq + EmitString("F7 3C 9F"); // idiv dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_DIVU: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("33 D2"); // xor edx, edx + EmitString("F7 34 9F"); // div dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MODI: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("99" ); // cdq + EmitString("F7 3C 9F"); // idiv dword ptr [edi + ebx * 4] + EmitString("89 54 9F FC"); // mov dword ptr -4[edi + ebx * 4],edx + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MODU: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("33 D2"); // xor edx, edx + EmitString("F7 34 9F"); // div dword ptr [edi + ebx * 4] + EmitString("89 54 9F FC"); // mov dword ptr -4[edi + ebx * 4],edx + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MULI: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("F7 2C 9F"); // imul dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MULU: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("F7 24 9F"); // mul dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BAND: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("21 44 9F FC"); // and dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BOR: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("09 44 9F FC"); // or dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BXOR: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("31 44 9F FC"); // xor dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BCOM: + EmitString("F7 14 9F"); // not dword ptr [edi + ebx * 4] + break; + case OP_LSH: + EmitMovECXStack(vm); + EmitString("D3 64 9F FC"); // shl dword ptr -4[edi + ebx * 4], cl + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_RSHI: + EmitMovECXStack(vm); + EmitString("D3 7C 9F FC"); // sar dword ptr -4[edi + ebx * 4], cl + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_RSHU: + EmitMovECXStack(vm); + EmitString("D3 6C 9F FC"); // shr dword ptr -4[edi + ebx * 4], cl + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_NEGF: + EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4] + EmitString("D9 E0"); // fchs + EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4] + break; + case OP_ADDF: + EmitString("D9 44 9F FC"); // fld dword ptr -4[edi + ebx * 4] + EmitString("D8 04 9F"); // fadd dword ptr [edi + ebx * 4] + EmitString("D9 5C 9F FC"); // fstp dword ptr -4[edi + ebx * 4] + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_SUBF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4] + EmitString("D8 64 9F 04"); // fsub dword ptr 4[edi + ebx * 4] + EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4] + break; + case OP_DIVF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4] + EmitString("D8 74 9F 04"); // fdiv dword ptr 4[edi + ebx * 4] + EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4] + break; + case OP_MULF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4] + EmitString("D8 4C 9F 04"); // fmul dword ptr 4[edi + ebx * 4] + EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4] + break; + case OP_CVIF: + EmitString("DB 04 9F"); // fild dword ptr [edi + ebx * 4] + EmitString("D9 1C 9F"); // fstp dword ptr [edi + ebx * 4] + break; + case OP_CVFI: +#ifndef FTOL_PTR // WHENHELLISFROZENOVER + // not IEEE complient, but simple and fast + EmitString("D9 04 9F"); // fld dword ptr [edi + ebx * 4] + EmitString("DB 1C 9F"); // fistp dword ptr [edi + ebx * 4] +#else // FTOL_PTR + // call the library conversion function + EmitRexString(0x48, "BA"); // mov edx, Q_VMftol + EmitPtr((void*)Q_VMftol); + EmitRexString(0x48, "FF D2"); // call edx + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax +#endif + break; + case OP_SEX8: + EmitString("0F BE 04 9F"); // movsx eax, byte ptr [edi + ebx * 4] + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_SEX16: + EmitString("0F BF 04 9F"); // movsx eax, word ptr [edi + ebx * 4] + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + + case OP_BLOCK_COPY: + EmitString("B8"); // mov eax, 0x12345678 + Emit4(VM_BLOCK_COPY); + EmitString("B9"); // mov ecx, 0x12345678 + Emit4(Constant4()); + + EmitCallRel(vm, callDoSyscallOfs); + + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + + case OP_JUMP: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("8B 44 9F 04"); // mov eax, dword ptr 4[edi + ebx * 4] + EmitString("81 F8"); // cmp eax, vm->instructionCount + Emit4(vm->instructionCount); +#if idx64 + EmitString("73 04"); // jae +4 + EmitRexString(0x49, "FF 24 C0"); // jmp qword ptr [r8 + eax * 8] +#else + EmitString("73 07"); // jae +7 + EmitString("FF 24 85"); // jmp dword ptr [instructionPointers + eax * 4] + Emit4((intptr_t) vm->instructionPointers); +#endif + EmitCallErrJump(vm, callDoSyscallOfs); + break; + default: + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc); + } + pop0 = pop1; + pop1 = op; + } + } + + // copy to an exact sized buffer with the appropriate permission bits + vm->codeLength = compiledOfs; +#ifdef VM_X86_MMAP + vm->codeBase = (byte*)mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if(vm->codeBase == MAP_FAILED) + Com_Error(ERR_FATAL, "VM_CompileX86: can't mmap memory"); +#elif _WIN32 + // allocate memory with EXECUTE permissions under windows. + vm->codeBase = (byte*)VirtualAlloc(NULL, compiledOfs, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if(!vm->codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86: VirtualAlloc failed"); +#else + vm->codeBase = malloc(compiledOfs); + if(!vm->codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86: malloc failed"); +#endif + + ::memcpy( vm->codeBase, buf, compiledOfs ); + +#ifdef VM_X86_MMAP + if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC)) + Com_Error(ERR_FATAL, "VM_CompileX86: mprotect failed"); +#elif _WIN32 + { + DWORD oldProtect = 0; + + // remove write permissions. + if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) + Com_Error(ERR_FATAL, "VM_CompileX86: VirtualProtect failed"); + } +#endif + + Z_Free( code ); + Z_Free( buf ); + Z_Free( jused ); + Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs ); + + vm->destroy = VM_Destroy_Compiled; + + // offset all the instruction pointers for the new location + for ( i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (intptr_t) vm->codeBase; + } +} + +void VM_Destroy_Compiled(vm_t* self) +{ +#ifdef VM_X86_MMAP + munmap(self->codeBase, self->codeLength); +#elif _WIN32 + VirtualFree(self->codeBase, 0, MEM_RELEASE); +#else + free(self->codeBase); +#endif +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ + +#if defined(_MSC_VER) && defined(idx64) +extern uint8_t qvmcall64(int *programStack, int *opStack, intptr_t *instructionPointers, byte *dataBase); +#endif + +int VM_CallCompiled(vm_t *vm, int *args) +{ + byte stack[OPSTACK_SIZE + 15]; + void *entryPoint; + int programStack, stackOnEntry; + byte *image; + int *opStack; + int opStackOfs; + int arg; + + currentVM = vm; + + // interpret the code + vm->currentlyInterpreting = true; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + + // set up the stack frame + image = vm->dataBase; + + programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); + + for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ ) + *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ]; + + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + entryPoint = vm->codeBase + vm->entryOfs; + opStack = (int*)PADP(stack, 16); + *opStack = 0xDEADBEEF; + opStackOfs = 0; + +#ifdef _MSC_VER + #if idx64 + opStackOfs = qvmcall64(&programStack, opStack, vm->instructionPointers, vm->dataBase); + #else + __asm + { + pushad + + mov esi, dword ptr programStack + mov edi, dword ptr opStack + mov ebx, dword ptr opStackOfs + + call entryPoint + + mov dword ptr opStackOfs, ebx + mov dword ptr opStack, edi + mov dword ptr programStack, esi + + popad + } + #endif +#elif idx64 + __asm__ volatile( + "movq %5, %%rax\n" + "movq %3, %%r8\n" + "movq %4, %%r9\n" + "push %%r15\n" + "push %%r14\n" + "push %%r13\n" + "push %%r12\n" + "callq *%%rax\n" + "pop %%r12\n" + "pop %%r13\n" + "pop %%r14\n" + "pop %%r15\n" + : "+S" (programStack), "+D" (opStack), "+b" (opStackOfs) + : "g" (vm->instructionPointers), "g" (vm->dataBase), "g" (entryPoint) + : "cc", "memory", "%rax", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11" + ); +#else + __asm__ volatile( + "calll *%3\n" + : "+S" (programStack), "+D" (opStack), "+b" (opStackOfs) + : "g" (entryPoint) + : "cc", "memory", "%eax", "%ecx", "%edx" + ); +#endif + + if(opStackOfs != 1 || *opStack != 0xDEADBEEF) + { + Com_Error(ERR_DROP, "opStack corrupted in compiled code"); + } + if(programStack != stackOnEntry - (8 + 4 * MAX_VMMAIN_ARGS)) + Com_Error(ERR_DROP, "programStack corrupted in compiled code"); + + vm->programStack = stackOnEntry; + + return opStack[opStackOfs]; +} diff --git a/src/renderer/tr_types.h b/src/renderer/tr_types.h deleted file mode 100644 index 6db8c12..0000000 --- a/src/renderer/tr_types.h +++ /dev/null @@ -1,234 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous 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. - -Tremulous 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#ifndef __TR_TYPES_H -#define __TR_TYPES_H - - -#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces -#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing - -// renderfx flags -#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) -#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) -#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) -#define RF_DEPTHHACK 8 // for view weapon Z crunching -#define RF_NOSHADOW 64 // don't add stencil shadows - -#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin - // for lighting. This allows entities to sink into the floor - // with their origin going solid, and allows all parts of a - // player to get the same lighting -#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane -#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous - // animation without needing to know the frame count - -// refdef flags -#define RDF_NOWORLDMODEL 1 // used for player configuration screen -#define RDF_HYPERSPACE 4 // teleportation effect - -typedef struct { - vec3_t xyz; - float st[2]; - byte modulate[4]; -} polyVert_t; - -typedef struct poly_s { - qhandle_t hShader; - int numVerts; - polyVert_t *verts; -} poly_t; - -typedef enum { - RT_MODEL, - RT_POLY, - RT_SPRITE, - RT_BEAM, - RT_RAIL_CORE, - RT_RAIL_RINGS, - RT_LIGHTNING, - RT_PORTALSURFACE, // doesn't draw anything, just info for portals - - RT_MAX_REF_ENTITY_TYPE -} refEntityType_t; - -typedef struct { - refEntityType_t reType; - int renderfx; - - qhandle_t hModel; // opaque type outside refresh - - // most recent data - vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) - float shadowPlane; // projection shadows go here, stencils go slightly lower - - vec3_t axis[3]; // rotation vectors - qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale - float origin[3]; // also used as MODEL_BEAM's "from" - int frame; // also used as MODEL_BEAM's diameter - - // previous data for frame interpolation - float oldorigin[3]; // also used as MODEL_BEAM's "to" - int oldframe; - float backlerp; // 0.0 = current, 1.0 = old - - // texturing - int skinNum; // inline skin index - qhandle_t customSkin; // NULL for default skin - qhandle_t customShader; // use one image for the entire thing - - // misc - byte shaderRGBA[4]; // colors used by rgbgen entity shaders - float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers - float shaderTime; // subtracted from refdef time to control effect start times - - // extra sprite information - float radius; - float rotation; -} refEntity_t; - - -#define MAX_RENDER_STRINGS 8 -#define MAX_RENDER_STRING_LENGTH 32 - -typedef struct { - int x, y, width, height; - float fov_x, fov_y; - vec3_t vieworg; - vec3_t viewaxis[3]; // transformation matrix - - // time in milliseconds for shader effects and other time dependent rendering issues - int time; - - int rdflags; // RDF_NOWORLDMODEL, etc - - // 1 bits will prevent the associated area from rendering at all - byte areamask[MAX_MAP_AREA_BYTES]; - - // text messages for deform text shaders - char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; -} refdef_t; - - -typedef enum { - STEREO_CENTER, - STEREO_LEFT, - STEREO_RIGHT -} stereoFrame_t; - - -/* -** glconfig_t -** -** Contains variables specific to the OpenGL configuration -** being run right now. These are constant once the OpenGL -** subsystem is initialized. -*/ -typedef enum { - TC_NONE, - TC_S3TC -} textureCompression_t; - -typedef enum { - GLDRV_ICD, // driver is integrated with window system - // WARNING: there are tests that check for - // > GLDRV_ICD for minidriverness, so this - // should always be the lowest value in this - // enum set - GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver - GLDRV_VOODOO // driver is a 3Dfx standalone driver -} glDriverType_t; - -typedef enum { - GLHW_GENERIC, // where everthing works the way it should - GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is - // the hardware type then there can NOT exist a secondary - // display adapter - GLHW_RIVA128, // where you can't interpolate alpha - GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures - GLHW_PERMEDIA2 // where you don't have src*dst -} glHardwareType_t; - -typedef struct { - char renderer_string[MAX_STRING_CHARS]; - char vendor_string[MAX_STRING_CHARS]; - char version_string[MAX_STRING_CHARS]; - char extensions_string[BIG_INFO_STRING]; - - int maxTextureSize; // queried from GL - int maxActiveTextures; // multitexture ability - - int colorBits, depthBits, stencilBits; - - glDriverType_t driverType; - glHardwareType_t hardwareType; - - qboolean deviceSupportsGamma; - textureCompression_t textureCompression; - qboolean textureEnvAddAvailable; - - int vidWidth, vidHeight; - // aspect is the screen's physical width / height, which may be different - // than scrWidth / scrHeight if the pixels are non-square - // normal screens should be 4/3, but wide aspect monitors may be 16/9 - float windowAspect; - - int displayFrequency; - - // synonymous with "does rendering consume the entire screen?", therefore - // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that - // used CDS. - qboolean isFullscreen; - qboolean stereoEnabled; - qboolean smpActive; // dual processor - - qboolean textureFilterAnisotropic; - int maxAnisotropy; - -} glconfig_t; - -// FIXME: VM should be OS agnostic .. in theory - -/* -#ifdef Q3_VM - -#define _3DFX_DRIVER_NAME "Voodoo" -#define OPENGL_DRIVER_NAME "Default" - -#elif defined(_WIN32) -*/ - -#if defined(Q3_VM) || defined(_WIN32) - -#define _3DFX_DRIVER_NAME "3dfxvgl" -#define OPENGL_DRIVER_NAME "opengl32" - -#else - -#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 -#define OPENGL_DRIVER_NAME "libGL.so.1" - -#endif // !defined _WIN32 - -#endif // __TR_TYPES_H diff --git a/src/renderercommon/CMakeLists.txt b/src/renderercommon/CMakeLists.txt new file mode 100644 index 0000000..6464d32 --- /dev/null +++ b/src/renderercommon/CMakeLists.txt @@ -0,0 +1,77 @@ +include(${CMAKE_SOURCE_DIR}/cmake/SDL2.cmake) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c ${SDL2_INCLUDE_DIRS} ) + +add_library( + renderercommon STATIC + iqm.h + qgl.h + tr_common.h + tr_font.cpp + tr_image_bmp.cpp + tr_image_jpg.cpp + tr_image_pcx.cpp + tr_image_png.cpp + tr_image_tga.cpp + tr_noise.cpp + tr_public.h + tr_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/../sdl/sdl_gamma.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../sdl/sdl_glimp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jaricom.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcapimin.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcapistd.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcarith.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jccoefct.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jccolor.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcdctmgr.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jchuff.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcinit.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcmainct.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcmarker.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcmaster.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcomapi.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcparam.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcprepct.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jcsample.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jctrans.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdapimin.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdapistd.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdarith.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdatadst.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdatasrc.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdcoefct.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdcolor.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jddctmgr.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdhuff.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdinput.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdmainct.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdmarker.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdmaster.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdmerge.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdpostct.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdsample.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jdtrans.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jerror.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jfdctflt.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jfdctfst.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jfdctint.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jidctflt.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jidctfst.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jidctint.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jmemmgr.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jmemnobs.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jquant1.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jquant2.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/jpeg-8c/jutils.c + ) + +add_definitions( + -DFLOATING_POINT + -DHAVE_LRINTF + -DUSE_INTERNAL_JPEG + -DUSE_RENDERER_DLOPEN + ${SDL2_DEFINES} + ) + +target_link_libraries( renderercommon ${SDL2_LIBRARIES} ) diff --git a/src/renderercommon/iqm.h b/src/renderercommon/iqm.h new file mode 100644 index 0000000..3011efc --- /dev/null +++ b/src/renderercommon/iqm.h @@ -0,0 +1,131 @@ +/* +=========================================================================== +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __IQM_H__ +#define __IQM_H__ + +#define IQM_MAGIC "INTERQUAKEMODEL" +#define IQM_VERSION 2 + +#define IQM_MAX_JOINTS 128 + +typedef struct iqmheader +{ + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +} iqmHeader_t; + +typedef struct iqmmesh +{ + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; +} iqmMesh_t; + +enum +{ + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, + IQM_CUSTOM = 0x10 +}; + +enum +{ + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8, +}; + +typedef struct iqmtriangle +{ + unsigned int vertex[3]; +} iqmTriangle_t; + +typedef struct iqmjoint +{ + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; +} iqmJoint_t; + +typedef struct iqmpose +{ + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; +} iqmPose_t; + +typedef struct iqmanim +{ + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; +} iqmAnim_t; + +enum +{ + IQM_LOOP = 1<<0 +}; + +typedef struct iqmvertexarray +{ + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +} iqmVertexArray_t; + +typedef struct iqmbounds +{ + float bbmin[3], bbmax[3]; + float xyradius, radius; +} iqmBounds_t; + +#endif diff --git a/src/renderercommon/qgl.h b/src/renderercommon/qgl.h new file mode 100644 index 0000000..4411daf --- /dev/null +++ b/src/renderercommon/qgl.h @@ -0,0 +1,570 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#ifdef USE_LOCAL_HEADERS +# include "SDL_opengl.h" +#else +# include +#endif + +extern void (APIENTRYP qglActiveTextureARB) (GLenum texture); +extern void (APIENTRYP qglClientActiveTextureARB) (GLenum texture); +extern void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); + +extern void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count); +extern void (APIENTRYP qglUnlockArraysEXT) (void); + + +//=========================================================================== + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + +// GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a + +// OpenGL 1.3, was GL_ARB_texture_compression +#define QGL_1_3_PROCS \ + GLE(void, ActiveTexture, GLenum texture) \ + GLE(void, CompressedTexImage2D, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data) \ + GLE(void, CompressedTexSubImage2D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data) \ + +// OpenGL 1.5, was GL_ARB_vertex_buffer_object and GL_ARB_occlusion_query +#define QGL_1_5_PROCS \ + GLE(void, GenQueries, GLsizei n, GLuint *ids) \ + GLE(void, DeleteQueries, GLsizei n, const GLuint *ids) \ + GLE(void, BeginQuery, GLenum target, GLuint id) \ + GLE(void, EndQuery, GLenum target) \ + GLE(void, GetQueryObjectiv, GLuint id, GLenum pname, GLint *params) \ + GLE(void, GetQueryObjectuiv, GLuint id, GLenum pname, GLuint *params) \ + GLE(void, BindBuffer, GLenum target, GLuint buffer) \ + GLE(void, DeleteBuffers, GLsizei n, const GLuint *buffers) \ + GLE(void, GenBuffers, GLsizei n, GLuint *buffers) \ + GLE(void, BufferData, GLenum target, GLsizeiptr size, const void *data, GLenum usage) \ + GLE(void, BufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, const void *data) \ + +// OpenGL 2.0, was GL_ARB_shading_language_100, GL_ARB_vertex_program, GL_ARB_shader_objects, and GL_ARB_vertex_shader +#define QGL_2_0_PROCS \ + GLE(void, AttachShader, GLuint program, GLuint shader) \ + GLE(void, BindAttribLocation, GLuint program, GLuint index, const GLchar *name) \ + GLE(void, CompileShader, GLuint shader) \ + GLE(GLuint, CreateProgram, void) \ + GLE(GLuint, CreateShader, GLenum type) \ + GLE(void, DeleteProgram, GLuint program) \ + GLE(void, DeleteShader, GLuint shader) \ + GLE(void, DetachShader, GLuint program, GLuint shader) \ + GLE(void, DisableVertexAttribArray, GLuint index) \ + GLE(void, EnableVertexAttribArray, GLuint index) \ + GLE(void, GetActiveUniform, GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name) \ + GLE(void, GetProgramiv, GLuint program, GLenum pname, GLint *params) \ + GLE(void, GetProgramInfoLog, GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) \ + GLE(void, GetShaderiv, GLuint shader, GLenum pname, GLint *params) \ + GLE(void, GetShaderInfoLog, GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) \ + GLE(void, GetShaderSource, GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source) \ + GLE(GLint, GetUniformLocation, GLuint program, const GLchar *name) \ + GLE(void, LinkProgram, GLuint program) \ + GLE(void, ShaderSource, GLuint shader, GLsizei count, const GLchar* *string, const GLint *length) \ + GLE(void, UseProgram, GLuint program) \ + GLE(void, Uniform1f, GLint location, GLfloat v0) \ + GLE(void, Uniform2f, GLint location, GLfloat v0, GLfloat v1) \ + GLE(void, Uniform3f, GLint location, GLfloat v0, GLfloat v1, GLfloat v2) \ + GLE(void, Uniform4f, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) \ + GLE(void, Uniform1i, GLint location, GLint v0) \ + GLE(void, Uniform1fv, GLint location, GLsizei count, const GLfloat *value) \ + GLE(void, UniformMatrix4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) \ + GLE(void, ValidateProgram, GLuint program) \ + GLE(void, VertexAttribPointer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer) \ + +// GL_NVX_gpu_memory_info +#ifndef GL_NVX_gpu_memory_info +#define GL_NVX_gpu_memory_info +#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047 +#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048 +#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 +#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A +#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B +#endif + +// GL_ATI_meminfo +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif + +// GL_ARB_texture_float +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +// OpenGL 3.0 specific +#define QGL_3_0_PROCS \ + GLE(const GLubyte *, GetStringi, GLenum name, GLuint index) \ + +// GL_ARB_framebuffer_object, built-in to OpenGL 3.0 +#define QGL_ARB_framebuffer_object_PROCS \ + GLE(void, BindRenderbuffer, GLenum target, GLuint renderbuffer) \ + GLE(void, DeleteRenderbuffers, GLsizei n, const GLuint *renderbuffers) \ + GLE(void, GenRenderbuffers, GLsizei n, GLuint *renderbuffers) \ + GLE(void, RenderbufferStorage, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) \ + GLE(void, BindFramebuffer, GLenum target, GLuint framebuffer) \ + GLE(void, DeleteFramebuffers, GLsizei n, const GLuint *framebuffers) \ + GLE(void, GenFramebuffers, GLsizei n, GLuint *framebuffers) \ + GLE(GLenum, CheckFramebufferStatus, GLenum target) \ + GLE(void, FramebufferTexture2D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) \ + GLE(void, FramebufferRenderbuffer, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) \ + GLE(void, GenerateMipmap, GLenum target) \ + GLE(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) \ + GLE(void, RenderbufferStorageMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) \ + +// GL_ARB_vertex_array_object, built-in to OpenGL 3.0 +#define QGL_ARB_vertex_array_object_PROCS \ + GLE(void, BindVertexArray, GLuint array) \ + GLE(void, DeleteVertexArrays, GLsizei n, const GLuint *arrays) \ + GLE(void, GenVertexArrays, GLsizei n, GLuint *arrays) \ + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#endif + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp +#define GL_DEPTH_CLAMP 0x864F +#endif + +#ifndef GL_ARB_seamless_cube_map +#define GL_ARB_seamless_cube_map +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#endif + +// GL_EXT_direct_state_access +#define QGL_EXT_direct_state_access_PROCS \ + GLE(GLvoid, BindMultiTextureEXT, GLenum texunit, GLenum target, GLuint texture) \ + GLE(GLvoid, TextureParameterfEXT, GLuint texture, GLenum target, GLenum pname, GLfloat param) \ + GLE(GLvoid, TextureParameteriEXT, GLuint texture, GLenum target, GLenum pname, GLint param) \ + GLE(GLvoid, TextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) \ + GLE(GLvoid, TextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) \ + GLE(GLvoid, CopyTextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) \ + GLE(GLvoid, CompressedTextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) \ + GLE(GLvoid, CompressedTextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data) \ + GLE(GLvoid, GenerateTextureMipmapEXT, GLuint texture, GLenum target) \ + GLE(GLvoid, ProgramUniform1iEXT, GLuint program, GLint location, GLint v0) \ + GLE(GLvoid, ProgramUniform1fEXT, GLuint program, GLint location, GLfloat v0) \ + GLE(GLvoid, ProgramUniform2fEXT, GLuint program, GLint location, GLfloat v0, GLfloat v1) \ + GLE(GLvoid, ProgramUniform3fEXT, GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2) \ + GLE(GLvoid, ProgramUniform4fEXT, GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) \ + GLE(GLvoid, ProgramUniform1fvEXT, GLuint program, GLint location, GLsizei count, const GLfloat *value) \ + GLE(GLvoid, ProgramUniformMatrix4fvEXT, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) \ + GLE(GLvoid, NamedRenderbufferStorageEXT, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) \ + GLE(GLvoid, NamedRenderbufferStorageMultisampleEXT, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) \ + GLE(GLenum, CheckNamedFramebufferStatusEXT, GLuint framebuffer, GLenum target) \ + GLE(GLvoid, NamedFramebufferTexture2DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level) \ + GLE(GLvoid, NamedFramebufferRenderbufferEXT, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) \ + +#define GLE(ret, name, ...) typedef ret APIENTRY name##proc(__VA_ARGS__); extern name##proc * qgl##name; +QGL_1_3_PROCS; +QGL_1_5_PROCS; +QGL_2_0_PROCS; +QGL_3_0_PROCS; +QGL_ARB_framebuffer_object_PROCS; +QGL_ARB_vertex_array_object_PROCS; +QGL_EXT_direct_state_access_PROCS; +#undef GLE + +#endif diff --git a/src/renderercommon/tr_common.h b/src/renderercommon/tr_common.h new file mode 100644 index 0000000..68e012f --- /dev/null +++ b/src/renderercommon/tr_common.h @@ -0,0 +1,166 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef TR_COMMON_H +#define TR_COMMON_H + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "sys/sys_shared.h" + +#include "iqm.h" +#include "qgl.h" +#include "tr_public.h" + +typedef enum +{ + IMGTYPE_COLORALPHA, // for color, lightmap, diffuse, and specular + IMGTYPE_NORMAL, + IMGTYPE_NORMALHEIGHT, + IMGTYPE_DELUXE, // normals are swizzled, deluxe are not +} imgType_t; + +typedef enum +{ + IMGFLAG_NONE = 0x0000, + IMGFLAG_MIPMAP = 0x0001, + IMGFLAG_PICMIP = 0x0002, + IMGFLAG_CUBEMAP = 0x0004, + IMGFLAG_NO_COMPRESSION = 0x0010, + IMGFLAG_NOLIGHTSCALE = 0x0020, + IMGFLAG_CLAMPTOEDGE = 0x0040, + IMGFLAG_GENNORMALMAP = 0x0100, +} imgFlags_t; + +typedef struct image_s { + char imgName[MAX_QPATH]; // game path, including extension + int width, height; // source image + int uploadWidth, uploadHeight; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE + GLuint texnum; // gl texture binding + + int frameUsed; // for texture usage in frame statistics + + int internalFormat; + int TMU; // only needed for voodoo2 + + imgType_t type; + int /*imgFlags_t*/ flags; + + struct image_s* next; +} image_t; + +// any change in the LIGHTMAP_* defines here MUST be reflected in +// R_FindShader() in tr_bsp.c +#define LIGHTMAP_2D -4 // shader is for 2D rendering +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models +#define LIGHTMAP_WHITEIMAGE -2 +#define LIGHTMAP_NONE -1 + +extern refimport_t ri; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init + +// +// cvars +// +extern cvar_t *r_stencilbits; // number of desired stencil bits +extern cvar_t *r_depthbits; // number of desired depth bits +extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen +extern cvar_t *r_alphabits; // number of desired alpha bits +extern cvar_t *r_texturebits; // number of desired texture bits +extern cvar_t *r_ext_multisample; + // 0 = use framebuffer depth + // 16 = use 16-bit textures + // 32 = use 32-bit textures + // all else = error + +extern cvar_t *r_width; +extern cvar_t *r_height; +extern cvar_t *r_pixelAspect; +extern cvar_t *r_noborder; +extern cvar_t *r_fullscreen; +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities +extern cvar_t *r_drawBuffer; +extern cvar_t *r_swapInterval; + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; + +extern cvar_t *r_ext_texture_filter_anisotropic; +extern cvar_t *r_ext_max_anisotropy; + +extern cvar_t *r_stereoEnabled; + +extern cvar_t *r_saveFontData; + +qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ); + +float R_NoiseGet4f( float x, float y, float z, double t ); +void R_NoiseInit( void ); + +image_t *R_FindImageFile( const char *name, imgType_t type, int/*imgFlags_t*/ flags ); +image_t *R_CreateImage( const char *name, byte *pic, int width, int height, imgType_t type, int flags, int internalFormat ); + +void R_IssuePendingRenderCommands( void ); +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ); +qhandle_t RE_RegisterShader( const char *name ); +qhandle_t RE_RegisterShaderNoMip( const char *name ); +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage); + +// font stuff +void R_InitFreeType( void ); +void R_DoneFreeType( void ); +void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); + +/* +============================================================= + +IMAGE LOADERS + +============================================================= +*/ + +void R_LoadBMP( const char *name, byte **pic, int *width, int *height ); +void R_LoadJPG( const char *name, byte **pic, int *width, int *height ); +void R_LoadPCX( const char *name, byte **pic, int *width, int *height ); +void R_LoadPNG( const char *name, byte **pic, int *width, int *height ); +void R_LoadTGA( const char *name, byte **pic, int *width, int *height ); + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_Init( bool ); +void GLimp_Shutdown( void ); +void GLimp_EndFrame( void ); +void GLimp_LogComment(const char *comment); +void GLimp_Minimize(void); +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ); + +#endif diff --git a/src/renderercommon/tr_font.cpp b/src/renderercommon/tr_font.cpp new file mode 100644 index 0000000..c93b462 --- /dev/null +++ b/src/renderercommon/tr_font.cpp @@ -0,0 +1,562 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_font.c +// +// +// The font system uses FreeType 2.x to render TrueType fonts for use within the game. +// As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and +// about 90% of the cgame presentation. A few areas of the CGAME were left uses the old +// fonts since the code is shared with standard Q3A. +// +// If you include this font rendering code in a commercial product you MUST include the +// following somewhere with your product, see www.freetype.org for specifics or changes. +// The Freetype code also uses some hinting techniques that MIGHT infringe on patents +// held by apple so be aware of that also. +// +// As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code +// disabled. This removes any potential patent issues and it keeps us from having to +// distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require +// an act of god to accomplish. +// +// What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType +// credit in the credits ) and then saved off the glyph data and then hand touched up the +// font bitmaps so they scale a bit better in GL. +// +// There are limitations in the way fonts are saved and reloaded in that it is based on +// point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point +// you will end up with a single 18 point data file and image set. Typically you will want to +// choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system +// +// In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we +// use three or four scales, most of them exactly equaling the specific rendered size. We +// rendered three sizes in Team Arena, 12, 16, and 20. +// +// To generate new font data you need to go through the following steps. +// 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path. +// 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and +// point size. the original TrueType fonts must exist in fonts at this point. +// 3. run the game, you should see things normally. +// 4. Exit the game and there will be three dat files and at least three tga files. The +// tga's are in 256x256 pages so if it takes three images to render a 24 point font you +// will end up with fontImage_0_24.tga through fontImage_2_24.tga +// 5. In future runs of the game, the system looks for these images and data files when a s +// specific point sized font is rendered and loads them for use. +// 6. Because of the original beta nature of the FreeType code you will probably want to hand +// touch the font bitmaps. +// +// Currently a define in the project turns on or off the FreeType code which is currently +// defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and +// uncheck the exclude from build check box in the FreeType2 area of the Renderer project. + +#include "tr_common.h" + +#include "qcommon/qcommon.h" + +#ifdef BUILD_FREETYPE +#include +#include FT_FREETYPE_H +#include FT_ERRORS_H +#include FT_SYSTEM_H +#include FT_IMAGE_H +#include FT_OUTLINE_H + +#define _FLOOR(x) ((x) & -64) +#define _CEIL(x) (((x)+63) & -64) +#define _TRUNC(x) ((x) >> 6) + +FT_Library ftLibrary = NULL; +#endif + +#define MAX_FONTS 6 +static int registeredFontCount = 0; +static fontInfo_t registeredFont[MAX_FONTS]; + +#ifdef BUILD_FREETYPE +void R_GetGlyphInfo(FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch) { + *left = _FLOOR( glyph->metrics.horiBearingX ); + *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width ); + *width = _TRUNC(*right - *left); + + *top = _CEIL( glyph->metrics.horiBearingY ); + *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height ); + *height = _TRUNC( *top - *bottom ); + *pitch = ( qtrue ? (*width+3) & -4 : (*width+7) >> 3 ); +} + + +FT_Bitmap *R_RenderGlyph(FT_GlyphSlot glyph, glyphInfo_t* glyphOut) { + FT_Bitmap *bit2; + int left, right, width, top, bottom, height, pitch, size; + + R_GetGlyphInfo(glyph, &left, &right, &width, &top, &bottom, &height, &pitch); + + if ( glyph->format == ft_glyph_format_outline ) { + size = pitch*height; + + bit2 = ri.Malloc(sizeof(FT_Bitmap)); + + bit2->width = width; + bit2->rows = height; + bit2->pitch = pitch; + bit2->pixel_mode = ft_pixel_mode_grays; + //bit2->pixel_mode = ft_pixel_mode_mono; + bit2->buffer = ri.Malloc(pitch*height); + bit2->num_grays = 256; + + Com_Memset( bit2->buffer, 0, size ); + + FT_Outline_Translate( &glyph->outline, -left, -bottom ); + + FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 ); + + glyphOut->height = height; + glyphOut->pitch = pitch; + glyphOut->top = (glyph->metrics.horiBearingY >> 6) + 1; + glyphOut->bottom = bottom; + + return bit2; + } else { + ri.Printf(PRINT_ALL, "Non-outline fonts are not supported\n"); + } + return NULL; +} + +void WriteTGA (char *filename, byte *data, int width, int height) { + byte *buffer; + int i, c; + int row; + unsigned char *flip; + unsigned char *src, *dst; + + buffer = ri.Malloc(width*height*4 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 32; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 4; + for (i=18 ; iglyph, &glyph); + if (bitmap) { + glyph.xSkip = (face->glyph->metrics.horiAdvance >> 6) + 1; + } else { + return &glyph; + } + + if (glyph.height > *maxHeight) { + *maxHeight = glyph.height; + } + + if (calcHeight) { + ri.Free(bitmap->buffer); + ri.Free(bitmap); + return &glyph; + } + +/* + // need to convert to power of 2 sizes so we do not get + // any scaling from the gl upload + for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1) + ; +*/ + + scaled_width = glyph.pitch; + scaled_height = glyph.height; + + // we need to make sure we fit + if (*xOut + scaled_width + 1 >= 255) { + *xOut = 0; + *yOut += *maxHeight + 1; + } + + if (*yOut + *maxHeight + 1 >= 255) { + *yOut = -1; + *xOut = -1; + ri.Free(bitmap->buffer); + ri.Free(bitmap); + return &glyph; + } + + + src = bitmap->buffer; + dst = imageOut + (*yOut * 256) + *xOut; + + if (bitmap->pixel_mode == ft_pixel_mode_mono) { + for (i = 0; i < glyph.height; i++) { + int j; + unsigned char *_src = src; + unsigned char *_dst = dst; + unsigned char mask = 0x80; + unsigned char val = *_src; + for (j = 0; j < glyph.pitch; j++) { + if (mask == 0x80) { + val = *_src++; + } + if (val & mask) { + *_dst = 0xff; + } + mask >>= 1; + + if ( mask == 0 ) { + mask = 0x80; + } + _dst++; + } + + src += glyph.pitch; + dst += 256; + } + } else { + for (i = 0; i < glyph.height; i++) { + Com_Memcpy(dst, src, glyph.pitch); + src += glyph.pitch; + dst += 256; + } + } + + // we now have an 8 bit per pixel grey scale bitmap + // that is width wide and pf->ftSize->metrics.y_ppem tall + + glyph.imageHeight = scaled_height; + glyph.imageWidth = scaled_width; + glyph.s = (float)*xOut / 256; + glyph.t = (float)*yOut / 256; + glyph.s2 = glyph.s + (float)scaled_width / 256; + glyph.t2 = glyph.t + (float)scaled_height / 256; + + *xOut += scaled_width + 1; + + ri.Free(bitmap->buffer); + ri.Free(bitmap); + } + + return &glyph; +} +#endif + +static int fdOffset; +static byte *fdFile; + +int readInt( void ) { + int i = fdFile[fdOffset]+(fdFile[fdOffset+1]<<8)+(fdFile[fdOffset+2]<<16)+(fdFile[fdOffset+3]<<24); + fdOffset += 4; + return i; +} + +typedef union { + byte fred[4]; + float ffred; +} poor; + +float readFloat( void ) { + poor me; +#if defined Q3_BIG_ENDIAN + me.fred[0] = fdFile[fdOffset+3]; + me.fred[1] = fdFile[fdOffset+2]; + me.fred[2] = fdFile[fdOffset+1]; + me.fred[3] = fdFile[fdOffset+0]; +#elif defined Q3_LITTLE_ENDIAN + me.fred[0] = fdFile[fdOffset+0]; + me.fred[1] = fdFile[fdOffset+1]; + me.fred[2] = fdFile[fdOffset+2]; + me.fred[3] = fdFile[fdOffset+3]; +#endif + fdOffset += 4; + return me.ffred; +} + +void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { +#ifdef BUILD_FREETYPE + FT_Face face; + int j, k, xOut, yOut, lastStart, imageNumber; + int scaledSize, newSize, maxHeight, left; + unsigned char *out, *imageBuff; + glyphInfo_t *glyph; + image_t *image; + qhandle_t h; + float max; + float dpi = 72; + float glyphScale; +#endif + void *faceData; + int i, len; + char name[1024]; + + if (!fontName) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: called with empty name\n"); + return; + } + + if (pointSize <= 0) { + pointSize = 12; + } + + R_IssuePendingRenderCommands(); + + if (registeredFontCount >= MAX_FONTS) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: Too many fonts registered already.\n"); + return; + } + + Com_sprintf(name, sizeof(name), "fonts/fontImage_%i.dat",pointSize); + for (i = 0; i < registeredFontCount; i++) { + if (Q_stricmp(name, registeredFont[i].name) == 0) { + Com_Memcpy(font, ®isteredFont[i], sizeof(fontInfo_t)); + return; + } + } + + len = ri.FS_ReadFile(name, NULL); + if (len == sizeof(fontInfo_t)) { + ri.FS_ReadFile(name, &faceData); + fdOffset = 0; + fdFile = (byte*)faceData; + for(i=0; iglyphs[i].height = readInt(); + font->glyphs[i].top = readInt(); + font->glyphs[i].bottom = readInt(); + font->glyphs[i].pitch = readInt(); + font->glyphs[i].xSkip = readInt(); + font->glyphs[i].imageWidth = readInt(); + font->glyphs[i].imageHeight = readInt(); + font->glyphs[i].s = readFloat(); + font->glyphs[i].t = readFloat(); + font->glyphs[i].s2 = readFloat(); + font->glyphs[i].t2 = readFloat(); + font->glyphs[i].glyph = readInt(); + Q_strncpyz(font->glyphs[i].shaderName, (const char *)&fdFile[fdOffset], sizeof(font->glyphs[i].shaderName)); + fdOffset += sizeof(font->glyphs[i].shaderName); + } + font->glyphScale = readFloat(); + Com_Memcpy(font->name, &fdFile[fdOffset], MAX_QPATH); + +// Com_Memcpy(font, faceData, sizeof(fontInfo_t)); + Q_strncpyz(font->name, name, sizeof(font->name)); + for (i = GLYPH_START; i <= GLYPH_END; i++) { + font->glyphs[i].glyph = RE_RegisterShaderNoMip(font->glyphs[i].shaderName); + } + Com_Memcpy(®isteredFont[registeredFontCount++], font, sizeof(fontInfo_t)); + ri.FS_FreeFile(faceData); + return; + } + +#ifndef BUILD_FREETYPE + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType code not available\n"); +#else + if (ftLibrary == NULL) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType not initialized.\n"); + return; + } + + len = ri.FS_ReadFile(fontName, &faceData); + if (len <= 0) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: Unable to read font file '%s'\n", fontName); + return; + } + + // allocate on the stack first in case we fail + if (FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face )) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to allocate new face.\n"); + return; + } + + + if (FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi)) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to set face char size.\n"); + return; + } + + //*font = ®isteredFonts[registeredFontCount++]; + + // make a 256x256 image buffer, once it is full, register it, clean it and keep going + // until all glyphs are rendered + + out = ri.Malloc(256*256); + if (out == NULL) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: ri.Malloc failure during output image creation.\n"); + return; + } + Com_Memset(out, 0, 256*256); + + maxHeight = 0; + + for (i = GLYPH_START; i <= GLYPH_END; i++) { + RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue); + } + + xOut = 0; + yOut = 0; + i = GLYPH_START; + lastStart = i; + imageNumber = 0; + + while ( i <= GLYPH_END + 1 ) { + + if ( i == GLYPH_END + 1 ) { + // upload/save current image buffer + xOut = yOut = -1; + } else { + glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse); + } + + if (xOut == -1 || yOut == -1) { + // ran out of room + // we need to create an image from the bitmap, set all the handles in the glyphs to this point + // + + scaledSize = 256*256; + newSize = scaledSize * 4; + imageBuff = ri.Malloc(newSize); + left = 0; + max = 0; + for ( k = 0; k < (scaledSize) ; k++ ) { + if (max < out[k]) { + max = out[k]; + } + } + + if (max > 0) { + max = 255/max; + } + + for ( k = 0; k < (scaledSize) ; k++ ) { + imageBuff[left++] = 255; + imageBuff[left++] = 255; + imageBuff[left++] = 255; + + imageBuff[left++] = ((float)out[k] * max); + } + + Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize); + if (r_saveFontData->integer) { + WriteTGA(name, imageBuff, 256, 256); + } + + //Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize); + image = R_CreateImage(name, imageBuff, 256, 256, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 ); + h = RE_RegisterShaderFromImage(name, LIGHTMAP_2D, image, qfalse); + for (j = lastStart; j < i; j++) { + font->glyphs[j].glyph = h; + Q_strncpyz(font->glyphs[j].shaderName, name, sizeof(font->glyphs[j].shaderName)); + } + lastStart = i; + Com_Memset(out, 0, 256*256); + xOut = 0; + yOut = 0; + ri.Free(imageBuff); + if ( i == GLYPH_END + 1 ) + i++; + } else { + Com_Memcpy(&font->glyphs[i], glyph, sizeof(glyphInfo_t)); + i++; + } + } + + // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 ) + glyphScale = 72.0f / dpi; + + // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font + glyphScale *= 48.0f / pointSize; + + registeredFont[registeredFontCount].glyphScale = glyphScale; + font->glyphScale = glyphScale; + Com_Memcpy(®isteredFont[registeredFontCount++], font, sizeof(fontInfo_t)); + + if (r_saveFontData->integer) { + ri.FS_WriteFile(va("fonts/fontImage_%i.dat", pointSize), font, sizeof(fontInfo_t)); + } + + ri.Free(out); + + ri.FS_FreeFile(faceData); +#endif +} + + + +void R_InitFreeType(void) { +#ifdef BUILD_FREETYPE + if (FT_Init_FreeType( &ftLibrary )) { + ri.Printf(PRINT_WARNING, "R_InitFreeType: Unable to initialize FreeType.\n"); + } +#endif + registeredFontCount = 0; +} + + +void R_DoneFreeType(void) { +#ifdef BUILD_FREETYPE + if (ftLibrary) { + FT_Done_FreeType( ftLibrary ); + ftLibrary = NULL; + } +#endif + registeredFontCount = 0; +} diff --git a/src/renderercommon/tr_image_bmp.cpp b/src/renderercommon/tr_image_bmp.cpp new file mode 100644 index 0000000..b3e51b7 --- /dev/null +++ b/src/renderercommon/tr_image_bmp.cpp @@ -0,0 +1,240 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_common.h" + +typedef struct +{ + char id[2]; + unsigned fileSize; + unsigned reserved0; + unsigned bitmapDataOffset; + unsigned bitmapHeaderSize; + unsigned width; + unsigned height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned compression; + unsigned bitmapDataSize; + unsigned hRes; + unsigned vRes; + unsigned colors; + unsigned importantColors; + unsigned char palette[256][4]; +} BMPHeader_t; + +void R_LoadBMP( const char *name, byte **pic, int *width, int *height ) +{ + int columns, rows; + unsigned numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + union { + byte *b; + void *v; + } buffer; + int length; + BMPHeader_t bmpHeader; + byte *bmpRGBA; + + *pic = NULL; + + if(width) + *width = 0; + + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile( ( char * ) name, &buffer.v); + if (!buffer.b || length < 0) { + return; + } + + if (length < 54) + { + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)", name ); + } + + buf_p = buffer.b; + end = buffer.b + length; + + bmpHeader.id[0] = *buf_p++; + bmpHeader.id[1] = *buf_p++; + bmpHeader.fileSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.width = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.height = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.planes = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.compression = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.hRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.vRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.colors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.importantColors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + + if ( bmpHeader.bitsPerPixel == 8 ) + { + if (buf_p + sizeof(bmpHeader.palette) > end) + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)", name ); + + Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + } + + if (buffer.b + bmpHeader.bitmapDataOffset > end) + { + ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)", name ); + } + + buf_p = buffer.b + bmpHeader.bitmapDataOffset; + + if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) + { + ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)", name ); + } + if ( bmpHeader.fileSize != length ) + { + ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%u vs. %u) (%s)", bmpHeader.fileSize, length, name ); + } + if ( bmpHeader.compression != 0 ) + { + ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)", name ); + } + if ( bmpHeader.bitsPerPixel < 8 ) + { + ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)", name ); + } + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + case 16: + case 24: + case 32: + break; + default: + ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'", bmpHeader.bitsPerPixel, name ); + break; + } + + columns = bmpHeader.width; + rows = bmpHeader.height; + if ( rows < 0 ) + rows = -rows; + numPixels = columns * rows; + + if(columns <= 0 || !rows || numPixels > 0x1FFFFFFF // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF + || ((numPixels * 4) / columns) / 4 != rows) + { + ri.Error (ERR_DROP, "LoadBMP: %s has an invalid image size", name); + } + if(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end) + { + ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)", name); + } + + if ( width ) + *width = columns; + if ( height ) + *height = rows; + + bmpRGBA = (byte*)ri.Malloc( numPixels * 4 ); + *pic = bmpRGBA; + + + for ( row = rows-1; row >= 0; row-- ) + { + pixbuf = bmpRGBA + row*columns*4; + + for ( column = 0; column < columns; column++ ) + { + unsigned char red, green, blue, alpha; + int palIndex; + unsigned short shortPixel; + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + palIndex = *buf_p++; + *pixbuf++ = bmpHeader.palette[palIndex][2]; + *pixbuf++ = bmpHeader.palette[palIndex][1]; + *pixbuf++ = bmpHeader.palette[palIndex][0]; + *pixbuf++ = 0xff; + break; + case 16: + shortPixel = * ( unsigned short * ) pixbuf; + pixbuf += 2; + *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7; + *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2; + *pixbuf++ = ( shortPixel & ( 31 ) ) << 3; + *pixbuf++ = 0xff; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + } + } + } + + ri.FS_FreeFile( buffer.v ); + +} diff --git a/src/renderercommon/tr_image_jpg.cpp b/src/renderercommon/tr_image_jpg.cpp new file mode 100644 index 0000000..9b6c1df --- /dev/null +++ b/src/renderercommon/tr_image_jpg.cpp @@ -0,0 +1,479 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include + +#include "tr_common.h" + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#ifdef USE_INTERNAL_JPEG +# define JPEG_INTERNALS +#endif + +#include + +#ifndef USE_INTERNAL_JPEG +# if JPEG_LIB_VERSION < 80 && !defined(MEM_SRCDST_SUPPORTED) +# error Need system libjpeg >= 80 or jpeg_mem_ support +# endif +#endif + +/* Catching errors, as done in libjpeg's example.c */ +typedef struct q_jpeg_error_mgr_s +{ + struct jpeg_error_mgr pub; /* "public" fields */ + + jmp_buf setjmp_buffer; /* for return to caller */ +} q_jpeg_error_mgr_t; + +static void R_JPGErrorExit(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* cinfo->err really points to a q_jpeg_error_mgr_s struct, so coerce pointer */ + q_jpeg_error_mgr_t *jerr = (q_jpeg_error_mgr_t *)cinfo->err; + + (*cinfo->err->format_message) (cinfo, buffer); + + ri.Printf(PRINT_ALL, "Error: %s", buffer); + + /* Return control to the setjmp point */ + longjmp(jerr->setjmp_buffer, 1); +} + +static void R_JPGOutputMessage(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + ri.Printf(PRINT_ALL, "%s\n", buffer); +} + +void R_LoadJPG(const char *filename, unsigned char **pic, int *width, int *height) +{ + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo = {}; + + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + q_jpeg_error_mgr_t jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + unsigned int row_stride; /* physical row width in output buffer */ + unsigned int pixelcount, memcount; + unsigned int sindex, dindex; + byte *out; + int len; + union { + byte *b; + void *v; + } fbuffer; + byte *buf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + len = ri.FS_ReadFile ( ( char * ) filename, &fbuffer.v); + if (!fbuffer.b || len < 0) { + return; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr.pub); + cinfo.err->error_exit = R_JPGErrorExit; + cinfo.err->output_message = R_JPGOutputMessage; + + /* Establish the setjmp return context for R_JPGErrorExit to use. */ + if (setjmp(jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress(&cinfo); + ri.FS_FreeFile(fbuffer.v); + + /* Append the filename to the error for easier debugging */ + ri.Printf(PRINT_ALL, ", loading file %s\n", filename); + return; + } + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_mem_src(&cinfo, fbuffer.b, len); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* + * Make sure it always converts images to RGB color space. This will + * automatically convert 8-bit greyscale images to RGB as well. + */ + cinfo.out_color_space = JCS_RGB; + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + + pixelcount = cinfo.output_width * cinfo.output_height; + + if(!cinfo.output_width || !cinfo.output_height + || ((pixelcount * 4) / cinfo.output_width) / 4 != cinfo.output_height + || pixelcount > 0x1FFFFFFF || cinfo.output_components != 3 + ) + { + // Free the memory to make sure we don't leak memory + ri.FS_FreeFile (fbuffer.v); + jpeg_destroy_decompress(&cinfo); + + ri.Error(ERR_DROP, "LoadJPG: %s has an invalid image format: %dx%d*4=%d, components: %d", filename, + cinfo.output_width, cinfo.output_height, pixelcount * 4, cinfo.output_components); + } + + memcount = pixelcount * 4; + row_stride = cinfo.output_width * cinfo.output_components; + + out = (byte*)ri.Malloc(memcount); + + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + buf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &buf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + buf = out; + + // Expand from RGB to RGBA + sindex = pixelcount * cinfo.output_components; + dindex = memcount; + + do + { + buf[--dindex] = 255; + buf[--dindex] = buf[--sindex]; + buf[--dindex] = buf[--sindex]; + buf[--dindex] = buf[--sindex]; + } while(sindex); + + *pic = out; + + /* Step 7: Finish decompression */ + + jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + ri.FS_FreeFile (fbuffer.v); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ +} + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + byte* outfile; /* target stream */ + int size; +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +static void +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + dest->pub.next_output_byte = dest->outfile; + dest->pub.free_in_buffer = dest->size; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +static boolean +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + jpeg_destroy_compress(cinfo); + + // Make crash fatal or we would probably leak memory. + ri.Error(ERR_FATAL, "Output buffer for encoded JPEG image has insufficient size of %d bytes", + dest->size); + + return FALSE; +} + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +static void term_destination(j_compress_ptr cinfo) +{ +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +static void +jpegDest (j_compress_ptr cinfo, byte* outfile, int size) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; + dest->size = size; +} + +/* +================= +SaveJPGToBuffer + +Encodes JPEG from image in image_buffer and writes to buffer. +Expects RGB input data +================= +*/ +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding) +{ + struct jpeg_compress_struct cinfo; + q_jpeg_error_mgr_t jerr; + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + my_dest_ptr dest; + int row_stride; /* physical row width in image buffer */ + size_t outcount; + + /* Step 1: allocate and initialize JPEG compression object */ + cinfo.err = jpeg_std_error(&jerr.pub); + cinfo.err->error_exit = R_JPGErrorExit; + cinfo.err->output_message = R_JPGOutputMessage; + + /* Establish the setjmp return context for R_JPGErrorExit to use. */ + if (setjmp(jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object and return. + */ + jpeg_destroy_compress(&cinfo); + + ri.Printf(PRINT_ALL, "\n"); + return 0; + } + + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + jpegDest(&cinfo, buffer, bufSize); + + /* Step 3: set parameters for compression */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + /* If quality is set high, disable chroma subsampling */ + if (quality >= 85) { + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + } + + /* Step 4: Start compressor */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + row_stride = image_width * cinfo.input_components + padding; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + row_pointer[0] = &image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + /* Step 6: Finish compression */ + jpeg_finish_compress(&cinfo); + + dest = (my_dest_ptr) cinfo.dest; + outcount = dest->size - dest->pub.free_in_buffer; + + /* Step 7: release JPEG compression object */ + jpeg_destroy_compress(&cinfo); + + /* And we're done! */ + return outcount; +} + +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, byte *image_buffer, int padding) +{ + byte *out; + size_t bufSize; + + bufSize = image_width * image_height * 3; + out = (byte*)ri.Hunk_AllocateTempMemory(bufSize); + + bufSize = RE_SaveJPGToBuffer(out, bufSize, quality, image_width, image_height, image_buffer, padding); + ri.FS_WriteFile(filename, out, bufSize); + + ri.Hunk_FreeTempMemory(out); +} diff --git a/src/renderercommon/tr_image_pcx.cpp b/src/renderercommon/tr_image_pcx.cpp new file mode 100644 index 0000000..9308cfe --- /dev/null +++ b/src/renderercommon/tr_image_pcx.cpp @@ -0,0 +1,177 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + 2008 Ludwig Nussel +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_common.h" + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + unsigned short hscreensize, vscreensize; + char filler[54]; + unsigned char data[]; +} pcx_t; + +void R_LoadPCX ( const char *filename, byte **pic, int *width, int *height) +{ + union { + byte *b; + void *v; + } raw; + byte *end; + pcx_t *pcx; + int len; + unsigned char dataByte = 0, runLength = 0; + byte *out, *pix; + unsigned short w, h; + byte *pic8; + byte *palette; + int i; + unsigned size = 0; + + if (width) + *width = 0; + if (height) + *height = 0; + *pic = NULL; + + // + // load the file + // + len = ri.FS_ReadFile( ( char * ) filename, &raw.v); + if (!raw.b || len < 0) { + return; + } + + if((unsigned)len < sizeof(pcx_t)) + { + ri.Printf (PRINT_ALL, "PCX truncated: %s\n", filename); + ri.FS_FreeFile (raw.v); + return; + } + + // + // parse the PCX file + // + pcx = (pcx_t *)raw.b; + end = raw.b+len; + + w = LittleShort(pcx->xmax)+1; + h = LittleShort(pcx->ymax)+1; + size = w*h; + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->color_planes != 1 + || pcx->bits_per_pixel != 8 + || w >= 1024 + || h >= 1024) + { + ri.Printf (PRINT_ALL, "Bad or unsupported pcx file %s (%dx%d@%d)\n", filename, w, h, pcx->bits_per_pixel); + return; + } + + pix = pic8 = (byte*)ri.Malloc ( size ); + + raw.b = pcx->data; + // FIXME: should use bytes_per_line but original q3 didn't do that either + while(pix < pic8+size) + { + if(runLength > 0) { + *pix++ = dataByte; + --runLength; + continue; + } + + if(raw.b+1 > end) + break; + dataByte = *raw.b++; + + if((dataByte & 0xC0) == 0xC0) + { + if(raw.b+1 > end) + break; + runLength = dataByte & 0x3F; + dataByte = *raw.b++; + } + else + runLength = 1; + } + + if(pix < pic8+size) + { + ri.Printf (PRINT_ALL, "PCX file truncated: %s\n", filename); + ri.FS_FreeFile (pcx); + ri.Free (pic8); + } + + if (raw.b-(byte*)pcx >= end - (byte*)769 || end[-769] != 0x0c) + { + ri.Printf (PRINT_ALL, "PCX missing palette: %s\n", filename); + ri.FS_FreeFile (pcx); + ri.Free (pic8); + return; + } + + palette = end-768; + + pix = out = (byte*)ri.Malloc(4 * size ); + for (i = 0 ; i < size ; i++) + { + unsigned char p = pic8[i]; + pix[0] = palette[p*3]; + pix[1] = palette[p*3 + 1]; + pix[2] = palette[p*3 + 2]; + pix[3] = 255; + pix += 4; + } + + if (width) + *width = w; + if (height) + *height = h; + + *pic = out; + + ri.FS_FreeFile (pcx); + ri.Free (pic8); +} diff --git a/src/renderercommon/tr_image_png.cpp b/src/renderercommon/tr_image_png.cpp new file mode 100644 index 0000000..6a3f6e8 --- /dev/null +++ b/src/renderercommon/tr_image_png.cpp @@ -0,0 +1,2486 @@ +/* +=========================================================================== +ioquake3 png decoder +Copyright (C) 2007,2008 Joerg Dietrich +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +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 3 +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, see . +=========================================================================== +*/ + +#include "tr_common.h" + +#include "qcommon/puff.h" + +// we could limit the png size to a lower value here +#ifndef INT_MAX +#define INT_MAX 0x1fffffff +#endif + +/* +================= +PNG LOADING +================= +*/ + +/* + * Quake 3 image format : RGBA + */ + +#define Q3IMAGE_BYTESPERPIXEL (4) + +/* + * PNG specifications + */ + +/* + * The first 8 Bytes of every PNG-File are a fixed signature + * to identify the file as a PNG. + */ + +#define PNG_Signature "\x89\x50\x4E\x47\xD\xA\x1A\xA" +#define PNG_Signature_Size (8) + +/* + * After the signature diverse chunks follow. + * A chunk consists of a header and if Length + * is bigger than 0 a body and a CRC of the body follow. + */ + +struct PNG_ChunkHeader +{ + uint32_t Length; + uint32_t Type; +}; + +#define PNG_ChunkHeader_Size (8) + +typedef uint32_t PNG_ChunkCRC; + +#define PNG_ChunkCRC_Size (4) + +/* + * We use the following ChunkTypes. + * All others are ignored. + */ + +#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d))) + +#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R') +#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E') +#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T') +#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D') +#define PNG_ChunkType_tRNS MAKE_CHUNKTYPE('t', 'R', 'N', 'S') + +/* + * Per specification the first chunk after the signature SHALL be IHDR. + */ + +struct PNG_Chunk_IHDR +{ + uint32_t Width; + uint32_t Height; + uint8_t BitDepth; + uint8_t ColourType; + uint8_t CompressionMethod; + uint8_t FilterMethod; + uint8_t InterlaceMethod; +}; + +#define PNG_Chunk_IHDR_Size (13) + +/* + * ColourTypes + */ + +#define PNG_ColourType_Grey (0) +#define PNG_ColourType_True (2) +#define PNG_ColourType_Indexed (3) +#define PNG_ColourType_GreyAlpha (4) +#define PNG_ColourType_TrueAlpha (6) + +/* + * number of colour components + * + * Grey : 1 grey + * True : 1 R, 1 G, 1 B + * Indexed : 1 index + * GreyAlpha : 1 grey, 1 alpha + * TrueAlpha : 1 R, 1 G, 1 B, 1 alpha + */ + +#define PNG_NumColourComponents_Grey (1) +#define PNG_NumColourComponents_True (3) +#define PNG_NumColourComponents_Indexed (1) +#define PNG_NumColourComponents_GreyAlpha (2) +#define PNG_NumColourComponents_TrueAlpha (4) + +/* + * For the different ColourTypes + * different BitDepths are specified. + */ + +#define PNG_BitDepth_1 ( 1) +#define PNG_BitDepth_2 ( 2) +#define PNG_BitDepth_4 ( 4) +#define PNG_BitDepth_8 ( 8) +#define PNG_BitDepth_16 (16) + +/* + * Only one valid CompressionMethod is standardized. + */ + +#define PNG_CompressionMethod_0 (0) + +/* + * Only one valid FilterMethod is currently standardized. + */ + +#define PNG_FilterMethod_0 (0) + +/* + * This FilterMethod defines 5 FilterTypes + */ + +#define PNG_FilterType_None (0) +#define PNG_FilterType_Sub (1) +#define PNG_FilterType_Up (2) +#define PNG_FilterType_Average (3) +#define PNG_FilterType_Paeth (4) + +/* + * Two InterlaceMethods are standardized : + * 0 - NonInterlaced + * 1 - Interlaced + */ + +#define PNG_InterlaceMethod_NonInterlaced (0) +#define PNG_InterlaceMethod_Interlaced (1) + +/* + * The Adam7 interlace method uses 7 passes. + */ + +#define PNG_Adam7_NumPasses (7) + +/* + * The compressed data starts with a header ... + */ + +struct PNG_ZlibHeader +{ + uint8_t CompressionMethod; + uint8_t Flags; +}; + +#define PNG_ZlibHeader_Size (2) + +/* + * ... and is followed by a check value + */ + +#define PNG_ZlibCheckValue_Size (4) + +/* + * Some support functions for buffered files follow. + */ + +/* + * buffered file representation + */ + +struct BufferedFile +{ + byte *Buffer; + int Length; + byte *Ptr; + int BytesLeft; +}; + +/* + * Read a file into a buffer. + */ + +static struct BufferedFile *ReadBufferedFile(const char *name) +{ + struct BufferedFile *BF; + union { + byte *b; + void *v; + } buffer; + + /* + * input verification + */ + + if(!name) + { + return(NULL); + } + + /* + * Allocate control struct. + */ + + BF = (struct BufferedFile*)ri.Malloc(sizeof(struct BufferedFile)); + if(!BF) + { + return(NULL); + } + + /* + * Initialize the structs components. + */ + + BF->Length = 0; + BF->Buffer = NULL; + BF->Ptr = NULL; + BF->BytesLeft = 0; + + /* + * Read the file. + */ + + BF->Length = ri.FS_ReadFile((char *) name, &buffer.v); + BF->Buffer = buffer.b; + + /* + * Did we get it? Is it big enough? + */ + + if(!(BF->Buffer && (BF->Length > 0))) + { + ri.Free(BF); + + return(NULL); + } + + /* + * Set the pointers and counters. + */ + + BF->Ptr = BF->Buffer; + BF->BytesLeft = BF->Length; + + return(BF); +} + +/* + * Close a buffered file. + */ + +static void CloseBufferedFile(struct BufferedFile *BF) +{ + if(BF) + { + if(BF->Buffer) + { + ri.FS_FreeFile(BF->Buffer); + } + + ri.Free(BF); + } +} + +/* + * Get a pointer to the requested bytes. + */ + +static void *BufferedFileRead(struct BufferedFile *BF, unsigned Length) +{ + void *RetVal; + + /* + * input verification + */ + + if(!(BF && Length)) + { + return(NULL); + } + + /* + * not enough bytes left + */ + + if(Length > BF->BytesLeft) + { + return(NULL); + } + + /* + * the pointer to the requested data + */ + + RetVal = BF->Ptr; + + /* + * Raise the pointer and counter. + */ + + BF->Ptr += Length; + BF->BytesLeft -= Length; + + return(RetVal); +} + +/* + * Rewind the buffer. + */ + +static qboolean BufferedFileRewind(struct BufferedFile *BF, unsigned Offset) +{ + unsigned BytesRead; + + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * special trick to rewind to the beginning of the buffer + */ + + if(Offset == (unsigned)-1) + { + BF->Ptr = BF->Buffer; + BF->BytesLeft = BF->Length; + + return(qtrue); + } + + /* + * How many bytes do we have already read? + */ + + BytesRead = BF->Ptr - BF->Buffer; + + /* + * We can only rewind to the beginning of the BufferedFile. + */ + + if(Offset > BytesRead) + { + return(qfalse); + } + + /* + * lower the pointer and counter. + */ + + BF->Ptr -= Offset; + BF->BytesLeft += Offset; + + return(qtrue); +} + +/* + * Skip some bytes. + */ + +static qboolean BufferedFileSkip(struct BufferedFile *BF, unsigned Offset) +{ + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * We can only skip to the end of the BufferedFile. + */ + + if(Offset > BF->BytesLeft) + { + return(qfalse); + } + + /* + * lower the pointer and counter. + */ + + BF->Ptr += Offset; + BF->BytesLeft -= Offset; + + return(qtrue); +} + +/* + * Find a chunk + */ + +static qboolean FindChunk(struct BufferedFile *BF, uint32_t ChunkType) +{ + struct PNG_ChunkHeader *CH; + + uint32_t Length; + uint32_t Type; + + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * cycle trough the chunks + */ + + while(qtrue) + { + /* + * Read the chunk-header. + */ + + CH = (struct PNG_ChunkHeader*)BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + return(qfalse); + } + + /* + * Do not swap the original types + * they might be needed later. + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We found it! + */ + + if(Type == ChunkType) + { + /* + * Rewind to the start of the chunk. + */ + + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + else + { + /* + * Skip the rest of the chunk. + */ + + if(Length) + { + if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size)) + { + return(qfalse); + } + } + } + } + + return(qtrue); +} + +/* + * Decompress all IDATs + */ + +static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer) +{ + uint8_t *DecompressedData; + uint32_t DecompressedDataLength; + + uint8_t *CompressedData; + uint8_t *CompressedDataPtr; + uint32_t CompressedDataLength; + + struct PNG_ChunkHeader *CH; + + uint32_t Length; + uint32_t Type; + + int BytesToRewind; + + int32_t puffResult; + uint8_t *puffDest; + uint32_t puffDestLen; + uint8_t *puffSrc; + uint32_t puffSrcLen; + + /* + * input verification + */ + + if(!(BF && Buffer)) + { + return(-1); + } + + /* + * some zeroing + */ + + DecompressedData = NULL; + *Buffer = DecompressedData; + + CompressedData = NULL; + CompressedDataLength = 0; + + BytesToRewind = 0; + + /* + * Find the first IDAT chunk. + */ + + if(!FindChunk(BF, PNG_ChunkType_IDAT)) + { + return(-1); + } + + /* + * Count the size of the uncompressed data + */ + + while(qtrue) + { + /* + * Read chunk header + */ + + CH = (struct PNG_ChunkHeader*)BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + /* + * Rewind to the start of this adventure + * and return unsuccessfull + */ + + BufferedFileRewind(BF, BytesToRewind); + + return(-1); + } + + /* + * Length and Type of chunk + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We have reached the end of the IDAT chunks + */ + + if(!(Type == PNG_ChunkType_IDAT)) + { + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + + /* + * Add chunk header to count. + */ + + BytesToRewind += PNG_ChunkHeader_Size; + + /* + * Skip to next chunk + */ + + if(Length) + { + if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size)) + { + BufferedFileRewind(BF, BytesToRewind); + + return(-1); + } + + BytesToRewind += Length + PNG_ChunkCRC_Size; + CompressedDataLength += Length; + } + } + + BufferedFileRewind(BF, BytesToRewind); + + CompressedData = (uint8_t*)ri.Malloc(CompressedDataLength); + if(!CompressedData) + { + return(-1); + } + + CompressedDataPtr = CompressedData; + + /* + * Collect the compressed Data + */ + + while(qtrue) + { + /* + * Read chunk header + */ + + CH = (struct PNG_ChunkHeader*)BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Length and Type of chunk + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We have reached the end of the IDAT chunks + */ + + if(!(Type == PNG_ChunkType_IDAT)) + { + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + + /* + * Copy the Data + */ + + if(Length) + { + uint8_t *OrigCompressedData; + + OrigCompressedData = (uint8_t*)BufferedFileRead(BF, Length); + if(!OrigCompressedData) + { + ri.Free(CompressedData); + + return(-1); + } + + if(!BufferedFileSkip(BF, PNG_ChunkCRC_Size)) + { + ri.Free(CompressedData); + + return(-1); + } + + memcpy(CompressedDataPtr, OrigCompressedData, Length); + CompressedDataPtr += Length; + } + } + + /* + * Let puff() calculate the decompressed data length. + */ + + puffDest = NULL; + puffDestLen = 0; + + /* + * The zlib header and checkvalue don't belong to the compressed data. + */ + + puffSrc = CompressedData + PNG_ZlibHeader_Size; + puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size; + + /* + * first puff() to calculate the size of the uncompressed data + */ + + puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen); + if(!((puffResult == 0) && (puffDestLen > 0))) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Allocate the buffer for the uncompressed data. + */ + + DecompressedData = (uint8_t*)ri.Malloc(puffDestLen); + if(!DecompressedData) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Set the input again in case something was changed by the last puff() . + */ + + puffDest = DecompressedData; + puffSrc = CompressedData + PNG_ZlibHeader_Size; + puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size; + + /* + * decompression puff() + */ + + puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen); + + /* + * The compressed data is not needed anymore. + */ + + ri.Free(CompressedData); + + /* + * Check if the last puff() was successfull. + */ + + if(!((puffResult == 0) && (puffDestLen > 0))) + { + ri.Free(DecompressedData); + + return(-1); + } + + /* + * Set the output of this function. + */ + + DecompressedDataLength = puffDestLen; + *Buffer = DecompressedData; + + return(DecompressedDataLength); +} + +/* + * the Paeth predictor + */ + +static uint8_t PredictPaeth(uint8_t a, uint8_t b, uint8_t c) +{ + /* + * a == Left + * b == Up + * c == UpLeft + */ + + uint8_t Pr; + int p; + int pa, pb, pc; + + p = ((int) a) + ((int) b) - ((int) c); + pa = abs(p - ((int) a)); + pb = abs(p - ((int) b)); + pc = abs(p - ((int) c)); + + if((pa <= pb) && (pa <= pc)) + { + Pr = a; + } + else if(pb <= pc) + { + Pr = b; + } + else + { + Pr = c; + } + + return(Pr); + +} + +/* + * Reverse the filters. + */ + +static qboolean UnfilterImage(uint8_t *DecompressedData, + uint32_t ImageHeight, + uint32_t BytesPerScanline, + uint32_t BytesPerPixel) +{ + uint8_t *DecompPtr; + uint8_t FilterType; + uint8_t *PixelLeft, *PixelUp, *PixelUpLeft; + uint32_t w, h, p; + + /* + * some zeros for the filters + */ + + uint8_t Zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + /* + * input verification + */ + + if(!(DecompressedData && BytesPerPixel)) + { + return(qfalse); + } + + /* + * ImageHeight and BytesPerScanline can be zero in small interlaced images. + */ + + if((!ImageHeight) || (!BytesPerScanline)) + { + return(qtrue); + } + + /* + * Set the pointer to the start of the decompressed Data. + */ + + DecompPtr = DecompressedData; + + /* + * Un-filtering is done in place. + */ + + /* + * Go trough all scanlines. + */ + + for(h = 0; h < ImageHeight; h++) + { + /* + * Every scanline starts with a FilterType byte. + */ + + FilterType = *DecompPtr; + DecompPtr++; + + /* + * Left pixel of the first byte in a scanline is zero. + */ + + PixelLeft = Zeros; + + /* + * Set PixelUp to previous line only if we are on the second line or above. + * + * Plus one byte for the FilterType + */ + + if(h > 0) + { + PixelUp = DecompPtr - (BytesPerScanline + 1); + } + else + { + PixelUp = Zeros; + } + + /* + * The pixel left to the first pixel of the previous scanline is zero too. + */ + + PixelUpLeft = Zeros; + + /* + * Cycle trough all pixels of the scanline. + */ + + for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++) + { + /* + * Cycle trough the bytes of the pixel. + */ + + for(p = 0; p < BytesPerPixel; p++) + { + switch(FilterType) + { + case PNG_FilterType_None : + { + /* + * The byte is unfiltered. + */ + + break; + } + + case PNG_FilterType_Sub : + { + DecompPtr[p] += PixelLeft[p]; + + break; + } + + case PNG_FilterType_Up : + { + DecompPtr[p] += PixelUp[p]; + + break; + } + + case PNG_FilterType_Average : + { + DecompPtr[p] += ((uint8_t) ((((uint16_t) PixelLeft[p]) + ((uint16_t) PixelUp[p])) / 2)); + + break; + } + + case PNG_FilterType_Paeth : + { + DecompPtr[p] += PredictPaeth(PixelLeft[p], PixelUp[p], PixelUpLeft[p]); + + break; + } + + default : + { + return(qfalse); + } + } + } + + PixelLeft = DecompPtr; + + /* + * We only have an upleft pixel if we are on the second line or above. + */ + + if(h > 0) + { + PixelUpLeft = DecompPtr - (BytesPerScanline + 1); + } + + /* + * Skip to the next pixel. + */ + + DecompPtr += BytesPerPixel; + + /* + * We only have a previous line if we are on the second line and above. + */ + + if(h > 0) + { + PixelUp = DecompPtr - (BytesPerScanline + 1); + } + } + } + + return(qtrue); +} + +/* + * Convert a raw input pixel to Quake 3 RGA format. + */ + +static qboolean ConvertPixel(struct PNG_Chunk_IHDR *IHDR, + byte *OutPtr, + uint8_t *DecompPtr, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + /* + * input verification + */ + + if(!(IHDR && OutPtr && DecompPtr && TransparentColour && OutPal)) + { + return(qfalse); + } + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + uint8_t Step; + uint8_t GreyValue; + + Step = 0xFF / ((1 << IHDR->BitDepth) - 1); + + GreyValue = DecompPtr[0] * Step; + + OutPtr[0] = GreyValue; + OutPtr[1] = GreyValue; + OutPtr[2] = GreyValue; + OutPtr[3] = 0xFF; + + /* + * Grey supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if(TransparentColour[1] == DecompPtr[0]) + { + OutPtr[3] = 0x00; + } + } + + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = 0xFF; + + /* + * Grey supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if(IHDR->BitDepth == PNG_BitDepth_8) + { + if(TransparentColour[1] == DecompPtr[0]) + { + OutPtr[3] = 0x00; + } + } + else + { + if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1])) + { + OutPtr[3] = 0x00; + } + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[1]; + OutPtr[2] = DecompPtr[2]; + OutPtr[3] = 0xFF; + + /* + * True supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if((TransparentColour[1] == DecompPtr[0]) && + (TransparentColour[3] == DecompPtr[1]) && + (TransparentColour[5] == DecompPtr[2])) + { + OutPtr[3] = 0x00; + } + } + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[2]; + OutPtr[2] = DecompPtr[4]; + OutPtr[3] = 0xFF; + + /* + * True supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]) && + (TransparentColour[2] == DecompPtr[2]) && (TransparentColour[3] == DecompPtr[3]) && + (TransparentColour[4] == DecompPtr[4]) && (TransparentColour[5] == DecompPtr[5])) + { + OutPtr[3] = 0x00; + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + OutPtr[0] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 0]; + OutPtr[1] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 1]; + OutPtr[2] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 2]; + OutPtr[3] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 3]; + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = DecompPtr[1]; + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = DecompPtr[2]; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[1]; + OutPtr[2] = DecompPtr[2]; + OutPtr[3] = DecompPtr[3]; + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[2]; + OutPtr[2] = DecompPtr[4]; + OutPtr[3] = DecompPtr[6]; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + return(qtrue); +} + + +/* + * Decode a non-interlaced image. + */ + +static qboolean DecodeImageNonInterlaced(struct PNG_Chunk_IHDR *IHDR, + byte *OutBuffer, + uint8_t *DecompressedData, + uint32_t DecompressedDataLength, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + uint32_t IHDR_Width; + uint32_t IHDR_Height; + uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte; + uint32_t w, h, p; + byte *OutPtr; + uint8_t *DecompPtr; + + /* + * input verification + */ + + if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal)) + { + return(qfalse); + } + + /* + * byte swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * information for un-filtering + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + { + BytesPerPixel = PNG_NumColourComponents_Indexed; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + /* + * Calculate the size of one scanline + */ + + BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte; + + /* + * Check if we have enough data for the whole image. + */ + + if(!(DecompressedDataLength == ((BytesPerScanline + 1) * IHDR_Height))) + { + return(qfalse); + } + + /* + * Unfilter the image. + */ + + if(!UnfilterImage(DecompressedData, IHDR_Height, BytesPerScanline, BytesPerPixel)) + { + return(qfalse); + } + + /* + * Set the working pointers to the beginning of the buffers. + */ + + OutPtr = OutBuffer; + DecompPtr = DecompressedData; + + /* + * Create the output image. + */ + + for(h = 0; h < IHDR_Height; h++) + { + /* + * Count the pixels on the scanline for those multipixel bytes + */ + + uint32_t CurrPixel; + + /* + * skip FilterType + */ + + DecompPtr++; + + /* + * Reset the pixel count. + */ + + CurrPixel = 0; + + for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++) + { + if(PixelsPerByte > 1) + { + uint8_t Mask; + uint32_t Shift; + uint8_t SinglePixel; + + for(p = 0; p < PixelsPerByte; p++) + { + if(CurrPixel < IHDR_Width) + { + Mask = (1 << IHDR->BitDepth) - 1; + Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth; + + SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift); + + if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + OutPtr += Q3IMAGE_BYTESPERPIXEL; + CurrPixel++; + } + } + + } + else + { + if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + + OutPtr += Q3IMAGE_BYTESPERPIXEL; + } + + DecompPtr += BytesPerPixel; + } + } + + return(qtrue); +} + +/* + * Decode an interlaced image. + */ + +static qboolean DecodeImageInterlaced(struct PNG_Chunk_IHDR *IHDR, + byte *OutBuffer, + uint8_t *DecompressedData, + uint32_t DecompressedDataLength, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + uint32_t IHDR_Width; + uint32_t IHDR_Height; + uint32_t BytesPerScanline[PNG_Adam7_NumPasses], BytesPerPixel, PixelsPerByte; + uint32_t PassWidth[PNG_Adam7_NumPasses], PassHeight[PNG_Adam7_NumPasses]; + uint32_t WSkip[PNG_Adam7_NumPasses], WOffset[PNG_Adam7_NumPasses], HSkip[PNG_Adam7_NumPasses], HOffset[PNG_Adam7_NumPasses]; + uint32_t w, h, p, a; + byte *OutPtr; + uint8_t *DecompPtr; + uint32_t TargetLength; + + /* + * input verification + */ + + if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal)) + { + return(qfalse); + } + + /* + * byte swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * Skip and Offset for the passes. + */ + + WSkip[0] = 8; + WOffset[0] = 0; + HSkip[0] = 8; + HOffset[0] = 0; + + WSkip[1] = 8; + WOffset[1] = 4; + HSkip[1] = 8; + HOffset[1] = 0; + + WSkip[2] = 4; + WOffset[2] = 0; + HSkip[2] = 8; + HOffset[2] = 4; + + WSkip[3] = 4; + WOffset[3] = 2; + HSkip[3] = 4; + HOffset[3] = 0; + + WSkip[4] = 2; + WOffset[4] = 0; + HSkip[4] = 4; + HOffset[4] = 2; + + WSkip[5] = 2; + WOffset[5] = 1; + HSkip[5] = 2; + HOffset[5] = 0; + + WSkip[6] = 1; + WOffset[6] = 0; + HSkip[6] = 2; + HOffset[6] = 1; + + /* + * Calculate the sizes of the passes. + */ + + PassWidth[0] = (IHDR_Width + 7) / 8; + PassHeight[0] = (IHDR_Height + 7) / 8; + + PassWidth[1] = (IHDR_Width + 3) / 8; + PassHeight[1] = (IHDR_Height + 7) / 8; + + PassWidth[2] = (IHDR_Width + 3) / 4; + PassHeight[2] = (IHDR_Height + 3) / 8; + + PassWidth[3] = (IHDR_Width + 1) / 4; + PassHeight[3] = (IHDR_Height + 3) / 4; + + PassWidth[4] = (IHDR_Width + 1) / 2; + PassHeight[4] = (IHDR_Height + 1) / 4; + + PassWidth[5] = (IHDR_Width + 0) / 2; + PassHeight[5] = (IHDR_Height + 1) / 2; + + PassWidth[6] = (IHDR_Width + 0) / 1; + PassHeight[6] = (IHDR_Height + 0) / 2; + + /* + * information for un-filtering + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + { + BytesPerPixel = PNG_NumColourComponents_Indexed; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + /* + * Calculate the size of the scanlines per pass + */ + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + BytesPerScanline[a] = (PassWidth[a] * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte; + } + + /* + * Calculate the size of all passes + */ + + TargetLength = 0; + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + TargetLength += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]); + } + + /* + * Check if we have enough data for the whole image. + */ + + if(!(DecompressedDataLength == TargetLength)) + { + return(qfalse); + } + + /* + * Unfilter the image. + */ + + DecompPtr = DecompressedData; + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + if(!UnfilterImage(DecompPtr, PassHeight[a], BytesPerScanline[a], BytesPerPixel)) + { + return(qfalse); + } + + DecompPtr += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]); + } + + /* + * Set the working pointers to the beginning of the buffers. + */ + + DecompPtr = DecompressedData; + + /* + * Create the output image. + */ + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + for(h = 0; h < PassHeight[a]; h++) + { + /* + * Count the pixels on the scanline for those multipixel bytes + */ + + uint32_t CurrPixel; + + /* + * skip FilterType + * but only when the pass has a width bigger than zero + */ + + if(BytesPerScanline[a]) + { + DecompPtr++; + } + + /* + * Reset the pixel count. + */ + + CurrPixel = 0; + + for(w = 0; w < (BytesPerScanline[a] / BytesPerPixel); w++) + { + if(PixelsPerByte > 1) + { + uint8_t Mask; + uint32_t Shift; + uint8_t SinglePixel; + + for(p = 0; p < PixelsPerByte; p++) + { + if(CurrPixel < PassWidth[a]) + { + Mask = (1 << IHDR->BitDepth) - 1; + Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth; + + SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift); + + OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((CurrPixel * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL); + + if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + CurrPixel++; + } + } + + } + else + { + OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((w * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL); + + if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + } + + DecompPtr += BytesPerPixel; + } + } + } + + return(qtrue); +} + +/* + * The PNG loader + */ + +void R_LoadPNG(const char *name, byte **pic, int *width, int *height) +{ + struct BufferedFile *ThePNG; + byte *OutBuffer; + uint8_t *Signature; + struct PNG_ChunkHeader *CH; + uint32_t ChunkHeaderLength; + uint32_t ChunkHeaderType; + struct PNG_Chunk_IHDR *IHDR; + uint32_t IHDR_Width; + uint32_t IHDR_Height; + PNG_ChunkCRC *CRC; + uint8_t *InPal; + uint8_t *DecompressedData; + uint32_t DecompressedDataLength; + uint32_t i; + + /* + * palette with 256 RGBA entries + */ + + uint8_t OutPal[1024]; + + /* + * transparent colour from the tRNS chunk + */ + + qboolean HasTransparentColour = qfalse; + uint8_t TransparentColour[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + /* + * input verification + */ + + if(!(name && pic)) + { + return; + } + + /* + * Zero out return values. + */ + + *pic = NULL; + + if(width) + { + *width = 0; + } + + if(height) + { + *height = 0; + } + + /* + * Read the file. + */ + + ThePNG = ReadBufferedFile(name); + if(!ThePNG) + { + return; + } + + /* + * Read the siganture of the file. + */ + + Signature = (uint8_t*)BufferedFileRead(ThePNG, PNG_Signature_Size); + if(!Signature) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Is it a PNG? + */ + + if(memcmp(Signature, PNG_Signature, PNG_Signature_Size)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the first chunk-header. + */ + + CH = (struct PNG_ChunkHeader*)BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the first chunk is an IHDR. + */ + + if(!((ChunkHeaderType == PNG_ChunkType_IHDR) && (ChunkHeaderLength == PNG_Chunk_IHDR_Size))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the IHDR. + */ + + IHDR = (struct PNG_Chunk_IHDR*)BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size); + if(!IHDR) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC for IHDR + */ + + CRC = (PNG_ChunkCRC*)BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Here we could check the CRC if we wanted to. + */ + + /* + * multi-byte type swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * Check if Width and Height are valid. + */ + + if(!((IHDR_Width > 0) && (IHDR_Height > 0)) + || IHDR_Width > INT_MAX / Q3IMAGE_BYTESPERPIXEL / IHDR_Height) + { + CloseBufferedFile(ThePNG); + + ri.Printf( PRINT_WARNING, "%s: invalid image size\n", name ); + + return; + } + + /* + * Do we need to check if the dimensions of the image are valid for Quake3? + */ + + /* + * Check if CompressionMethod and FilterMethod are valid. + */ + + if(!((IHDR->CompressionMethod == PNG_CompressionMethod_0) && (IHDR->FilterMethod == PNG_FilterMethod_0))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Check if InterlaceMethod is valid. + */ + + if(!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced) || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read palette for an indexed image. + */ + + if(IHDR->ColourType == PNG_ColourType_Indexed) + { + /* + * We need the palette first. + */ + + if(!FindChunk(ThePNG, PNG_ChunkType_PLTE)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the chunk-header. + */ + + CH = (struct PNG_ChunkHeader*)BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the chunk is a PLTE. + */ + + if(!(ChunkHeaderType == PNG_ChunkType_PLTE)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Check if Length is divisible by 3 + */ + + if(ChunkHeaderLength % 3) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the raw palette data + */ + + InPal = (uint8_t*)BufferedFileRead(ThePNG, ChunkHeaderLength); + if(!InPal) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC for the palette + */ + + CRC = (PNG_ChunkCRC*)BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Set some default values. + */ + + for(i = 0; i < 256; i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF; + } + + /* + * Convert to the Quake3 RGBA-format. + */ + + for(i = 0; i < (ChunkHeaderLength / 3); i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = InPal[i*3+0]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = InPal[i*3+1]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = InPal[i*3+2]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF; + } + } + + /* + * transparency information is sometimes stored in a tRNS chunk + */ + + /* + * Let's see if there is a tRNS chunk + */ + + if(FindChunk(ThePNG, PNG_ChunkType_tRNS)) + { + uint8_t *Trans; + + /* + * Read the chunk-header. + */ + + CH = (struct PNG_ChunkHeader*)BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the chunk is a tRNS. + */ + + if(!(ChunkHeaderType == PNG_ChunkType_tRNS)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the transparency information. + */ + + Trans = (uint8_t*)BufferedFileRead(ThePNG, ChunkHeaderLength); + if(!Trans) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC. + */ + + CRC = (PNG_ChunkCRC*)BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Only for Grey, True and Indexed ColourType should tRNS exist. + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + if(ChunkHeaderLength != 2) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * Grey can have one colour which is completely transparent. + * This colour is always stored in 16 bits. + */ + + TransparentColour[0] = Trans[0]; + TransparentColour[1] = Trans[1]; + + break; + } + + case PNG_ColourType_True : + { + if(ChunkHeaderLength != 6) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * True can have one colour which is completely transparent. + * This colour is always stored in 16 bits. + */ + + TransparentColour[0] = Trans[0]; + TransparentColour[1] = Trans[1]; + TransparentColour[2] = Trans[2]; + TransparentColour[3] = Trans[3]; + TransparentColour[4] = Trans[4]; + TransparentColour[5] = Trans[5]; + + break; + } + + case PNG_ColourType_Indexed : + { + /* + * Maximum of 256 one byte transparency entries. + */ + + if(ChunkHeaderLength > 256) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * alpha values for palette entries + */ + + for(i = 0; i < ChunkHeaderLength; i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = Trans[i]; + } + + break; + } + + /* + * All other ColourTypes should not have tRNS chunks + */ + + default : + { + CloseBufferedFile(ThePNG); + + return; + } + } + } + + /* + * Rewind to the start of the file. + */ + + if(!BufferedFileRewind(ThePNG, -1)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Skip the signature + */ + + if(!BufferedFileSkip(ThePNG, PNG_Signature_Size)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Decompress all IDAT chunks + */ + + DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData); + if(!(DecompressedDataLength && DecompressedData)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Allocate output buffer. + */ + + OutBuffer = (byte*)ri.Malloc(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL); + if(!OutBuffer) + { + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Interlaced and Non-interlaced images need to be handled differently. + */ + + switch(IHDR->InterlaceMethod) + { + case PNG_InterlaceMethod_NonInterlaced : + { + if(!DecodeImageNonInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal)) + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + break; + } + + case PNG_InterlaceMethod_Interlaced : + { + if(!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal)) + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + break; + } + + default : + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + } + + /* + * update the pointer to the image data + */ + + *pic = OutBuffer; + + /* + * Fill width and height. + */ + + if(width) + { + *width = IHDR_Width; + } + + if(height) + { + *height = IHDR_Height; + } + + /* + * DecompressedData is not needed anymore. + */ + + ri.Free(DecompressedData); + + /* + * We have all data, so close the file. + */ + + CloseBufferedFile(ThePNG); +} diff --git a/src/renderercommon/tr_image_tga.cpp b/src/renderercommon/tr_image_tga.cpp new file mode 100644 index 0000000..c7f587d --- /dev/null +++ b/src/renderercommon/tr_image_tga.cpp @@ -0,0 +1,317 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_common.h" + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + +void R_LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + unsigned columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + union { + byte *b; + void *v; + } buffer; + TargaHeader targa_header; + byte *targa_rgba; + int length; + + *pic = NULL; + + if(width) + *width = 0; + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile ( ( char * ) name, &buffer.v); + if (!buffer.b || length < 0) { + return; + } + + if(length < 18) + { + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)", name ); + } + + buf_p = buffer.b; + end = buffer.b + length; + + targa_header.id_length = buf_p[0]; + targa_header.colormap_type = buf_p[1]; + targa_header.image_type = buf_p[2]; + + memcpy(&targa_header.colormap_index, &buf_p[3], 2); + memcpy(&targa_header.colormap_length, &buf_p[5], 2); + targa_header.colormap_size = buf_p[7]; + memcpy(&targa_header.x_origin, &buf_p[8], 2); + memcpy(&targa_header.y_origin, &buf_p[10], 2); + memcpy(&targa_header.width, &buf_p[12], 2); + memcpy(&targa_header.height, &buf_p[14], 2); + targa_header.pixel_size = buf_p[16]; + targa_header.attributes = buf_p[17]; + + targa_header.colormap_index = LittleShort(targa_header.colormap_index); + targa_header.colormap_length = LittleShort(targa_header.colormap_length); + targa_header.x_origin = LittleShort(targa_header.x_origin); + targa_header.y_origin = LittleShort(targa_header.y_origin); + targa_header.width = LittleShort(targa_header.width); + targa_header.height = LittleShort(targa_header.height); + + buf_p += 18; + + if (targa_header.image_type!=2 + && targa_header.image_type!=10 + && targa_header.image_type != 3 ) + { + ri.Error (ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported"); + } + + if ( targa_header.colormap_type != 0 ) + { + ri.Error( ERR_DROP, "LoadTGA: colormaps not supported" ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) + { + ri.Error (ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)"); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows * 4; + + if(!columns || !rows || numPixels > 0x7FFFFFFF || numPixels / columns / 4 != rows) + { + ri.Error (ERR_DROP, "LoadTGA: %s has an invalid image size", name); + } + + + targa_rgba = (byte*)ri.Malloc (numPixels); + + if (targa_header.id_length != 0) + { + if (buf_p + targa_header.id_length > end) + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)", name ); + + buf_p += targa_header.id_length; // skip TARGA image comment + } + + if ( targa_header.image_type==2 || targa_header.image_type == 3 ) + { + if(buf_p + columns*rows*targa_header.pixel_size/8 > end) + { + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + } + + // Uncompressed RGB or gray scale image + for(row=rows-1; row>=0; row--) + { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column=0; row--) { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + packetHeader= *buf_p++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) { // run-length packet + if(buf_p + targa_header.pixel_size/8 > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + switch (targa_header.pixel_size) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'", targa_header.pixel_size, name ); + break; + } + + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + else { // non run-length packet + + if(buf_p + targa_header.pixel_size/8*packetSize > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut:; + } + } + +#if 0 + // TTimo: this is the chunk of code to ensure a behavior that meets TGA specs + // bit 5 set => top-down + if (targa_header.attributes & 0x20) { + unsigned char *flip = (unsigned char*)malloc (columns*4); + unsigned char *src, *dst; + + for (row = 0; row < rows/2; row++) { + src = targa_rgba + row * 4 * columns; + dst = targa_rgba + (rows - row - 1) * 4 * columns; + + memcpy (flip, src, columns*4); + memcpy (src, dst, columns*4); + memcpy (dst, flip, columns*4); + } + free (flip); + } +#endif + // instead we just print a warning + if (targa_header.attributes & 0x20) { + ri.Printf( PRINT_WARNING, "WARNING: '%s' TGA file header declares top-down image, ignoring\n", name); + } + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = targa_rgba; + + ri.FS_FreeFile (buffer.v); +} diff --git a/src/renderercommon/tr_noise.cpp b/src/renderercommon/tr_noise.cpp new file mode 100644 index 0000000..b31016b --- /dev/null +++ b/src/renderercommon/tr_noise.cpp @@ -0,0 +1,93 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_noise.c +#include "tr_common.h" + +#define NOISE_SIZE 256 +#define NOISE_MASK ( NOISE_SIZE - 1 ) + +#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +static float s_noise_table[NOISE_SIZE]; +static int s_noise_perm[NOISE_SIZE]; + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return s_noise_table[index]; +} + +void R_NoiseInit( void ) +{ + int i; + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) ); + s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 ); + } +} + +float R_NoiseGet4f( float x, float y, float z, double t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + + return finalvalue; +} diff --git a/src/renderercommon/tr_public.h b/src/renderercommon/tr_public.h new file mode 100644 index 0000000..9d926d0 --- /dev/null +++ b/src/renderercommon/tr_public.h @@ -0,0 +1,199 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef __TR_PUBLIC_H +#define __TR_PUBLIC_H + +#include "renderercommon/tr_types.h" + +#define REF_API_VERSION 8 + +// AVI files have the start of pixel lines 4 byte-aligned +#define AVI_LINE_PADDING 4 + +// +// these are the functions exported by the refresh module +// +typedef struct { + // called before the library is unloaded + // if the system is just reconfiguring, pass destroyWindow = qfalse, + // which will keep the screen from flashing to the desktop. + void (*Shutdown)( bool destroyWindow ); + + // 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. + // + // BeginRegistration makes any existing media pointers invalid + // and returns the current gl configuration, including screen width + // and height, which can be used by the client to intelligently + // size display elements + void (*BeginRegistration)( glconfig_t *config ); + qhandle_t (*RegisterModel)( const char *name ); + qhandle_t (*RegisterSkin)( const char *name ); + qhandle_t (*RegisterShader)( const char *name ); + qhandle_t (*RegisterShaderNoMip)( const char *name ); + void (*LoadWorld)( const char *name ); + + // the vis data is a large enough block of data that we go to the trouble + // of sharing it with the clipmodel subsystem + void (*SetWorldVisData)( const byte *vis ); + + // EndRegistration will draw a tiny polygon with each texture, forcing + // them to be loaded into card memory + void (*EndRegistration)( void ); + + // a scene is built up by calls to R_ClearScene and the various R_Add functions. + // Nothing is drawn until R_RenderScene is called. + void (*ClearScene)( void ); + void (*AddRefEntityToScene)( const refEntity_t *re ); + void (*AddPolyToScene)( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); + bool (*LightForPoint)( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + void (*AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*AddAdditiveLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*RenderScene)( const refdef_t *fd ); + + void (*SetColor)( const float *rgba ); // NULL = 1,1,1,1 + void (*SetClipRegion)( const float *region ); + void (*DrawStretchPic) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + + // Draw images for cinematic rendering, pass as 32 bit rgba + void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, bool dirty); + void (*UploadCinematic) (int w, int h, int cols, int rows, const byte *data, int client, bool dirty); + + void (*BeginFrame)( stereoFrame_t stereoFrame ); + + // if the pointers are not NULL, timing info will be returned + void (*EndFrame)( int *frontEndMsec, int *backEndMsec ); + + + int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + int (*LerpTag)( orientation_t *tag, qhandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + void (*ModelBounds)( qhandle_t model, vec3_t mins, vec3_t maxs ); + +#ifdef __USEA3D + void (*A3D_RenderGeometry) (void *pVoidA3D, void *pVoidGeom, void *pVoidMat, void *pVoidGeomStatus); +#endif + void (*RegisterFont)(const char *fontName, int pointSize, fontInfo_t *font); + void (*RemapShader)(const char *oldShader, const char *newShader, const char *offsetTime); + bool (*GetEntityToken)( char *buffer, int size ); + bool (*inPVS)( const vec3_t p1, const vec3_t p2 ); + + void (*TakeVideoFrame)( int h, int w, byte* captureBuffer, byte *encodeBuffer, bool motionJpeg ); +} refexport_t; + +// +// these are the functions imported by the refresh module +// +typedef struct { + // print message on the local console + void (QDECL *Printf)( int printLevel, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); + + // abort the game + void (QDECL *Error)( int errorLevel, const char *fmt, ...) __attribute__ ((noreturn, format (printf, 2, 3))); + + // milliseconds should only be used for profiling, never + // for anything game related. Get time from the refdef + int (*Milliseconds)( void ); + + // stack based memory allocation for per-level things that + // won't be freed +#ifdef HUNK_DEBUG + void *(*Hunk_AllocDebug)( int size, ha_pref pref, const char *label, const char *file, int line ); +#else + void *(*Hunk_Alloc)( int size, ha_pref pref ); +#endif + void *(*Hunk_AllocateTempMemory)( int size ); + void (*Hunk_FreeTempMemory)( void *block ); + + // dynamic memory allocator for things that need to be freed + void *(*Malloc)( int bytes ); + void (*Free)( void *buf ); + + cvar_t *(*Cvar_Get)( const char *name, const char *value, int flags ); + void (*Cvar_Set)( const char *name, const char *value ); + void (*Cvar_SetValue) (const char *name, float value); + void (*Cvar_CheckRange)( cvar_t *cv, float minVal, float maxVal, bool shouldBeIntegral ); + void (*Cvar_SetDescription)( cvar_t *cv, const char *description ); + + int (*Cvar_VariableIntegerValue) (const char *var_name); + + void (*Cmd_AddCommand)( const char *name, void(*cmd)(void) ); + void (*Cmd_RemoveCommand)( const char *name ); + + int (*Cmd_Argc) (void); + const char* (*Cmd_Argv) (int i); + + void (*Cmd_ExecuteText) (int exec_when, const char *text); + + byte *(*CM_ClusterPVS)(int cluster); + + // visualization for debugging collision detection + void (*CM_DrawDebugSurface)( void (*drawPoly)(int color, int numPoints, float *points) ); + + // a -1 return means the file does not exist + // NULL can be passed for buf to just determine existance + int (*FS_FileIsInPAK)( const char *name, int *pCheckSum ); + long (*FS_ReadFile)( const char *name, void **buf ); + void (*FS_FreeFile)( void *buf ); + char ** (*FS_ListFiles)( const char *name, const char *extension, int *numfilesfound ); + void (*FS_FreeFileList)( char **filelist ); + void (*FS_WriteFile)( const char *qpath, const void *buffer, int size ); + bool (*FS_FileExists)( const char *file ); + + // cinematic stuff + void (*CIN_UploadCinematic)(int handle); + int (*CIN_PlayCinematic)( const char *arg0, int xpos, int ypos, int width, int height, int bits); + e_status (*CIN_RunCinematic) (int handle); + + void (*CL_WriteAVIVideoFrame)( const byte *buffer, int size ); + + // input event handling + void (*IN_Init)( void *windowData ); + void (*IN_Shutdown)( void ); + void (*IN_Restart)( void ); + + // system stuff + void (*Sys_GLimpSafeInit)( void ); + void (*Sys_GLimpInit)( void ); + bool (*Sys_LowPhysicalMemory)( void ); +} refimport_t; + + +// this is the only function actually exported at the linker level +// If the module can't init to a valid rendering state, NULL will be +// returned. +#ifdef USE_RENDERER_DLOPEN +extern "C" { +typedef refexport_t* (QDECL *GetRefAPI_t) (int apiVersion, refimport_t * rimp); +} +#else +refexport_t* GetRefAPI( int apiVersion, refimport_t *rimp ); +#endif + +#endif // __TR_PUBLIC_H diff --git a/src/renderercommon/tr_types.h b/src/renderercommon/tr_types.h new file mode 100644 index 0000000..67f3733 --- /dev/null +++ b/src/renderercommon/tr_types.h @@ -0,0 +1,222 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces + +#define REFENTITYNUM_BITS 10 // can't be increased without changing drawsurf bit packing +#define REFENTITYNUM_MASK ((1<lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting + +#define RF_SHADOW_PLANE 0x0100 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 0x0200 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +// refdef flags +#define RDF_NOWORLDMODEL 0x0001 // used for player configuration screen +#define RDF_HYPERSPACE 0x0004 // teleportation effect + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_BEAM, + RT_RAIL_CORE, + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + float rotation; +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC, // this is for the GL_S3_s3tc extension. + TC_S3TC_ARB // this is for the GL_EXT_texture_compression_s3tc extension. +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[BIG_INFO_STRING]; + + int maxTextureSize; // queried from GL + int numTextureUnits; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + int/*textureCompression_t*/ textureCompression; + qboolean textureEnvAddAvailable; + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + float displayAspect; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; + qboolean textureFilterAnisotropic; + int maxAnisotropy; +} glconfig_t; + +#endif // __TR_TYPES_H diff --git a/src/renderergl1/CMakeLists.txt b/src/renderergl1/CMakeLists.txt new file mode 100644 index 0000000..2e28420 --- /dev/null +++ b/src/renderergl1/CMakeLists.txt @@ -0,0 +1,67 @@ +include("${CMAKE_SOURCE_DIR}/cmake/SDL2.cmake") + +find_package(OpenGL) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../jpeg-8c + ${CMAKE_CURRENT_SOURCE_DIR}/../renderercommon + ${SDL2_INCLUDE_DIRS} + ) + +set(renderergl1_SRCS + tr_animation.cpp + tr_backend.cpp + tr_bsp.cpp + tr_cmds.cpp + tr_curve.cpp + tr_flares.cpp + tr_image.cpp + tr_init.cpp + tr_light.cpp + tr_local.h + tr_main.cpp + tr_marks.cpp + tr_mesh.cpp + tr_model.cpp + tr_model_iqm.cpp + tr_scene.cpp + tr_shade.cpp + tr_shade_calc.cpp + tr_shader.cpp + tr_shadows.cpp + tr_sky.cpp + tr_subs.cpp + tr_surface.cpp + tr_world.cpp + tr_local.h + ${CMAKE_SOURCE_DIR}/src/common/puff.cpp + ${CMAKE_SOURCE_DIR}/src/common/q_shared.c + ${CMAKE_SOURCE_DIR}/src/common/q_math.c + ) + +if(NOT USE_RENDERER_DLOPEN) + add_library( + renderergl1 STATIC + ${renderergl1_SRCS} + ) + + target_link_libraries( + renderergl1 renderercommon + ${SDL2_LIBRARIES} + ) +else(NOT USE_RENDERER_DLOPEN) + add_library( + renderergl1 SHARED + ${renderergl1_SRCS} + ) + target_link_libraries( + renderergl1 + renderercommon + ${FRAMEWORKS} + ${OPENGL_LIBRARIES} + ${SDL2_LIBRARIES} + ) + +endif(NOT USE_RENDERER_DLOPEN) + + diff --git a/src/renderergl1/tr_animation.cpp b/src/renderergl1/tr_animation.cpp new file mode 100644 index 0000000..c496cfd --- /dev/null +++ b/src/renderergl1/tr_animation.cpp @@ -0,0 +1,523 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + + +// copied and adapted from tr_mesh.c + +/* +============= +R_MDRCullModel +============= +*/ + +static int R_MDRCullModel( mdrHeader_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdrFrame_t *oldFrame, *newFrame; + int i, frameSize; + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // compute frame pointers + newFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + oldFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.oldframe); + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + // Ummm... yeah yeah I know we don't really have an md3 here.. but we pretend + // we do. After all, the purpose of mdrs are not that different, are they? + + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_MDRComputeFogNum + +================= +*/ + +int R_MDRComputeFogNum( mdrHeader_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdrFrame_t *mdrFrame; + vec3_t localOrigin; + int frameSize; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // FIXME: non-normalized axis issues + mdrFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + VectorAdd( ent->e.origin, mdrFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdrFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdrFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + + +/* +============== +R_MDRAddAnimSurfaces +============== +*/ + +// much stuff in there is just copied from R_AddMd3Surfaces in tr_mesh.c + +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) { + mdrHeader_t *header; + mdrSurface_t *surface; + mdrLOD_t *lod; + shader_t *shader; + skin_t *skin; + int i, j; + int lodnum = 0; + int fogNum = 0; + int cull; + bool personalModel; + + header = (mdrHeader_t *) tr.currentModel->modelData; + + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) + { + ent->e.frame %= header->numFrames; + ent->e.oldframe %= header->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ((ent->e.frame >= header->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= header->numFrames) + || (ent->e.oldframe < 0) ) + { + ri.Printf( PRINT_DEVELOPER, "R_MDRAddAnimSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_MDRCullModel (header, ent); + if ( cull == CULL_OUT ) { + return; + } + + // figure out the current LOD of the model we're rendering, and set the lod pointer respectively. + lodnum = R_ComputeLOD(ent); + // check whether this model has as that many LODs at all. If not, try the closest thing we got. + if(header->numLODs <= 0) + return; + if(header->numLODs <= lodnum) + lodnum = header->numLODs - 1; + + lod = (mdrLOD_t *)( (byte *)header + header->ofsLODs); + for(i = 0; i < lodnum; i++) + { + lod = (mdrLOD_t *) ((byte *) lod + lod->ofsEnd); + } + + // set up lighting + if ( !personalModel || r_shadows->integer > 1 ) + { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // fogNum? + fogNum = R_MDRComputeFogNum( header, ent ); + + surface = (mdrSurface_t *)( (byte *)lod + lod->ofsSurfaces ); + + for ( i = 0 ; i < lod->numSurfaces ; i++ ) + { + + if(ent->e.customShader) + shader = R_GetShaderByHandle(ent->e.customShader); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j].name, surface->name)) + { + shader = skin->surfaces[j].shader; + break; + } + } + } + else if(surface->shaderIndex > 0) + shader = R_GetShaderByHandle( surface->shaderIndex ); + else + shader = tr.defaultShader; + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (surfaceType_t*)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (surfaceType_t*)surface, tr.projectionShadowShader, 0, qfalse ); + } + + if (!personalModel) + R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, qfalse ); + + surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + +/* +============== +RB_MDRSurfaceAnim +============== +*/ +void RB_MDRSurfaceAnim( mdrSurface_t *surface ) +{ + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + mdrVertex_t *v; + mdrHeader_t *header; + mdrFrame_t *frame; + mdrFrame_t *oldFrame; + mdrBone_t bones[MDR_MAX_BONES], *bonePtr, *bone; + + int frameSize; + + // don't lerp if lerping off, or this is the only frame, or the last frame... + // + if (backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame) + { + backlerp = 0; // if backlerp is 0, lerping is off and frontlerp is never used + frontlerp = 1; + } + else + { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0f - backlerp; + } + + header = (mdrHeader_t *)((byte *)surface + surface->ofsHeader); + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + frame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles * 3 ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + + // Set up all triangles. + for (j = 0 ; j < indexes ; j++) + { + tess.indexes[baseIndex + j] = baseVertex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp ) + { + // no lerping needed + bonePtr = frame->bones; + } + else + { + bonePtr = bones; + + for ( i = 0 ; i < header->numBones*12 ; i++ ) + { + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + v = (mdrVertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) + { + vec3_t tempVert, tempNormal; + mdrWeight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) + { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + tess.normal[baseVertex + j][0] = tempNormal[0]; + tess.normal[baseVertex + j][1] = tempNormal[1]; + tess.normal[baseVertex + j][2] = tempNormal[2]; + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + v = (mdrVertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} + + +#define MC_MASK_X ((1<<(MC_BITS_X))-1) +#define MC_MASK_Y ((1<<(MC_BITS_Y))-1) +#define MC_MASK_Z ((1<<(MC_BITS_Z))-1) +#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1) + +#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2)) + +#define MC_POS_X (0) +#define MC_SHIFT_X (0) + +#define MC_POS_Y ((((MC_BITS_X))/8)) +#define MC_SHIFT_Y ((((MC_BITS_X)%8))) + +#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8)) +#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8))) + +#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8)) +#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8))) + +#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8)) +#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8))) + +#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8)) +#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8))) + +#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8)) +#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8))) + +#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8)) +#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8))) + +#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8)) +#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8))) + +#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8)) +#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8))) + +#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8)) +#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8))) + +#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8)) +#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8))) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp) +{ + int val; + + val=(int)((unsigned short *)(comp))[0]; + val-=1<<(MC_BITS_X-1); + mat[0][3]=((float)(val))*MC_SCALE_X; + + val=(int)((unsigned short *)(comp))[1]; + val-=1<<(MC_BITS_Y-1); + mat[1][3]=((float)(val))*MC_SCALE_Y; + + val=(int)((unsigned short *)(comp))[2]; + val-=1<<(MC_BITS_Z-1); + mat[2][3]=((float)(val))*MC_SCALE_Z; + + val=(int)((unsigned short *)(comp))[3]; + val-=1<<(MC_BITS_VECT-1); + mat[0][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[4]; + val-=1<<(MC_BITS_VECT-1); + mat[0][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[5]; + val-=1<<(MC_BITS_VECT-1); + mat[0][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[6]; + val-=1<<(MC_BITS_VECT-1); + mat[1][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[7]; + val-=1<<(MC_BITS_VECT-1); + mat[1][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[8]; + val-=1<<(MC_BITS_VECT-1); + mat[1][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[9]; + val-=1<<(MC_BITS_VECT-1); + mat[2][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[10]; + val-=1<<(MC_BITS_VECT-1); + mat[2][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[11]; + val-=1<<(MC_BITS_VECT-1); + mat[2][2]=((float)(val))*MC_SCALE_VECT; +} diff --git a/src/renderergl1/tr_backend.cpp b/src/renderergl1/tr_backend.cpp new file mode 100644 index 0000000..172228d --- /dev/null +++ b/src/renderergl1/tr_backend.cpp @@ -0,0 +1,1163 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + +backEndData_t *backEndData; +backEndState_t backEnd; + + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +/* +** GL_Bind +*/ +void GL_Bind( image_t *image ) { + int texnum; + + if ( !image ) { + ri.Printf( PRINT_WARNING, "GL_Bind: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { + if ( image ) { + image->frameUsed = tr.frameCount; + } + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture (GL_TEXTURE_2D, texnum); + } +} + +/* +** GL_SelectTexture +*/ +void GL_SelectTexture( int unit ) +{ + if ( glState.currenttmu == unit ) + { + return; + } + + if ( unit == 0 ) + { + qglActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + } + else if ( unit == 1 ) + { + qglActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + } else { + ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit ); + } + + glState.currenttmu = unit; +} + + +/* +** GL_BindMultitexture +*/ +void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) { + int texnum0, texnum1; + + texnum0 = image0->texnum; + texnum1 = image1->texnum; + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum0 = texnum1 = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[1] != texnum1 ) { + GL_SelectTexture( 1 ); + image1->frameUsed = tr.frameCount; + glState.currenttextures[1] = texnum1; + qglBindTexture( GL_TEXTURE_2D, texnum1 ); + } + if ( glState.currenttextures[0] != texnum0 ) { + GL_SelectTexture( 0 ); + image0->frameUsed = tr.frameCount; + glState.currenttextures[0] = texnum0; + qglBindTexture( GL_TEXTURE_2D, texnum0 ); + } +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { + if ( glState.faceCulling == cullType ) { + return; + } + + glState.faceCulling = cullType; + + if ( cullType == CT_TWO_SIDED ) + { + qglDisable( GL_CULL_FACE ); + } + else + { + bool cullFront; + qglEnable( GL_CULL_FACE ); + + cullFront = (cullType == CT_FRONT_SIDED); + if ( backEnd.viewParms.isMirror ) + { + cullFront = !cullFront; + } + + qglCullFace( cullFront ? GL_FRONT : GL_BACK ); + } +} + +/* +** GL_TexEnv +*/ +void GL_TexEnv( int env ) +{ + if ( env == glState.texEnv[glState.currenttmu] ) + { + return; + } + + glState.texEnv[glState.currenttmu] = env; + + + switch ( env ) + { + case GL_MODULATE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case GL_REPLACE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + break; + case GL_DECAL: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + break; + case GL_ADD: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; + default: + ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed", env ); + break; + } +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) +{ + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) + { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_EQUAL ) + { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) + { + qglDepthFunc( GL_EQUAL ); + } + else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + GLenum srcFactor = GL_ONE, dstFactor = GL_ONE; + + if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + ri.Error( ERR_DROP, "GL_State: invalid src blend state bits" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits" ); + break; + } + + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } + else + { + qglDisable( GL_BLEND ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) + { + if ( stateBits & GLS_DEPTHMASK_TRUE ) + { + qglDepthMask( GL_TRUE ); + } + else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) + { + if ( stateBits & GLS_POLYMODE_LINE ) + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) + { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) + { + qglDisable( GL_DEPTH_TEST ); + } + else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) + { + switch ( stateBits & GLS_ATEST_BITS ) + { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_GT_0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GREATER, 0.0f ); + break; + case GLS_ATEST_LT_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5f ); + break; + case GLS_ATEST_GE_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5f ); + break; + default: + assert( 0 ); + break; + } + } + + glState.glStateBits = stateBits; +} + + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + backEnd.isHyperspace = true; +} + + +static void SetViewportAndScissor( void ) { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.viewParms.projectionMatrix ); + qglMatrixMode(GL_MODELVIEW); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + int clearBits = 0; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish (); + glState.finishCalled = true; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = true; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = false; + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + // clear relevant buffers + clearBits = GL_DEPTH_BUFFER_BIT; + + if ( r_measureOverdraw->integer || r_shadows->integer == 2 ) + { + clearBits |= GL_STENCIL_BUFFER_BIT; + } + if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) ) + { + clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky + } + qglClear( clearBits ); + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) + { + RB_Hyperspace(); + return; + } + else + { + backEnd.isHyperspace = false; + } + + glState.faceCulling = -1; // force face culling to set next time + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = false; + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { + float plane[4]; + GLdouble plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct (backEnd.viewParms.orientation.axis[0], plane); + plane2[1] = DotProduct (backEnd.viewParms.orientation.axis[1], plane); + plane2[2] = DotProduct (backEnd.viewParms.orientation.axis[2], plane); + plane2[3] = DotProduct (plane, backEnd.viewParms.orientation.origin) - plane[3]; + + qglLoadMatrixf( s_flipMatrix ); + qglClipPlane (GL_CLIP_PLANE0, plane2); + qglEnable (GL_CLIP_PLANE0); + } else { + qglDisable (GL_CLIP_PLANE0); + } +} + + +/* +================== +RB_RenderDrawSurfList +================== +*/ +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + bool depthRange, oldDepthRange, isCrosshair, wasCrosshair; + int i; + drawSurf_t *drawSurf; + int oldSort; + double originalTime; + + // save original time for entity shader offsets + originalTime = backEnd.refdef.floatTime; + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView (); + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = false; + wasCrosshair = false; + oldDlighted = false; + oldSort = -1; + depthRange = false; + + backEnd.pc.c_surfaces += numDrawSurfs; + + for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) { + if ( drawSurf->sort == oldSort ) { + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + oldSort = drawSurf->sort; + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if ( shader != NULL && ( shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) ) { + if (oldShader != NULL) { + RB_EndSurface(); + } + RB_BeginSurface( shader, fogNum ); + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + } + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + depthRange = isCrosshair = false; + + if ( entityNum != REFENTITYNUM_WORLD ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + // FIXME: e.shaderTime must be passed as int to avoid fp-precision loss issues + backEnd.refdef.floatTime = originalTime - (double)backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.orientation ); + } + + if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) + { + // hack the depth range to prevent view model from poking into walls + depthRange = true; + + if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR) + isCrosshair = true; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.orientation = backEnd.viewParms.world; + // we haveientation to reset the shaderTime as well otherwise image animations on + // the worientationld (like water) continue with the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.orientation ); + } + + qglLoadMatrixf( backEnd.orientation.modelMatrix ); + + // + // change depthrange. Also change projection matrix so first person weapon does not look like coming + // out of the screen. + // + if (oldDepthRange != depthRange || wasCrosshair != isCrosshair) + { + if (depthRange) + { + if(backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + if(isCrosshair) + { + if(oldDepthRange) + { + // was not a crosshair but now is, change back proj matrix + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(backEnd.viewParms.projectionMatrix); + qglMatrixMode(GL_MODELVIEW); + } + } + else + { + viewParms_t temp = backEnd.viewParms; + + R_SetupProjection(&temp, r_znear->value, false); + + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(temp.projectionMatrix); + qglMatrixMode(GL_MODELVIEW); + } + } + + if(!oldDepthRange) + qglDepthRange (0, 0.3); + } + else + { + if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(backEnd.viewParms.projectionMatrix); + qglMatrixMode(GL_MODELVIEW); + } + + qglDepthRange (0, 1); + } + + oldDepthRange = depthRange; + wasCrosshair = isCrosshair; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + backEnd.refdef.floatTime = originalTime; + + // draw the contents of the last shader batch + if (oldShader != NULL) { + RB_EndSurface(); + } + + // go back to the world modelview matrix + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + if ( depthRange ) { + qglDepthRange (0, 1); + } + + if (r_drawSun->integer) { + RB_DrawSun(0.1, tr.sunShader); + } + + // darken down any stencil shadows + RB_ShadowFinish(); + + // add light flares on lights that aren't obscured + RB_RenderFlares(); +} + + +/* +============================================================================ + +RENDER BACK END FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +void RB_SetGL2D (void) { + backEnd.projection2D = true; + + // set 2D virtual screen size + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglMatrixMode(GL_PROJECTION); + qglLoadIdentity (); + qglOrtho (0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1); + qglMatrixMode(GL_MODELVIEW); + qglLoadIdentity (); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = ri.Milliseconds(); + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; +} + + +/* +============= +RE_StretchRaw + +FIXME: not exactly backend +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, bool dirty) { + int i, j; + int start, end; + + if ( !tr.registered ) { + return; + } + R_IssuePendingRenderCommands(); + + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // we definately want to sync every frame for the cinematics + qglFinish(); + + start = 0; + if ( r_speeds->integer ) { + start = ri.Milliseconds(); + } + + // make sure rows and cols are powers of 2 + for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { + } + for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { + } + if ( ( 1 << i ) != cols || ( 1 << j ) != rows) { + ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); + } + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + + if ( r_speeds->integer ) { + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + + RB_SetGL2D(); + + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglBegin (GL_QUADS); + qglTexCoord2f ( 0.5f / cols, 0.5f / rows ); + qglVertex2f (x, y); + qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows ); + qglVertex2f (x+w, y); + qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows ); + qglVertex2f (x+w, y+h); + qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows ); + qglVertex2f (x, y+h); + qglEnd (); +} + +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, bool dirty) { + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic ( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + if ( r_clear->integer ) { + qglClearColor( 1, 0, 0.5, 1 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + return (const void *)(cmd + 1); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + int i; + image_t *image; + float x, y, w, h; + int start, end; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + + start = ri.Milliseconds(); + + for ( i=0 ; iinteger == 2 ) { + w *= image->uploadWidth / 512.0f; + h *= image->uploadHeight / 512.0f; + } + + GL_Bind( image ); + qglBegin (GL_QUADS); + qglTexCoord2f( 0, 0 ); + qglVertex2f( x, y ); + qglTexCoord2f( 1, 0 ); + qglVertex2f( x + w, y ); + qglTexCoord2f( 1, 1 ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0, 1 ); + qglVertex2f( x, y + h ); + qglEnd(); + } + + qglFinish(); + + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); + +} + +/* +============= +RB_ColorMask + +============= +*/ +const void *RB_ColorMask(const void *data) +{ + const colorMaskCommand_t *cmd = (const colorMaskCommand_t*)data; + + qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]); + + return (const void *)(cmd + 1); +} + +/* +============= +RB_ClearDepth + +============= +*/ +const void *RB_ClearDepth(const void *data) +{ + const clearDepthCommand_t *cmd = (const clearDepthCommand_t*)data; + + if(tess.numIndexes) + RB_EndSurface(); + + // texture swapping test + if (r_showImages->integer) + RB_ShowImages(); + + qglClear(GL_DEPTH_BUFFER_BIT); + + return (const void *)(cmd + 1); +} + +/* +============= +RB_SwapBuffers + +============= +*/ +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = (unsigned char*)ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + ri.Hunk_FreeTempMemory( stencilReadback ); + } + + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.projection2D = false; + + return (const void *)(cmd + 1); +} + +/* +==================== +RB_ExecuteRenderCommands +==================== +*/ +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = ri.Milliseconds (); + + while ( 1 ) { + data = PADP(data, sizeof(void *)); + + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + case RC_SCREENSHOT: + data = RB_TakeScreenshotCmd( data ); + break; + case RC_VIDEOFRAME: + data = RB_TakeVideoFrameCmd( data ); + break; + case RC_COLORMASK: + data = RB_ColorMask(data); + break; + case RC_CLEARDEPTH: + data = RB_ClearDepth(data); + break; + case RC_END_OF_LIST: + default: + // stop rendering + t2 = ri.Milliseconds (); + backEnd.pc.msec = t2 - t1; + return; + } + } + +} diff --git a/src/renderergl1/tr_bsp.cpp b/src/renderergl1/tr_bsp.cpp new file mode 100644 index 0000000..b15bc79 --- /dev/null +++ b/src/renderergl1/tr_bsp.cpp @@ -0,0 +1,1869 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_map.c + +#include "tr_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift, r, g, b; + + // shift the color data based on overbright range + shift = r_mapOverBrightBits->integer - tr.overbrightBits; + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +static void R_LoadLightmaps( lump_t *l ) { + byte *buf, *buf_p; + int len; + byte image[LIGHTMAP_SIZE*LIGHTMAP_SIZE*4]; + int i, j; + float maxIntensity = 0; + double sumIntensity = 0; + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + R_IssuePendingRenderCommands(); + + // create all the lightmaps + tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3); + if ( tr.numLightmaps == 1 ) { + //FIXME: HACK: maps with only one lightmap turn up fullbright for some reason. + //this avoids this, but isn't the correct solution. + tr.numLightmaps++; + } + + // if we are in r_vertexLight mode, we don't need the lightmaps at all + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + + tr.lightmaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); + for ( i = 0 ; i < tr.numLightmaps ; i++ ) { + // expand the 24 bit on-disk to 32 bit + buf_p = buf + i * LIGHTMAP_SIZE*LIGHTMAP_SIZE * 3; + + if ( r_lightmap->integer == 2 ) + { // color code by intensity as development tool (FIXME: check range) + for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) + { + float r = buf_p[j*3+0]; + float g = buf_p[j*3+1]; + float b = buf_p[j*3+2]; + float intensity; + float out[3] = {0.0, 0.0, 0.0}; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) + intensity = 1.0f; + else + intensity /= 255.0f; + + if ( intensity > maxIntensity ) + maxIntensity = intensity; + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j*4+0] = out[0] * 255; + image[j*4+1] = out[1] * 255; + image[j*4+2] = out[2] * 255; + image[j*4+3] = 255; + + sumIntensity += intensity; + } + } else { + for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) { + R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] ); + image[j*4+3] = 255; + } + } + tr.lightmaps[i] = R_CreateImage( va("*lightmap%d",i), image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, IMGTYPE_COLORALPHA, + IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, 0 ); + } + + if ( r_lightmap->integer == 2 ) { + ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } +} + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = ( s_worldData.numClusters + 63 ) & ~63; + s_worldData.novis = (byte*)ri.Hunk_Alloc( len, h_low ); + Com_Memset( s_worldData.novis, 0xff, len ); + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + s_worldData.numClusters = LittleLong( ((int *)buf)[0] ); + s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + } else { + byte *dest; + + dest = (byte*)ri.Hunk_Alloc( len - 8, h_low ); + Com_Memcpy( dest, buf + 8, len - 8 ); + s_worldData.vis = dest; + } +} + +//=============================================================================== + + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) { + shader_t *shader; + dshader_t *dsh; + + int _shaderNum = LittleLong( shaderNum ); + if ( _shaderNum < 0 || _shaderNum >= s_worldData.numShaders ) { + ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", _shaderNum ); + } + dsh = &s_worldData.shaders[ _shaderNum ]; + + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + lightmapNum = LIGHTMAP_BY_VERTEX; + } + + if ( r_fullbright->integer ) { + lightmapNum = LIGHTMAP_WHITEIMAGE; + } + + shader = R_FindShader( dsh->shader, lightmapNum, true ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + int i, j; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + int lightmapNum; + int sfaceSize, ofsIndexes; + + lightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numPoints = LittleLong( ds->numVerts ); + if (numPoints > MAX_FACE_POINTS) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints); + numPoints = MAX_FACE_POINTS; + surf->shader = tr.defaultShader; + } + + numIndexes = LittleLong( ds->numIndexes ); + + // create the srfSurfaceFace_t + sfaceSize = ( size_t ) &((srfSurfaceFace_t *)0)->points[numPoints]; + ofsIndexes = sfaceSize; + sfaceSize += sizeof( int ) * numIndexes; + + cv = (srfSurfaceFace_t*)ri.Hunk_Alloc( sfaceSize, h_low ); + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + cv->points[i][j] = LittleFloat( verts[i].xyz[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + cv->points[i][3+j] = LittleFloat( verts[i].st[j] ); + cv->points[i][5+j] = LittleFloat( verts[i].lightmap[j] ); + } + R_ColorShiftLightingBytes( verts[i].color, (byte *)&cv->points[i][7] ); + } + + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + ((int *)((byte *)cv + cv->ofsIndices ))[i] = LittleLong( indexes[ i ] ); + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->plane.dist = DotProduct( cv->points[0], cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, msurface_t *surf ) { + srfGridMesh_t *grid; + int i, j; + int width, height, numPoints; + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE]; + int lightmapNum; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + lightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + points[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + points[i].st[j] = LittleFloat( verts[i].st[j] ); + points[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] ); + } + R_ColorShiftLightingBytes( verts[i].color, points[i].color ); + } + + // pre-tesseleate + grid = R_SubdividePatchToGrid( width, height, points ); + surf->data = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfTriangles_t *tri; + int i, j; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong( ds->numVerts ); + numIndexes = LittleLong( ds->numIndexes ); + + tri = (srfTriangles_t*)ri.Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + + numIndexes * sizeof( tri->indexes[0] ), h_low ); + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = (drawVert_t *)(tri + 1); + tri->indexes = (int *)(tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + ClearBounds( tri->bounds[0], tri->bounds[1] ); + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + tri->verts[i].st[j] = LittleFloat( verts[i].st[j] ); + tri->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] ); + } + + R_ColorShiftLightingBytes( verts[i].color, tri->verts[i].color ); + } + + // copy indexes + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = LittleLong( indexes[i] ); + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + ri.Error( ERR_DROP, "Bad index in triangle surface" ); + } + } +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfFlare_t *flare; + int i; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + flare = (srfFlare_t*)ri.Hunk_Alloc( sizeof( *flare ), h_low ); + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } +} + + +/* +================= +R_MergedWidthPoints + +returns true if there are grid points merged on a width edge +================= +*/ +int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->width-1; i++) { + for (j = i + 1; j < grid->width-1; j++) { + if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue; + return true; + } + } + return false; +} + +/* +================= +R_MergedHeightPoints + +returns true if there are grid points merged on a height edge +================= +*/ +int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->height-1; i++) { + for (j = i + 1; j < grid->height-1; j++) { + if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue; + return true; + } + } + return false; +} + +/* +================= +R_FixSharedVertexLodError_r + +NOTE: never sync LoD through grid edges with merged points! + +FIXME: write generalized version that also avoids cracks between a patch and one that meets half way? +================= +*/ +void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) { + int j, k, l, m, n, offset1, offset2, touch; + srfGridMesh_t *grid2; + + for ( j = start; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // if the LOD errors are already fixed for this patch + if ( grid2->lodFixed == 2 ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + touch = false; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->width-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->widthLodError[k]; + touch = true; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->widthLodError[k]; + touch = true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->height-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->heightLodError[k]; + touch = true; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->heightLodError[k]; + touch = true; + } + } + } + } + if (touch) { + grid2->lodFixed = 2; + R_FixSharedVertexLodError_r ( start, grid2 ); + //NOTE: this would be correct but makes things really slow + //grid2->lodFixed = 1; + } + } +} + +/* +================= +R_FixSharedVertexLodError + +This function assumes that all patches in one group are nicely stitched together for the highest LoD. +If this is not the case this function will still do its job but won't fix the highest LoD cracks. +================= +*/ +void R_FixSharedVertexLodError( void ) { + int i; + srfGridMesh_t *grid1; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodFixed ) + continue; + // + grid1->lodFixed = 2; + // recursively fix other patches in the same LOD group + R_FixSharedVertexLodError_r( i + 1, grid1); + } +} + + +/* +=============== +R_StitchPatches +=============== +*/ +int R_StitchPatches( int grid1num, int grid2num ) { + float *v1, *v2; + srfGridMesh_t *grid1, *grid2; + int k, l, m, n, offset1, offset2, row, column; + + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->width-2; k += 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->height-2; k += 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = grid1->width-1; k > 1; k -= 2) { + + for (m = 0; m < 2; m++) { + + if ( !grid2 || grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (!grid2 || grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + if (!grid2) + break; + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = grid1->height-1; k > 1; k -= 2) { + for (m = 0; m < 2; m++) { + + if ( !grid2 || grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (!grid2 || grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + return false; +} + +/* +=============== +R_TryStitchPatch + +This function will try to stitch patches in the same LoD group together for the highest LoD. + +Only single missing vertice cracks will be fixed. + +Vertices will be joined at the patch side a crack is first found, at the other side +of the patch (on the same row or column) the vertices will not be joined and cracks +might still appear at that side. +=============== +*/ +int R_TryStitchingPatch( int grid1num ) { + int j, numstitches; + srfGridMesh_t *grid1, *grid2; + + numstitches = 0; + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + for ( j = 0; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + while (R_StitchPatches(grid1num, j)) + { + numstitches++; + } + } + return numstitches; +} + +/* +=============== +R_StitchAllPatches +=============== +*/ +void R_StitchAllPatches( void ) { + int i, stitched, numstitches; + srfGridMesh_t *grid1; + + numstitches = 0; + do + { + stitched = false; + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodStitched ) + continue; + // + grid1->lodStitched = true; + stitched = true; + // + numstitches += R_TryStitchingPatch( i ); + } + } + while (stitched); + ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches ); +} + +/* +=============== +R_MovePatchSurfacesToHunk +=============== +*/ +void R_MovePatchSurfacesToHunk(void) { + int i, size; + srfGridMesh_t *grid, *hunkgrid; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid->surfaceType != SF_GRID ) + continue; + // + size = (grid->width * grid->height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + hunkgrid = (srfGridMesh_t*)ri.Hunk_Alloc( size, h_low ); + Com_Memcpy(hunkgrid, grid, size); + + hunkgrid->widthLodError = (float*)ri.Hunk_Alloc( grid->width * 4, h_low ); + Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 ); + + hunkgrid->heightLodError = (float*)ri.Hunk_Alloc( grid->height * 4, h_low ); + Com_Memcpy( hunkgrid->heightLodError, grid->heightLodError, grid->height * 4 ); + + R_FreeSurfaceGridMesh( grid ); + + s_worldData.surfaces[i].data = (surfaceType_t*) hunkgrid; + } +} + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) { + dsurface_t *in; + msurface_t *out; + drawVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + in = (dsurface_t*)(fileBase + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = surfs->filelen / sizeof(*in); + + dv = (drawVert_t*)(fileBase + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (int*)(fileBase + indexLump->fileofs); + if ( indexLump->filelen % sizeof(*indexes)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + out = (msurface_t*)ri.Hunk_Alloc ( count * sizeof(*out), h_low ); + + s_worldData.surfaces = out; + s_worldData.numsurfaces = count; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh ( in, dv, out ); + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, out, indexes ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, out, indexes ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes ); + numFlares++; + break; + default: + ri.Error( ERR_DROP, "Bad surfaceType" ); + } + } + +#ifdef PATCH_STITCHING + R_StitchAllPatches(); +#endif + + R_FixSharedVertexLodError(); + +#ifdef PATCH_STITCHING + R_MovePatchSurfacesToHunk(); +#endif + + ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", + numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (dmodel_t*)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + + s_worldData.bmodels = out = (bmodel_t*)ri.Hunk_Alloc( count * sizeof(*out), h_low ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = LittleFloat (in->mins[j]); + out->bounds[1][j] = LittleFloat (in->maxs[j]); + } + + out->firstSurface = s_worldData.surfaces + LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = (dnode_t*)(fileBase + nodeLump->fileofs); + if (nodeLump->filelen % sizeof(dnode_t) || + leafLump->filelen % sizeof(dleaf_t) ) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + numNodes = nodeLump->filelen / sizeof(dnode_t); + numLeafs = leafLump->filelen / sizeof(dleaf_t); + + out = (mnode_t*)ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low); + + s_worldData.nodes = out; + s_worldData.numnodes = numNodes + numLeafs; + s_worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i=0 ; imins[j] = LittleLong (in->mins[j]); + out->maxs[j] = LittleLong (in->maxs[j]); + } + + p = LittleLong(in->planeNum); + out->plane = s_worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = LittleLong (in->children[j]); + if (p >= 0) + out->children[j] = s_worldData.nodes + p; + else + out->children[j] = s_worldData.nodes + numNodes + (-1 - p); + } + } + + // load leafs + inLeaf = (dleaf_t*)(fileBase + leafLump->fileofs); + for ( i=0 ; imins[j] = LittleLong (inLeaf->mins[j]); + out->maxs[j] = LittleLong (inLeaf->maxs[j]); + } + + out->cluster = LittleLong(inLeaf->cluster); + out->area = LittleLong(inLeaf->area); + + if ( out->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = s_worldData.marksurfaces + + LittleLong(inLeaf->firstLeafSurface); + out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces); + } + + // chain decendants + R_SetParent (s_worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l ) { + int i, count; + dshader_t *in, *out; + + in = (dshader_t*)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = (dshader_t*)ri.Hunk_Alloc ( count*sizeof(*out), h_low ); + + s_worldData.shaders = out; + s_worldData.numShaders = count; + + Com_Memcpy( out, in, count*sizeof(*out) ); + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = (msurface_t**)ri.Hunk_Alloc ( count*sizeof(*out), h_low); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = (cplane_t*)ri.Hunk_Alloc ( count*2*sizeof(*out), h_low); + + s_worldData.planes = out; + s_worldData.numplanes = count; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) { + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide; + + fogs = (dfog_t*)(fileBase + l->fileofs); + if (l->filelen % sizeof(*fogs)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + count = l->filelen / sizeof(*fogs); + + // create fog structures for them + s_worldData.numfogs = count + 1; + s_worldData.fogs = (fog_t*)ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low); + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = (dbrush_t*)(fileBase + brushesLump->fileofs); + if (brushesLump->filelen % sizeof(*brushes)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + brushesCount = brushesLump->filelen / sizeof(*brushes); + + sides = (dbrushside_t*)(fileBase + sidesLump->fileofs); + if (sidesLump->filelen % sizeof(*sides)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + sidesCount = sidesLump->filelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = LittleLong( fogs->brushNum ); + + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + ri.Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + ri.Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, true ); + + out->parms = shader->fogParms; + + out->colorInt = ColorBytes4 ( shader->fogParms.color[0] * tr.identityLight, + shader->fogParms.color[1] * tr.identityLight, + shader->fogParms.color[2] * tr.identityLight, 1.0 ); + + d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + out->hasSurface = false; + } else { + out->hasSurface = true; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + +} + + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l ) { + int i; + vec3_t maxs; + int numGridPoints; + world_t *w; + float *wMins, *wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != numGridPoints * 8 ) { + ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridData = (byte*)ri.Hunk_Alloc( l->filelen, h_low ); + Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridPoints ; i++ ) { + R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] ); + R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] ); + } +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l ) { + char *p, *token, *s; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + p = (char *)(fileBase + l->fileofs); + + // store for reference by the cgame + w->entityString = (char*)ri.Hunk_Alloc( l->filelen + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + // check for remapping of shaders for vertex lighting + if (!Q_strncmp(keyname, "vertexremapshader", strlen("vertexremapshader")) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + if (r_vertexLight->integer) { + R_RemapShader(value, s, "0"); + } + continue; + } + // check for remapping of shaders + if (!Q_strncmp(keyname, "remapshader", strlen("remapshader")) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader(value, s, "0"); + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + } +} + +/* +================= +R_GetEntityToken +================= +*/ +bool R_GetEntityToken( char *buffer, int size ) { + const char *s; + + s = COM_Parse( &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint && !s[0] ) { + s_worldData.entityParsePoint = s_worldData.entityString; + return false; + } else { + return true; + } +} + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap( const char *name ) { + int i; + dheader_t *header; + union { + byte *b; + void *v; + } buffer; + byte *startMarker; + + if ( tr.worldMapLoaded ) { + ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map" ); + } + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + tr.worldMapLoaded = true; + + // load it + ri.FS_ReadFile( name, &buffer.v ); + if ( !buffer.b ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name); + } + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + Com_Memset( &s_worldData, 0, sizeof( s_worldData ) ); + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName)); + + startMarker = (byte*)ri.Hunk_Alloc(0, h_low); + c_gridVerts = 0; + + header = (dheader_t *)buffer.b; + fileBase = (byte *)header; + + i = LittleLong (header->version); + if ( i != BSP_VERSION ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", + name, i, BSP_VERSION); + } + + // swap all the lumps + for (i=0 ; ilumps[LUMP_SHADERS] ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS] ); + R_LoadPlanes (&header->lumps[LUMP_PLANES]); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] ); + R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]); + R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]); + R_LoadSubmodels (&header->lumps[LUMP_MODELS]); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] ); + R_LoadEntities( &header->lumps[LUMP_ENTITIES] ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] ); + + s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker; + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; + + ri.FS_FreeFile( buffer.v ); +} diff --git a/src/renderergl1/tr_cmds.cpp b/src/renderergl1/tr_cmds.cpp new file mode 100644 index 0000000..12bfac4 --- /dev/null +++ b/src/renderergl1/tr_cmds.cpp @@ -0,0 +1,601 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if (r_speeds->integer == 1) { + ri.Printf (PRINT_ALL, "%i/%i shaders/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, + R_SumOfUsedImages()/(1000000.0f), backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); + } else if (r_speeds->integer == 2) { + ri.Printf (PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + ri.Printf (PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if (r_speeds->integer == 3) { + ri.Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster ); + } else if (r_speeds->integer == 4) { + if ( backEnd.pc.c_dlightVertexes ) { + ri.Printf (PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } + else if (r_speeds->integer == 5 ) + { + ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar ); + } + else if (r_speeds->integer == 6 ) + { + ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_IssueRenderCommands +==================== +*/ +void R_IssueRenderCommands( bool runPerformanceCounters ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + assert(cmdList); + // add an end-of-list command + *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + RB_ExecuteRenderCommands( cmdList->cmds ); + } +} + + +/* +==================== +R_IssuePendingRenderCommands + +Issue any pending commands and wait for them to complete. +==================== +*/ +void R_IssuePendingRenderCommands( void ) { + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( false ); +} + +/* +============ +R_GetCommandBufferReserved + +make sure there is enough command space +============ +*/ +void *R_GetCommandBufferReserved( int bytes, int reservedBytes ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + bytes = PAD(bytes, sizeof(void *)); + + // always leave room for the end of list command + if ( cmdList->used + bytes + sizeof( int ) + reservedBytes > MAX_RENDER_COMMANDS ) { + if ( bytes > MAX_RENDER_COMMANDS - sizeof( int ) ) { + ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + +/* +============= +R_GetCommandBuffer + +returns NULL if there is not enough space for important commands +============= +*/ +void *R_GetCommandBuffer( int bytes ) { + return R_GetCommandBufferReserved( bytes, PAD( sizeof( swapBuffersCommand_t ), sizeof(void *) ) ); +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = (drawSurfsCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = (setColorCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( !rgba ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + rgba = colorWhite; + } + + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; +} + +/* +============= +R_ClipRegion +============= +*/ +static bool R_ClipRegion ( float *x, float *y, float *w, float *h, + float *s1, float *t1, float *s2, float *t2 ) { + float left, top, right, bottom; + float _s1, _t1, _s2, _t2; + float clipLeft, clipTop, clipRight, clipBottom; + + if (tr.clipRegion[2] <= tr.clipRegion[0] || + tr.clipRegion[3] <= tr.clipRegion[1] ) { + return false; + } + + left = *x; + top = *y; + right = *x + *w; + bottom = *y + *h; + + _s1 = *s1; + _t1 = *t1; + _s2 = *s2; + _t2 = *t2; + + clipLeft = tr.clipRegion[0]; + clipTop = tr.clipRegion[1]; + clipRight = tr.clipRegion[2]; + clipBottom = tr.clipRegion[3]; + + // Completely clipped away + if ( right <= clipLeft || left >= clipRight || + bottom <= clipTop || top >= clipBottom ) { + return true; + } + + // Clip left edge + if ( left < clipLeft ) { + float f = ( clipLeft - left ) / ( right - left ); + *s1 = ( f * ( _s2 - _s1 ) ) + _s1; + *x = clipLeft; + *w -= ( clipLeft - left ); + } + + // Clip right edge + if ( right > clipRight ) { + float f = ( clipRight - right ) / ( left - right ); + *s2 = ( f * ( _s1 - _s2 ) ) + _s2; + *w = clipRight - *x; + } + + // Clip top edge + if ( top < clipTop ) { + float f = ( clipTop - top ) / ( bottom - top ); + *t1 = ( f * ( _t2 - _t1 ) ) + _t1; + *y = clipTop; + *h -= ( clipTop - top ); + } + + // Clip bottom edge + if ( bottom > clipBottom ) { + float f = ( clipBottom - bottom ) / ( top - bottom ); + *t2 = ( f * ( _t1 - _t2 ) ) + _t2; + *h = clipBottom - *y; + } + + return false; +} + +/* +============= +RE_SetClipRegion +============= +*/ +void RE_SetClipRegion( const float *region ) { + if ( region == NULL ) { + Com_Memset( tr.clipRegion, 0, sizeof( vec4_t ) ); + } else { + Vector4Copy( region, tr.clipRegion ); + } +} + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + if (!tr.registered) { + return; + } + if (R_ClipRegion(&x, &y, &w, &h, &s1, &t1, &s2, &t2)) { + return; + } + cmd = (stretchPicCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +#define MODE_RED_CYAN 1 +#define MODE_RED_BLUE 2 +#define MODE_RED_GREEN 3 +#define MODE_GREEN_MAGENTA 4 +#define MODE_MAX MODE_GREEN_MAGENTA + +void R_SetColorMode(GLboolean *rgba, stereoFrame_t stereoFrame, int colormode) +{ + rgba[0] = rgba[1] = rgba[2] = rgba[3] = GL_TRUE; + + if(colormode > MODE_MAX) + { + if(stereoFrame == STEREO_LEFT) + stereoFrame = STEREO_RIGHT; + else if(stereoFrame == STEREO_RIGHT) + stereoFrame = STEREO_LEFT; + + colormode -= MODE_MAX; + } + + if(colormode == MODE_GREEN_MAGENTA) + { + if(stereoFrame == STEREO_LEFT) + rgba[0] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + rgba[1] = GL_FALSE; + } + else + { + if(stereoFrame == STEREO_LEFT) + rgba[1] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + { + rgba[0] = GL_FALSE; + + if(colormode == MODE_RED_BLUE) + rgba[1] = GL_FALSE; + else if(colormode == MODE_RED_GREEN) + rgba[2] = GL_FALSE; + } + } +} + + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd = NULL; + colorMaskCommand_t *colcmd = NULL; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = false; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // + if ( r_measureOverdraw->integer ) + { + if ( glConfig.stencilBits < 4 ) + { + ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = false; + } + else if ( r_shadows->integer == 2 ) + { + ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = false; + } + else + { + R_IssuePendingRenderCommands(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = false; + } + else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_IssuePendingRenderCommands(); + qglDisable( GL_STENCIL_TEST ); + } + r_measureOverdraw->modified = false; + } + + // + // texturemode stuff + // + if ( r_textureMode->modified ) { + R_IssuePendingRenderCommands(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = false; + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = false; + + R_IssuePendingRenderCommands(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) + { + int err; + + R_IssuePendingRenderCommands(); + if ((err = qglGetError()) != GL_NO_ERROR) + ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!", err); + } + + if (glConfig.stereoEnabled) { + if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + cmd->commandId = RC_DRAW_BUFFER; + + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } + else + { + if(r_anaglyphMode->integer) + { + if(r_anaglyphMode->modified) + { + // clear both, front and backbuffer. + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + qglClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + qglDrawBuffer(GL_FRONT); + qglClear(GL_COLOR_BUFFER_BIT); + qglDrawBuffer(GL_BACK); + qglClear(GL_COLOR_BUFFER_BIT); + + r_anaglyphMode->modified = false; + } + + if(stereoFrame == STEREO_LEFT) + { + if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + if( !(colcmd = (colorMaskCommand_t*)R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else if(stereoFrame == STEREO_RIGHT) + { + clearDepthCommand_t *cldcmd; + + if( !(cldcmd = (clearDepthCommand_t*)R_GetCommandBuffer(sizeof(*cldcmd))) ) + return; + + cldcmd->commandId = RC_CLEARDEPTH; + + if( !(colcmd = (colorMaskCommand_t*)R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + + R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer); + colcmd->commandId = RC_COLORMASK; + } + else + { + if(stereoFrame != STEREO_CENTER) + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + + if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) ) + return; + } + + if(cmd) + { + cmd->commandId = RC_DRAW_BUFFER; + + if(r_anaglyphMode->modified) + { + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + r_anaglyphMode->modified = false; + } + + if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT")) + cmd->buffer = (int)GL_FRONT; + else + cmd->buffer = (int)GL_BACK; + } + } + + tr.refdef.stereoFrame = stereoFrame; +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = (swapBuffersCommand_t*)R_GetCommandBufferReserved( sizeof( *cmd ), 0 ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + + R_IssueRenderCommands( true ); + + R_InitNextFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; +} + +/* +============= +RE_TakeVideoFrame +============= +*/ +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, bool motionJpeg ) +{ + videoFrameCommand_t *cmd; + + if( !tr.registered ) { + return; + } + + cmd = (videoFrameCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if( !cmd ) { + return; + } + + cmd->commandId = RC_VIDEOFRAME; + + cmd->width = width; + cmd->height = height; + cmd->captureBuffer = captureBuffer; + cmd->encodeBuffer = encodeBuffer; + cmd->motionJpeg = motionJpeg; +} diff --git a/src/renderergl1/tr_curve.cpp b/src/renderergl1/tr_curve.cpp new file mode 100644 index 0000000..a56d026 --- /dev/null +++ b/src/renderergl1/tr_curve.cpp @@ -0,0 +1,627 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfGridMesh_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) { + out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]); + + out->st[0] = 0.5f * (a->st[0] + b->st[0]); + out->st[1] = 0.5f * (a->st[1] + b->st[1]); + + out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]); + out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]); + + out->color[0] = (a->color[0] + b->color[0]) >> 1; + out->color[1] = (a->color[1] + b->color[1]) >> 1; + out->color[2] = (a->color[2] + b->color[2]) >> 1; + out->color[3] = (a->color[3] + b->color[3]) >> 1; +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} + + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count = 0; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + bool good[8]; + bool wrapWidth, wrapHeight; + float len; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = false; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = true; + } + + wrapHeight = false; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = true; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = false; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = true; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + //if ( count == 0 ) { + // printf("bad normal\n"); + //} + VectorNormalize2( sum, dv->normal ); + } + } +} + + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width-1-j]; + ctrl[i][width-1-j] = temp; + } + } +} + + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + Com_Memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height-1-i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height, + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE] ) { + int i, j, size; + drawVert_t *vert; + vec3_t tmpVec; + srfGridMesh_t *grid; + + // copy the results out to a grid + size = (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + +#ifdef PATCH_STITCHING + grid = /*ri.Hunk_Alloc*/ (srfGridMesh_t*)ri.Malloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = /*ri.Hunk_Alloc*/ (float*)ri.Malloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = /*ri.Hunk_Alloc*/ (float*)ri.Malloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#else + grid = ri.Hunk_Alloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = ri.Hunk_Alloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = ri.Hunk_Alloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + // + return grid; +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { + ri.Free(grid->widthLodError); + ri.Free(grid->heightLodError); + ri.Free(grid); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + drawVert_t_cleared( prev ); + drawVert_t_cleared( next ); + drawVert_t_cleared( mid ); + float len, maxLen; + int dir; + int t; + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + + // FIXME: also check midpoints of adjacent patches against the control points + // this would basically stitch all patches in the same LOD group together. + + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t midxyz2; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + + ctrl[i][j+2].xyz[l] ) * 0.25f; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz2); + len = VectorLengthSquared( midxyz2 ); // we will do the sqrt later + if ( len > maxLen ) { + maxLen = len; + } + } + + maxLen = sqrt(maxLen); + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1f ) { + errorTable[dir][j+1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j+1] = 1.0f/maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j+1] = 1.0f/maxLen; + continue; // didn't need subdivision + } + + errorTable[dir][j+2] = 1.0f/maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); + LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k-2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j-1] = ctrl[k][j]; + } + errorTable[0][j-1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j-1][k] = ctrl[j][k]; + } + errorTable[1][j-1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + return R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); +} + +/* +=============== +R_GridInsertColumn +=============== +*/ +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) { + int i, j; + int width, height, oldwidth; + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldwidth = 0; + width = grid->width + 1; + if (width > MAX_GRID_SIZE) + return NULL; + height = grid->height; + for (i = 0; i < width; i++) { + if (i == column) { + //insert new column + for (j = 0; j < grid->height; j++) { + LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); + if (j == row) + VectorCopy(point, ctrl[j][i].xyz); + } + errorTable[0][i] = loderror; + continue; + } + errorTable[0][i] = grid->widthLodError[oldwidth]; + for (j = 0; j < grid->height; j++) { + ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; + } + oldwidth++; + } + for (j = 0; j < grid->height; j++) { + errorTable[1][j] = grid->heightLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} + +/* +=============== +R_GridInsertRow +=============== +*/ +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) { + int i, j; + int width, height, oldheight; + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldheight = 0; + width = grid->width; + height = grid->height + 1; + if (height > MAX_GRID_SIZE) + return NULL; + for (i = 0; i < height; i++) { + if (i == row) { + //insert new row + for (j = 0; j < grid->width; j++) { + LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); + if (j == column) + VectorCopy(point, ctrl[i][j].xyz); + } + errorTable[1][i] = loderror; + continue; + } + errorTable[1][i] = grid->heightLodError[oldheight]; + for (j = 0; j < grid->width; j++) { + ctrl[i][j] = grid->verts[oldheight * grid->width + j]; + } + oldheight++; + } + for (j = 0; j < grid->width; j++) { + errorTable[0][j] = grid->widthLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} diff --git a/src/renderergl1/tr_flares.cpp b/src/renderergl1/tr_flares.cpp new file mode 100644 index 0000000..53ca050 --- /dev/null +++ b/src/renderergl1/tr_flares.cpp @@ -0,0 +1,540 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_flares.c + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare relative to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that its midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + bool inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + bool visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + + int windowX, windowY; + float eyeZ; + + vec3_t origin; + vec3_t color; +} flare_t; + +#define MAX_FLARES 256 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + +int flareCoeff; + +/* +================== +R_SetFlareCoeff +================== +*/ +static void R_SetFlareCoeff( void ) { + + if(r_flareCoeff->value == 0.0f) + flareCoeff = atof(FLARE_STDCOEFF); + else + flareCoeff = r_flareCoeff->value; +} + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } + + R_SetFlareCoeff(); +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) { + int i; + flare_t *f; + vec3_t local; + float d = 1; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + if(normal && (normal[0] || normal[1] || normal[2])) + { + VectorSubtract( backEnd.viewParms.orientation.origin, point, local ); + VectorNormalizeFast(local); + d = DotProduct(local, normal); + + // If the viewer is behind the flare don't add it. + if(d < 0) + return; + } + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.orientation.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if (!f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + } + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = false; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + + VectorCopy(point, f->origin); + VectorCopy( color, f->color ); + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + VectorScale( f->color, d, f->color ); + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + fog_t *fog = NULL; + + if ( !r_flares->integer ) { + return; + } + + l = backEnd.refdef.dlights; + + if(tr.world) + fog = tr.world->fogs; + + for (i=0 ; inumfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + } + else + j = 0; + + RB_AddFlare( (void *)l, j, l->origin, l->color, NULL ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { + float depth; + bool visible; + float fade; + float screenZ; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = false; + + // read back the z buffer contents + qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -f->eyeZ - -screenZ ) < 24; + + if ( visible ) { + if ( !f->visible ) { + f->visible = true; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = false; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + float distance, intensity, factor; + byte fogFactors[3] = {255, 255, 255}; + + backEnd.pc.c_flareRenders++; + + // We don't want too big values anyways when dividing by distance. + if(f->eyeZ > -1.0f) + distance = 1.0f; + else + distance = -f->eyeZ; + + // calculate the flare size.. + size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance ); + +/* + * This is an alternative to intensity scaling. It changes the size of the flare on screen instead + * with growing distance. See in the description at the top why this is not the way to go. + // size will change ~ 1/r. + size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f)); +*/ + +/* + * As flare sizes stay nearly constant with increasing distance we must decrease the intensity + * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be + * got by considering the ratio of + * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare) + * An important requirement is: + * intensity <= 1 for all distances. + * + * The formula used here to compute the intensity is as follows: + * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2 + * As you can see, the intensity will have a max. of 1 when the distance is 0. + * The coefficient flareCoeff will determine the falloff speed with increasing distance. + */ + + factor = distance + size * sqrt(flareCoeff); + + intensity = flareCoeff * size * size / (factor * factor); + + VectorScale(f->color, f->drawIntensity * intensity, color); + + // Calculations for fogging + if(tr.world && f->fogNum > 0 && f->fogNum < tr.world->numfogs) + { + tess.numVertexes = 1; + VectorCopy(f->origin, tess.xyz[0]); + tess.fogNum = f->fogNum; + + RB_CalcModulateColorsByFog(fogFactors); + + // We don't need to render the flare if colors are 0 anyways. + if(!(fogFactors[0] || fogFactors[1] || fogFactors[2])) + return; + } + + iColor[0] = color[0] * fogFactors[0]; + iColor[1] = color[1] * fogFactors[1]; + iColor[2] = color[2] * fogFactors[2]; + + RB_BeginSurface( tr.flareShader, f->fogNum ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares (void) { + flare_t *f; + flare_t **prev; + bool draw; + + if ( !r_flares->integer ) { + return; + } + + if(r_flareCoeff->modified) + { + R_SetFlareCoeff(); + r_flareCoeff->modified = false; + } + + // Reset currentEntity to world so that any previously referenced entities + // don't have influence on the rendering of these flares (i.e. RF_ renderer flags). + backEnd.currentEntity = &tr.worldEntity; + backEnd.orientation = backEnd.viewParms.world; + +// RB_AddDlightFlares(); + + // perform z buffer readback on each flare in this view + draw = false; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = true; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable (GL_CLIP_PLANE0); + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999 ); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); +} diff --git a/src/renderergl1/tr_image.cpp b/src/renderergl1/tr_image.cpp new file mode 100644 index 0000000..0863db7 --- /dev/null +++ b/src/renderergl1/tr_image.cpp @@ -0,0 +1,1680 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_image.c +#include "tr_local.h" + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 1024 +static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +struct textureMode_t { + const char *name; + int minimize, maximize; +}; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +=============== +GL_TextureMode +=============== +*/ +void GL_TextureMode( const char *string ) { + int i; + image_t *glt; + + for ( i=0 ; i< 6 ; i++ ) { + if ( !Q_stricmp( modes[i].name, string ) ) { + break; + } + } + + // hack to prevent trilinear from being set on voodoo, + // because their driver freaks... + if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) { + ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" ); + i = 3; + } + + + if ( i == 6 ) { + ri.Printf (PRINT_ALL, "bad filter name\n"); + return; + } + + gl_filter_min = modes[i].minimize; + gl_filter_max = modes[i].maximize; + + // change all the existing mipmap texture objects + for ( i = 0 ; i < tr.numImages ; i++ ) { + glt = tr.images[ i ]; + if ( glt->flags & IMGFLAG_MIPMAP ) { + GL_Bind (glt); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +int R_SumOfUsedImages( void ) { + int total; + int i; + + total = 0; + for ( i = 0; i < tr.numImages; i++ ) { + if ( tr.images[i]->frameUsed == tr.frameCount ) { + total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight; + } + } + + return total; +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i; + int estTotalSize = 0; + + ri.Printf(PRINT_ALL, "\n -w-- -h-- type -size- --name-------\n"); + + for ( i = 0 ; i < tr.numImages ; i++ ) + { + image_t *image = tr.images[i]; + const char *format = "???? "; + const char *sizeSuffix; + int estSize; + int displaySize; + + estSize = image->uploadHeight * image->uploadWidth; + + switch(image->internalFormat) + { + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + format = "sDXT1"; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + format = "sDXT5"; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + format = "sBPTC"; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: + format = "LATC "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + format = "DXT1 "; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + format = "DXT5 "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + format = "BPTC "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_RGB4_S3TC: + format = "S3TC "; + // same as DXT1? + estSize /= 2; + break; + case GL_RGBA4: + case GL_RGBA8: + case GL_RGBA: + format = "RGBA "; + // 4 bytes per pixel + estSize *= 4; + break; + case GL_LUMINANCE8: + case GL_LUMINANCE16: + case GL_LUMINANCE: + format = "L "; + // 1 byte per pixel? + break; + case GL_RGB5: + case GL_RGB8: + case GL_RGB: + format = "RGB "; + // 3 bytes per pixel? + estSize *= 3; + break; + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE16_ALPHA16: + case GL_LUMINANCE_ALPHA: + format = "LA "; + // 2 bytes per pixel? + estSize *= 2; + break; + case GL_SRGB_EXT: + case GL_SRGB8_EXT: + format = "sRGB "; + // 3 bytes per pixel? + estSize *= 3; + break; + case GL_SRGB_ALPHA_EXT: + case GL_SRGB8_ALPHA8_EXT: + format = "sRGBA"; + // 4 bytes per pixel? + estSize *= 4; + break; + case GL_SLUMINANCE_EXT: + case GL_SLUMINANCE8_EXT: + format = "sL "; + // 1 byte per pixel? + break; + case GL_SLUMINANCE_ALPHA_EXT: + case GL_SLUMINANCE8_ALPHA8_EXT: + format = "sLA "; + // 2 byte per pixel? + estSize *= 2; + break; + } + + // mipmap adds about 50% + if (image->flags & IMGFLAG_MIPMAP) + estSize += estSize / 2; + + sizeSuffix = "b "; + displaySize = estSize; + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "kb"; + } + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "Mb"; + } + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "Gb"; + } + + ri.Printf(PRINT_ALL, "%4i: %4ix%4i %s %4i%s %s\n", i, image->uploadWidth, image->uploadHeight, format, displaySize, sizeSuffix, image->imgName); + estTotalSize += estSize; + } + + ri.Printf (PRINT_ALL, " ---------\n"); + ri.Printf (PRINT_ALL, " approx %i bytes\n", estTotalSize); + ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages ); +} + +//======================================================================= + +/* +================ +ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only be filtered properly if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +before or after. +================ +*/ +static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out, + int outwidth, int outheight ) { + int i, j; + unsigned *inrow, *inrow2; + unsigned frac, fracstep; + unsigned p1[2048], p2[2048]; + byte *pix1, *pix2, *pix3, *pix4; + + if (outwidth>2048) + ri.Error(ERR_DROP, "ResampleTexture: max width"); + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for ( i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for ( i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i>2; + ((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + ((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + ((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } +} + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, bool only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth*inheight; + for (i=0 ; i> 1; + outHeight = inHeight >> 1; + temp = (unsigned*)ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = (byte *) ( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k]; + outpix[k] = total / 36; + } + } + } + + Com_Memcpy( in, temp, outWidth * outHeight * 4 ); + ri.Hunk_FreeTempMemory( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap (byte *in, int width, int height) { + int i, j; + byte *out; + int row; + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( (unsigned *)in, width, height ); + return; + } + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out[1] = ( in[1] + in[5] )>>1; + out[2] = ( in[2] + in[6] )>>1; + out[3] = ( in[3] + in[7] )>>1; + } + return; + } + + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2; + out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2; + } + } +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + + +/* +=============== +Upload32 + +=============== +*/ +static void Upload32( unsigned *data, + int width, int height, + bool mipmap, + bool picmip, + bool lightMap, + bool allowCompression, + int *format, + int *pUploadWidth, int *pUploadHeight ) +{ + int samples; + unsigned *scaledBuffer = NULL; + unsigned *resampledBuffer = NULL; + int scaled_width, scaled_height; + int i, c; + byte *scan; + GLenum internalFormat = GL_RGB; + float rMax = 0, gMax = 0, bMax = 0; + + // + // convert to exact power of 2 sizes + // + for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) + ; + if ( r_roundImagesDown->integer && scaled_width > width ) + scaled_width >>= 1; + if ( r_roundImagesDown->integer && scaled_height > height ) + scaled_height >>= 1; + + if ( scaled_width != width || scaled_height != height ) { + resampledBuffer = (unsigned*)ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); + ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height); + data = resampledBuffer; + width = scaled_width; + height = scaled_height; + } + + // + // perform optional picmip operation + // + if ( picmip ) { + scaled_width >>= r_picmip->integer; + scaled_height >>= r_picmip->integer; + } + + // + // clamp to minimum size + // + if (scaled_width < 1) { + scaled_width = 1; + } + if (scaled_height < 1) { + scaled_height = 1; + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( scaled_width > glConfig.maxTextureSize + || scaled_height > glConfig.maxTextureSize ) { + scaled_width >>= 1; + scaled_height >>= 1; + } + + scaledBuffer = (unsigned*)ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = ((byte *)data); + samples = 3; + + if( r_greyscale->integer ) + { + for ( i = 0; i < c; i++ ) + { + byte luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = luma; + scan[i*4 + 1] = luma; + scan[i*4 + 2] = luma; + } + } + else if( r_greyscale->value ) + { + for ( i = 0; i < c; i++ ) + { + float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); + scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); + scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); + } + } + + if(lightMap) + { + if(r_greyscale->integer) + internalFormat = GL_LUMINANCE; + else + internalFormat = GL_RGB; + } + else + { + for ( i = 0; i < c; i++ ) + { + if ( scan[i*4+0] > rMax ) + { + rMax = scan[i*4+0]; + } + if ( scan[i*4+1] > gMax ) + { + gMax = scan[i*4+1]; + } + if ( scan[i*4+2] > bMax ) + { + bMax = scan[i*4+2]; + } + if ( scan[i*4 + 3] != 255 ) + { + samples = 4; + break; + } + } + // select proper internal format + if ( samples == 3 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16; + else + internalFormat = GL_LUMINANCE; + } + else + { + if ( allowCompression && glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } + else if ( allowCompression && glConfig.textureCompression == TC_S3TC ) + { + internalFormat = GL_RGB4_S3TC; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGB8; + } + else + { + internalFormat = GL_RGB; + } + } + } + else if ( samples == 4 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8_ALPHA8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16_ALPHA16; + else + internalFormat = GL_LUMINANCE_ALPHA; + } + else + { + if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGBA8; + } + else + { + internalFormat = GL_RGBA; + } + } + } + } + + // copy or resample data as appropriate for first MIP level + if ( ( scaled_width == width ) && + ( scaled_height == height ) ) { + if (!mipmap) + { + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + + goto done; + } + Com_Memcpy (scaledBuffer, data, width*height*4); + } + else + { + // use the normal mip-mapping function to go down from here + while ( width > scaled_width || height > scaled_height ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + } + Com_Memcpy( scaledBuffer, data, width * height * 4 ); + } + + R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !mipmap ); + + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + + if (mipmap) + { + int miplevel; + + miplevel = 0; + while (scaled_width > 1 || scaled_height > 1) + { + R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height ); + scaled_width >>= 1; + scaled_height >>= 1; + if (scaled_width < 1) + scaled_width = 1; + if (scaled_height < 1) + scaled_height = 1; + miplevel++; + + if ( r_colorMipLevels->integer ) { + R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] ); + } + + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } + } +done: + + if (mipmap) + { + if ( glConfig.textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, + (GLint)Com_Clamp( 1, glConfig.maxAnisotropy, r_ext_max_anisotropy->integer ) ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + else + { + if ( glConfig.textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); + + if ( scaledBuffer != 0 ) + ri.Hunk_FreeTempMemory( scaledBuffer ); + if ( resampledBuffer != 0 ) + ri.Hunk_FreeTempMemory( resampledBuffer ); +} + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, byte *pic, int width, int height, + imgType_t type, int flags, int internalFormat ) { + image_t *image; + bool isLightmap = false; + long hash; + int glWrapClampMode; + + if (strlen(name) >= MAX_QPATH ) { + ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); + } + if ( !strncmp( name, "*lightmap", 9 ) ) { + isLightmap = true; + } + + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit"); + } + + image = tr.images[tr.numImages] = (image_t*)ri.Hunk_Alloc( sizeof( image_t ), h_low ); + image->texnum = 1024 + tr.numImages; + tr.numImages++; + + image->type = type; + image->flags = flags; + + strcpy (image->imgName, name); + + image->width = width; + image->height = height; + if (flags & IMGFLAG_CLAMPTOEDGE) + glWrapClampMode = GL_CLAMP_TO_EDGE; + else + glWrapClampMode = GL_REPEAT; + + // lightmaps are always allocated on TMU 1 + if ( qglActiveTextureARB && isLightmap ) { + image->TMU = 1; + } else { + image->TMU = 0; + } + + if ( qglActiveTextureARB ) { + GL_SelectTexture( image->TMU ); + } + + GL_Bind(image); + + Upload32( (unsigned *)pic, image->width, image->height, + (image->flags & IMGFLAG_MIPMAP) == IMGFLAG_MIPMAP, + (image->flags & IMGFLAG_PICMIP) == IMGFLAG_PICMIP, + isLightmap, + !(image->flags & IMGFLAG_NO_COMPRESSION), + &image->internalFormat, + &image->uploadWidth, + &image->uploadHeight ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); + + glState.currenttextures[glState.currenttmu] = 0; + qglBindTexture( GL_TEXTURE_2D, 0 ); + + if ( image->TMU == 1 ) { + GL_SelectTexture( 0 ); + } + + hash = generateHashValue(name); + image->next = hashTable[hash]; + hashTable[hash] = image; + + return image; +} + +//=================================================================== + +struct imageExtToLoaderMap_t +{ + const char *ext; + void (*ImageLoader)( const char *, unsigned char **, int *, int * ); +}; + +// Note that the ordering indicates the order of preference used +// when there are multiple images of different formats available +static imageExtToLoaderMap_t imageLoaders[ ] = +{ + { "tga", R_LoadTGA }, + { "jpg", R_LoadJPG }, + { "jpeg", R_LoadJPG }, + { "png", R_LoadPNG }, + { "pcx", R_LoadPCX }, + { "bmp", R_LoadBMP } +}; + +static int numImageLoaders = ARRAY_LEN( imageLoaders ); + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *name, byte **pic, int *width, int *height ) +{ + bool orgNameFailed = false; + int orgLoader = -1; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + const char *altName; + + *pic = NULL; + *width = 0; + *height = 0; + + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numImageLoaders; i++ ) + { + if( !Q_stricmp( ext, imageLoaders[ i ].ext ) ) + { + // Load + imageLoaders[ i ].ImageLoader( localName, pic, width, height ); + break; + } + } + + // A loader was found + if( i < numImageLoaders ) + { + if( *pic == NULL ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = true; + orgLoader = i; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return; + } + } + } + + // Try and find a suitable match using all + // the image formats supported + for( i = 0; i < numImageLoaders; i++ ) + { + if (i == orgLoader) + continue; + + altName = va( "%s.%s", localName, imageLoaders[ i ].ext ); + + // Load + imageLoaders[ i ].ImageLoader( altName, pic, width, height ); + + if( *pic ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } +} + + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, imgType_t type, int /*imgFlags_t*/ flags ) +{ + image_t *image; + int width, height; + byte *pic; + long hash; + + if (!name) { + return NULL; + } + + hash = generateHashValue(name); + + // + // see if the image is already loaded + // + for (image=hashTable[hash]; image; image=image->next) { + if ( !strcmp( name, image->imgName ) ) { + // the white image can be used with any set of parms, but other mismatches are errors + if ( strcmp( name, "*white" ) ) { + if ( image->flags != flags ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed flags (%i vs %i)\n", name, image->flags, flags ); + } + } + return image; + } + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height ); + if ( pic == NULL ) { + return NULL; + } + + image = R_CreateImage( ( char * ) name, pic, width, height, type, flags, 0 ); + ri.Free( pic ); + return image; +} + + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) { + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 ); +} + + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0f/32.0f) / (30.0f/32.0f); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float d; + + data = (byte*)ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); + + // S is distance, T is depth + for (x=0 ; xinteger; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( !glConfig.isFullscreen ) + { + tr.overbrightBits = 0; + } + + // allow 2 overbright bits in 24 bit, but only 1 in 16 bit + if ( glConfig.colorBits > 16 ) { + if ( tr.overbrightBits > 2 ) { + tr.overbrightBits = 2; + } + } else { + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + } + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value <= 1 ) { + ri.Cvar_Set( "r_intensity", "1" ); + } + + if ( r_gamma->value < 0.5f ) { + ri.Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + ri.Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } + + if ( glConfig.deviceSupportsGamma ) + { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + Com_Memset(hashTable, 0, sizeof(hashTable)); + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +void R_DeleteTextures( void ) { + int i; + + for ( i=0; itexnum ); + } + Com_Memset( tr.images, 0, sizeof( tr.images ) ); + + tr.numImages = 0; + + Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } else { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static const char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + data += 2; + while (*data && *data != '\n') { + data++; + } + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + data += 2; + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if (len < MAX_TOKEN_CHARS - 1) + { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if (len < MAX_TOKEN_CHARS - 1) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32 && c != ',' ); + + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + + +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name ) { + skinSurface_t parseSurfaces[MAX_SKIN_SURFACES]; + qhandle_t hSkin; + skin_t *skin; + skinSurface_t *surf; + union { + char *c; + void *v; + } text; + char *text_p; + const char *token; + char surfName[MAX_QPATH]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_DEVELOPER, "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return hSkin; + } + } + + // allocate a new skin + if ( tr.numSkins == MAX_SKINS ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + tr.numSkins++; + skin = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + + R_IssuePendingRenderCommands(); + + // If not a .skin file, load as a single shader + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { + skin->numSurfaces = 1; + skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = R_FindShader( name, LIGHTMAP_NONE, true ); + return hSkin; + } + + // load and parse the skin file + ri.FS_ReadFile( name, &text.v ); + if ( !text.c ) { + return 0; + } + + int totalSurfaces = 0; + text_p = text.c; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( strstr( token, "tag_" ) ) { + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + + if ( skin->numSurfaces < MAX_SKIN_SURFACES ) { + surf = &parseSurfaces[skin->numSurfaces]; + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, true ); + skin->numSurfaces++; + } + + totalSurfaces++; + } + + ri.FS_FreeFile( text.v ); + + if ( totalSurfaces > MAX_SKIN_SURFACES ) { + ri.Printf( PRINT_WARNING, "WARNING: Ignoring excess surfaces (found %d, max is %d) in skin '%s'!\n", + totalSurfaces, MAX_SKIN_SURFACES, name ); + } + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + // copy surfaces to skin + skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( skin->numSurfaces * sizeof( skinSurface_t ), h_low ); + memcpy( skin->surfaces, parseSurfaces, skin->numSurfaces * sizeof( skinSurface_t ) ); + + return hSkin; +} + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f( void ) { + int i, j; + skin_t *skin; + + ri.Printf (PRINT_ALL, "------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + + ri.Printf( PRINT_ALL, "%3i:%s (%d surfaces)\n", i, skin->name, skin->numSurfaces ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + ri.Printf( PRINT_ALL, " %s = %s\n", + skin->surfaces[j].name, skin->surfaces[j].shader->name ); + } + } + ri.Printf (PRINT_ALL, "------------------\n"); +} diff --git a/src/renderergl1/tr_init.cpp b/src/renderergl1/tr_init.cpp new file mode 100644 index 0000000..cbd5095 --- /dev/null +++ b/src/renderergl1/tr_init.cpp @@ -0,0 +1,1299 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_init.c -- functions that are not called every frame + +#include "tr_local.h" + +glconfig_t glConfig; +glstate_t glState; + +static void GfxInfo_f( void ); + +#ifdef USE_RENDERER_DLOPEN +cvar_t *com_altivec; +#endif + +cvar_t *r_flareSize; +cvar_t *r_flareFade; +cvar_t *r_flareCoeff; + +cvar_t *r_railWidth; +cvar_t *r_railCoreWidth; +cvar_t *r_railSegmentLength; + +cvar_t *r_ignoreFastPath; + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_displayRefresh; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; +cvar_t *r_zproj; +cvar_t *r_stereoSeparation; + +cvar_t *r_skipBackEnd; + +cvar_t *r_stereoEnabled; +cvar_t *r_anaglyphMode; + +cvar_t *r_greyscale; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_inGameVideo; +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; +cvar_t *r_ext_texture_filter_anisotropic; +cvar_t *r_ext_max_anisotropy; + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_alphabits; +cvar_t *r_primitives; +cvar_t *r_texturebits; +cvar_t *r_ext_multisample; + +cvar_t *r_drawBuffer; +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_uiFullScreen; +cvar_t *r_shadows; +cvar_t *r_flares; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_roundImagesDown; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen; +cvar_t *r_noborder; + +cvar_t *r_width; +cvar_t *r_height; +cvar_t *r_pixelAspect; + +cvar_t *r_overBrightBits; +cvar_t *r_mapOverBrightBits; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; +cvar_t *r_printShaders; +cvar_t *r_saveFontData; + +cvar_t *r_marksOnTriangleMeshes; + +cvar_t *r_aviMotionJpegQuality; +cvar_t *r_screenshotJpegQuality; + +cvar_t *r_maxpolys; +int max_polys; +cvar_t *r_maxpolyverts; +int max_polyverts; + +#define GENERIC_HW_R_PICMIP_DEFAULT "0" +#define GENERIC_HW_R_TEXTUREMODE_DEFAULT "GL_LINEAR_MIPMAP_LINEAR" + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) +{ + char renderer_buffer[1024]; + + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_(width|height|pixelAspect) + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) + { + GLint temp; + + GLimp_Init( qfalse ); + + strcpy( renderer_buffer, glConfig.renderer_string ); + Q_strlwr( renderer_buffer ); + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) + { + glConfig.maxTextureSize = 0; + } + } + + // set default state + GL_SetDefaultState(); +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors( void ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof(s), "%i", err); + break; + } + + ri.Error( ERR_FATAL, "GL_CheckErrors: %s", s ); +} + + +/* +============================================================================== + + SCREEN SHOTS + +NOTE TTimo +some thoughts about the screenshots system: +screenshots get written in fs_homepath + fs_gamedir +vanilla q3 .. baseq3/screenshots/ *.tga +team arena .. missionpack/screenshots/ *.tga + +two commands: "screenshot" and "screenshotJPEG" +we use statics to store a count and start writing the first screenshot/screenshot????.tga (.jpg) available +(with FS_FileExists / FS_FOpenFileWrite calls) +FIXME: the statics don't get a reinit between fs_game changes + +============================================================================== +*/ + +/* +================== +RB_ReadPixels + +Reads an image but takes care of alignment issues for reading RGB images. + +Reads a minimum offset for where the RGB data starts in the image from +integer stored at pointer offset. When the function has returned the actual +offset was written back to address offset. This address will always have an +alignment of packAlign to ensure efficient copying. + +Stores the length of padding after a line of pixels to address padlen + +Return value must be freed with ri.Hunk_FreeTempMemory() +================== +*/ + +byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen) +{ + byte *buffer, *bufstart; + int padwidth, linelen; + GLint packAlign; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = width * 3; + padwidth = PAD(linelen, packAlign); + + // Allocate a few more bytes so that we can choose an alignment we like + buffer = (byte*)ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1); + + bufstart = (byte*)PADP((intptr_t) buffer + *offset, packAlign); + qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart); + + *offset = bufstart - buffer; + *padlen = padwidth - linelen; + + return buffer; +} + +/* +================== +RB_TakeScreenshot +================== +*/ +void RB_TakeScreenshot(int x, int y, int width, int height, char *fileName) +{ + byte *allbuf, *buffer; + byte *srcptr, *destptr; + byte *endline, *endmem; + byte temp; + + int linelen, padlen; + size_t offset = 18, memcount; + + allbuf = RB_ReadPixels(x, y, width, height, &offset, &padlen); + buffer = allbuf + offset - 18; + + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + // swap rgb to bgr and remove padding from line endings + linelen = width * 3; + + srcptr = destptr = allbuf + offset; + endmem = srcptr + (linelen + padlen) * height; + + while(srcptr < endmem) + { + endline = srcptr + linelen; + + while(srcptr < endline) + { + temp = srcptr[0]; + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = temp; + + srcptr += 3; + } + + // Skip the pad + srcptr += padlen; + } + + memcount = linelen * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(allbuf + offset, memcount); + + ri.FS_WriteFile(fileName, buffer, memcount + 18); + + ri.Hunk_FreeTempMemory(allbuf); +} + +/* +================== +RB_TakeScreenshotJPEG +================== +*/ + +void RB_TakeScreenshotJPEG(int x, int y, int width, int height, char *fileName) +{ + byte *buffer; + size_t offset = 0, memcount; + int padlen; + + buffer = RB_ReadPixels(x, y, width, height, &offset, &padlen); + memcount = (width * 3 + padlen) * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(buffer + offset, memcount); + + RE_SaveJPG(fileName, r_screenshotJpegQuality->integer, width, height, buffer + offset, padlen); + ri.Hunk_FreeTempMemory(buffer); +} + +/* +================== +RB_TakeScreenshotCmd +================== +*/ +const void *RB_TakeScreenshotCmd( const void *data ) { + const screenshotCommand_t *cmd; + + cmd = (const screenshotCommand_t *)data; + + if (cmd->jpeg) + RB_TakeScreenshotJPEG( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + else + RB_TakeScreenshot( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + + return (const void *)(cmd + 1); +} + +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshot( int x, int y, int width, int height, char *name, bool jpeg ) { + static char fileName[MAX_OSPATH]; // bad things if two screenshots per frame? + screenshotCommand_t *cmd; + + cmd = (screenshotCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SCREENSHOT; + + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + Q_strncpyz( fileName, name, sizeof(fileName) ); + cmd->fileName = fileName; + cmd->jpeg = jpeg; +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga" + , a, b, c, d ); +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg" + , a, b, c, d ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 128*128 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +void R_LevelShot( void ) { + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source, *allsource; + byte *src, *dst; + size_t offset = 0; + int padlen; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + Com_sprintf(checkname, sizeof(checkname), "levelshots/%s.tga", tr.world->baseName); + + allsource = RB_ReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, &offset, &padlen); + source = allsource + offset; + + buffer = (byte*)ri.Hunk_AllocateTempMemory(128 * 128*3 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = 128; + buffer[14] = 128; + buffer[16] = 24; // pixel size + + // resample from source + xScale = glConfig.vidWidth / 512.0f; + yScale = glConfig.vidHeight / 384.0f; + for ( y = 0 ; y < 128 ; y++ ) { + for ( x = 0 ; x < 128 ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + (3 * glConfig.vidWidth + padlen) * (int)((y*3 + yy) * yScale) + + 3 * (int) ((x*4 + xx) * xScale); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * 128 + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, 128 * 128 * 3 ); + } + + ri.FS_WriteFile( checkname, buffer, 128 * 128*3 + 18 ); + + ri.Hunk_FreeTempMemory(buffer); + ri.Hunk_FreeTempMemory(allsource); + + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); +} + +/* +================== +R_ScreenShot_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShot_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + bool silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = true; + } else { + silent = false; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber >= 9999 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, false ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +void R_ScreenShotJPEG_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + bool silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = true; + } else { + silent = false; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilenameJPEG( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber == 10000 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, true ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +//============================================================================ + +/* +================== +RB_TakeVideoFrameCmd +================== +*/ +const void *RB_TakeVideoFrameCmd( const void *data ) +{ + const videoFrameCommand_t *cmd; + byte *cBuf; + size_t memcount, linelen; + int padwidth, avipadwidth, padlen, avipadlen; + GLint packAlign; + + cmd = (const videoFrameCommand_t *)data; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = cmd->width * 3; + + // Alignment stuff for glReadPixels + padwidth = PAD(linelen, packAlign); + padlen = padwidth - linelen; + // AVI line padding + avipadwidth = PAD(linelen, AVI_LINE_PADDING); + avipadlen = avipadwidth - linelen; + + cBuf = (byte*)PADP(cmd->captureBuffer, packAlign); + + qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB, + GL_UNSIGNED_BYTE, cBuf); + + memcount = padwidth * cmd->height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(cBuf, memcount); + + if(cmd->motionJpeg) + { + memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, + r_aviMotionJpegQuality->integer, + cmd->width, cmd->height, cBuf, padlen); + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); + } + else + { + byte *lineend, *memend; + byte *srcptr, *destptr; + + srcptr = cBuf; + destptr = cmd->encodeBuffer; + memend = srcptr + memcount; + + // swap R and B and remove line paddings + while(srcptr < memend) + { + lineend = srcptr + linelen; + while(srcptr < lineend) + { + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = srcptr[0]; + srcptr += 3; + } + + Com_Memset(destptr, '\0', avipadlen); + destptr += avipadlen; + + srcptr += padlen; + } + + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, avipadwidth * cmd->height); + } + + return (const void *)(cmd + 1); +} + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) +{ + qglClearDepth( 1.0f ); + + qglCullFace(GL_FRONT); + + qglColor4f (1,1,1,1); + + // initialize downstream texture unit if we're running + // in a multitexture environment + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( 0 ); + } + + qglEnable(GL_TEXTURE_2D); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + + qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // the vertex array is always enabled, but the color and texture + // arrays are enabled and disabled around the compiled vertex array call + qglEnableClientState (GL_VERTEX_ARRAY); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); +} + +/* +================ +R_PrintLongString + +Workaround for ri.Printf's 1024 characters buffer limit. +================ +*/ +void R_PrintLongString(const char *string) { + char buffer[1024]; + const char *p; + int size = strlen(string); + + p = string; + while(size > 0) + { + Q_strncpyz(buffer, p, sizeof (buffer) ); + ri.Printf( PRINT_ALL, "%s", buffer ); + p += 1023; + size -= 1023; + } +} + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) +{ + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); + ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); + ri.Printf( PRINT_ALL, "GL_EXTENSIONS: " ); + R_PrintLongString( glConfig.extensions_string ); + ri.Printf( PRINT_ALL, "\n" ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits ); + ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + ri.Printf( PRINT_ALL, "MODE: %d x %d %s hz:", glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) + { + ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency ); + } + else + { + ri.Printf( PRINT_ALL, "N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) + { + ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } + else + { + ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + + // rendering primitives + { + int primitives; + + // default is to use triangles if compiled vertex arrays are present + ri.Printf( PRINT_ALL, "rendering primitives: " ); + primitives = r_primitives->integer; + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + if ( primitives == -1 ) { + ri.Printf( PRINT_ALL, "none\n" ); + } else if ( primitives == 2 ) { + ri.Printf( PRINT_ALL, "single glDrawElements\n" ); + } else if ( primitives == 1 ) { + ri.Printf( PRINT_ALL, "multiple glArrayElement\n" ); + } else if ( primitives == 3 ) { + ri.Printf( PRINT_ALL, "multiple glColor4ubv + glTexCoord2fv + glVertex3fv\n" ); + } + } + + ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); + ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); + ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); + ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); + ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] ); + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) + { + ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" ); + } + if ( glConfig.hardwareType == GLHW_RAGEPRO ) + { + ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" ); + } + if ( glConfig.hardwareType == GLHW_RIVA128 ) + { + ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" ); + } + if ( r_finish->integer ) { + ri.Printf( PRINT_ALL, "Forcing glFinish\n" ); + } +} + +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) +{ + #ifdef USE_RENDERER_DLOPEN + com_altivec = ri.Cvar_Get("com_altivec", "1", CVAR_ARCHIVE); + #endif + + // + // latched and archived variables + // + r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); + + r_picmip = ri.Cvar_Get ("r_picmip", GENERIC_HW_R_PICMIP_DEFAULT, + CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic", + "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_max_anisotropy = ri.Cvar_Get( "r_ext_max_anisotropy", "2", CVAR_ARCHIVE | CVAR_LATCH ); + + r_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); + ri.Cvar_CheckRange( r_picmip, 0, 16, true ); + r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_alphabits = ri.Cvar_Get( "r_alphabits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_stencilbits = ri.Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH ); + r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH ); + ri.Cvar_CheckRange( r_ext_multisample, 0, 4, true ); + r_overBrightBits = ri.Cvar_Get ("r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE ); + r_noborder = ri.Cvar_Get("r_noborder", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_width = ri.Cvar_Get( "r_width", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_height = ri.Cvar_Get( "r_height", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_pixelAspect = ri.Cvar_Get( "r_pixelAspect", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0); + r_subdivisions = ri.Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH); + r_stereoEnabled = ri.Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_ignoreFastPath = ri.Cvar_Get( "r_ignoreFastPath", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_greyscale = ri.Cvar_Get("r_greyscale", "0", CVAR_ARCHIVE | CVAR_LATCH); + ri.Cvar_CheckRange(r_greyscale, 0, 1, false); + + // + // temporary latched variables that can only change over a restart + // + r_displayRefresh = ri.Cvar_Get( "r_displayRefresh", "0", CVAR_LATCH ); + ri.Cvar_CheckRange( r_displayRefresh, 0, 200, true ); + r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT ); + r_mapOverBrightBits = ri.Cvar_Get ("r_mapOverBrightBits", "2", CVAR_LATCH ); + r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_LATCH ); + r_singleShader = ri.Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE|CVAR_CHEAT ); + r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_flares = ri.Cvar_Get ("r_flares", "0", CVAR_ARCHIVE ); + r_znear = ri.Cvar_Get( "r_znear", "1", CVAR_CHEAT ); + ri.Cvar_CheckRange( r_znear, 0.001f, 200, false ); + r_zproj = ri.Cvar_Get( "r_zproj", "64", CVAR_ARCHIVE ); + r_stereoSeparation = ri.Cvar_Get( "r_stereoSeparation", "64", CVAR_ARCHIVE ); + r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + r_drawSun = ri.Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE ); + r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); + r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE ); + r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE); + r_textureMode = ri.Cvar_Get( "r_textureMode", + GENERIC_HW_R_TEXTUREMODE_DEFAULT, CVAR_ARCHIVE ); + r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", + CVAR_ARCHIVE | CVAR_LATCH ); + r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE ); + r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE ); + r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "6", CVAR_ARCHIVE ); + r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE ); + + r_primitives = ri.Cvar_Get( "r_primitives", "0", CVAR_ARCHIVE ); + + r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT ); + r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE); + + // + // temporary variables that can change at any time + // + r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_CHEAT|CVAR_TEMP ); + + r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 ); + r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 ); + + r_nocurves = ri.Cvar_Get ("r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = ri.Cvar_Get ("r_drawworld", "1", CVAR_CHEAT ); + r_lightmap = ri.Cvar_Get ("r_lightmap", "0", CVAR_CHEAT ); + r_portalOnly = ri.Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT ); + + r_flareSize = ri.Cvar_Get ("r_flareSize", "40", CVAR_CHEAT); + r_flareFade = ri.Cvar_Get ("r_flareFade", "7", CVAR_CHEAT); + r_flareCoeff = ri.Cvar_Get ("r_flareCoeff", FLARE_STDCOEFF, CVAR_CHEAT); + + r_skipBackEnd = ri.Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT); + + r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT ); + r_norefresh = ri.Cvar_Get ("r_norefresh", "0", CVAR_CHEAT); + r_drawentities = ri.Cvar_Get ("r_drawentities", "1", CVAR_CHEAT ); + r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT ); + r_nocull = ri.Cvar_Get ("r_nocull", "0", CVAR_CHEAT); + r_novis = ri.Cvar_Get ("r_novis", "0", CVAR_CHEAT); + r_showcluster = ri.Cvar_Get ("r_showcluster", "0", CVAR_CHEAT); + r_speeds = ri.Cvar_Get ("r_speeds", "0", CVAR_CHEAT); + r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = ri.Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT); + r_nobind = ri.Cvar_Get ("r_nobind", "0", CVAR_CHEAT); + r_showtris = ri.Cvar_Get ("r_showtris", "0", CVAR_CHEAT); + r_showsky = ri.Cvar_Get ("r_showsky", "0", CVAR_CHEAT); + r_shownormals = ri.Cvar_Get ("r_shownormals", "0", CVAR_CHEAT); + r_clear = ri.Cvar_Get ("r_clear", "0", CVAR_CHEAT); + r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); + r_lockpvs = ri.Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT); + r_noportals = ri.Cvar_Get ("r_noportals", "0", CVAR_CHEAT); + r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 ); + + r_marksOnTriangleMeshes = ri.Cvar_Get("r_marksOnTriangleMeshes", "0", CVAR_ARCHIVE); + + r_aviMotionJpegQuality = ri.Cvar_Get("r_aviMotionJpegQuality", "90", CVAR_ARCHIVE); + r_screenshotJpegQuality = ri.Cvar_Get("r_screenshotJpegQuality", "90", CVAR_ARCHIVE); + + r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); + r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + + // make sure all the commands added here are also + // removed in R_Shutdown + ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); + ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + ri.Cmd_AddCommand( "skinlist", R_SkinList_f ); + ri.Cmd_AddCommand( "modellist", R_Modellist_f ); + ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f ); + ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f ); + ri.Cmd_AddCommand( "minimize", GLimp_Minimize ); +} + +/* +=============== +R_Init +=============== +*/ +void R_Init( void ) { + int err; + int i; + byte *ptr; + + ri.Printf( PRINT_ALL, "----- R_Init -----\n" ); + + // clear all our internal state + Com_Memset( &tr, 0, sizeof( tr ) ); + Com_Memset( &backEnd, 0, sizeof( backEnd ) ); + Com_Memset( &tess, 0, sizeof( tess ) ); + +// Swap_Init(); + + if ( (intptr_t)tess.xyz & 15 ) { + ri.Printf( PRINT_WARNING, "tess.xyz not 16 byte aligned\n" ); + } + Com_Memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) ); + + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) + { + if ( i < FUNCTABLE_SIZE / 4 ) + { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } + else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; + } + } + else + { + tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; + } + } + + R_InitFogTable(); + + R_NoiseInit(); + + R_Register(); + + max_polys = r_maxpolys->integer; + if (max_polys < MAX_POLYS) + max_polys = MAX_POLYS; + + max_polyverts = r_maxpolyverts->integer; + if (max_polyverts < MAX_POLYVERTS) + max_polyverts = MAX_POLYVERTS; + + ptr = (byte*)ri.Hunk_Alloc( sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData = (backEndData_t *) ptr; + backEndData->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData )); + backEndData->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys); + R_InitNextFrame(); + + InitOpenGL(); + + R_InitImages(); + + R_InitShaders(); + + R_InitSkins(); + + R_ModelInit(); + + R_InitFreeType(); + + + err = qglGetError(); + if ( err != GL_NO_ERROR ) + ri.Printf (PRINT_ALL, "glGetError() = 0x%x\n", err); + + // print info + GfxInfo_f(); + ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" ); +} + +/* +=============== +RE_Shutdown +=============== +*/ +void RE_Shutdown( bool destroyWindow ) { + + ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); + + ri.Cmd_RemoveCommand("gfxinfo"); + ri.Cmd_RemoveCommand("imagelist"); + ri.Cmd_RemoveCommand("modellist"); + ri.Cmd_RemoveCommand("screenshot"); + ri.Cmd_RemoveCommand("screenshotJPEG"); + ri.Cmd_RemoveCommand("shaderlist"); + ri.Cmd_RemoveCommand("skinlist"); + ri.Cmd_RemoveCommand("minimize"); + + if ( tr.registered ) { + R_IssuePendingRenderCommands(); + R_DeleteTextures(); + } + + R_DoneFreeType(); + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + + Com_Memset( &glConfig, 0, sizeof( glConfig ) ); + Com_Memset( &glState, 0, sizeof( glState ) ); + } + + tr.registered = false; +} + + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +void RE_EndRegistration( void ) { + R_IssuePendingRenderCommands(); + if (!ri.Sys_LowPhysicalMemory()) { + RB_ShowImages(); + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +#ifdef USE_RENDERER_DLOPEN +extern "C" Q_EXPORT refexport_t* QDECL GetRefAPI ( int apiVersion, refimport_t *rimp ) { +#else +refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) { +#endif + + static refexport_t re; + + ri = *rimp; + + Com_Memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + ri.Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; + + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.ClearScene = RE_ClearScene; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.AddPolyToScene = RE_AddPolyToScene; + re.LightForPoint = R_LightForPoint; + re.AddLightToScene = RE_AddLightToScene; + re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene; + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.SetClipRegion = RE_SetClipRegion; + re.DrawStretchPic = RE_StretchPic; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + + re.RegisterFont = RE_RegisterFont; + re.RemapShader = R_RemapShader; + re.GetEntityToken = R_GetEntityToken; + re.inPVS = R_inPVS; + + re.TakeVideoFrame = RE_TakeVideoFrame; + + return &re; +} diff --git a/src/renderergl1/tr_light.cpp b/src/renderergl1/tr_light.cpp new file mode 100644 index 0000000..5cf13a8 --- /dev/null +++ b/src/renderergl1/tr_light.cpp @@ -0,0 +1,402 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_light.c + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *orientation) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, orientation->origin, temp ); + dl->transformed[0] = DotProduct( temp, orientation->axis[0] ); + dl->transformed[1] = DotProduct( temp, orientation->axis[1] ); + dl->transformed[2] = DotProduct( temp, orientation->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void R_DlightBmodel( bmodel_t *bmodel ) { + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.orientation ); + + mask = 0; + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + + tr.currentEntity->needDlights = (mask != 0); + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits = mask; + } + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +static void R_SetupEntityLightingGrid( trRefEntity_t *ent ) { + vec3_t lightOrigin; + int pos[3]; + int i, j; + byte *gridData; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + VectorSubtract( lightOrigin, tr.world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i]*tr.world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] > tr.world->lightGridBounds[i] - 1 ) { + pos[i] = tr.world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + assert( tr.world->lightGridData ); // NULL with -nolight maps + + // trilerp the light value + gridStep[0] = 8; + gridStep[1] = 8 * tr.world->lightGridBounds[0]; + gridStep[2] = 8 * tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1]; + gridData = tr.world->lightGridData + pos[0] * gridStep[0] + + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + byte *data; + int lat, lng; + vec3_t normal; + #if idppc + float d0, d1, d2, d3, d4, d5; + #endif + factor = 1.0; + data = gridData; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & (1< tr.world->lightGridBounds[j] - 1 ) { + break; // ignore values outside lightgrid + } + factor *= frac[j]; + data += gridStep[j]; + } else { + factor *= (1.0f - frac[j]); + } + } + + if ( j != 3 ) { + continue; + } + if ( !(data[0]+data[1]+data[2]) ) { + continue; // ignore samples in walls + } + totalFactor += factor; + #if idppc + d0 = data[0]; d1 = data[1]; d2 = data[2]; + d3 = data[3]; d4 = data[4]; d5 = data[5]; + + ent->ambientLight[0] += factor * d0; + ent->ambientLight[1] += factor * d1; + ent->ambientLight[2] += factor * d2; + + ent->directedLight[0] += factor * d3; + ent->directedLight[1] += factor * d4; + ent->directedLight[2] += factor * d5; + #else + ent->ambientLight[0] += factor * data[0]; + ent->ambientLight[1] += factor * data[1]; + ent->ambientLight[2] += factor * data[2]; + + ent->directedLight[0] += factor * data[3]; + ent->directedLight[1] += factor * data[4]; + ent->directedLight[2] += factor * data[5]; + #endif + lat = data[7]; + lng = data[6]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) { + totalFactor = 1.0f / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + + ri.Printf( PRINT_ALL, "amb:%i dir:%i\n", max1, max2 ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = true; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + // bonus items and view weapons have a fixed minimum add + if ( 1 /* ent->e.renderfx & RF_MINLIGHT */ ) { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp ambient + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + } + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = static_cast(ent->ambientLight[0]); + ((byte *)&ent->ambientLightInt)[1] = static_cast(ent->ambientLight[1]); + ((byte *)&ent->ambientLightInt)[2] = static_cast(ent->ambientLight[2]); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); +} + +/* +================= +R_LightForPoint +================= +*/ +bool R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + trRefEntity_t ent; + + if ( tr.world->lightGridData == NULL ) + return false; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent ); + VectorCopy(ent.ambientLight, ambientLight); + VectorCopy(ent.directedLight, directedLight); + VectorCopy(ent.lightDir, lightDir); + + return true; +} diff --git a/src/renderergl1/tr_local.h b/src/renderergl1/tr_local.h new file mode 100644 index 0000000..e954036 --- /dev/null +++ b/src/renderergl1/tr_local.h @@ -0,0 +1,1603 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + + +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + +#include + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/qfiles.h" +#include "qcommon/qcommon.h" + +#include "renderercommon/tr_common.h" + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; + +// 14 bits +// can't be increased without changing bit packing for drawsurfs +// see QSORT_SHADERNUM_SHIFT +#define SHADERNUM_BITS 14 +#define MAX_SHADERS (1<or.origin in local coordinates + float modelMatrix[16]; +} orientationr_t; + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_CONST +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_FOG, // standard fog + CGEN_CONST // fixed color +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE + float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL + float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // + = clockwise + // - = counterclockwise + float rotateSpeed; + +} texModInfo_t; + + +#define MAX_IMAGE_ANIMATIONS 8 + +typedef struct { + image_t *image[MAX_IMAGE_ANIMATIONS]; + int numImageAnimations; + float imageAnimationSpeed; + + texCoordGen_t tcGen; + vec3_t tcGenVectors[2]; + + int numTexMods; + texModInfo_t *texMods; + + int videoMapHandle; + bool isLightmap; + bool isVideoMap; +} textureBundle_t; + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + bool active; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + bool isDetail; +} shaderStage_t; + +struct shaderCommands_s; + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface) +} fogPass_t; + +typedef struct { + float cloudHeight; + image_t *outerbox[6], *innerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + int lightmapIndex; // for a shader to match, both name and lightmapIndex must match + + int index; // this shader == tr.shaders[index] + int sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + bool defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + + bool explicitlyDefined; // found in a .shader file + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + bool entityMergable; // merge across entites optimizable (smoke, blood) + + bool isSky; + skyParms_t sky; + fogParms_t fogParms; + + float portalRange; // distance to fog out at + + int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage) + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + bool polygonOffset; // set for decals and other items that must be offset + bool noMipMaps; // for console fonts, 2D elements, etc. + bool noPicMip; // for images that must always be full resolution + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + + bool needsNormal; // not all shaders will need all data to be gathered + bool needsST1; + bool needsST2; + bool needsColor; + + int numDeforms; + deformStage_t deforms[MAX_SHADER_DEFORMS]; + + int numUnfoggedPasses; + shaderStage_t *stages[MAX_SHADER_STAGES]; + + void (*optimalStageIteratorFunc)( void ); + + double clampTime; // time this shader is clamped to + double timeOffset; // current time offset for this shader + + struct shader_s *remappedShader; // current shader this one is remapped too + + struct shader_s *next; +} shader_t; + + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + stereoFrame_t stereoFrame; + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + bool areamaskModified; // true if areamask changed since last scene + + double floatTime; // tr.refdef.time / 1000.0 + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + + int num_dlights; + struct dlight_s *dlights; + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; + + +} trRefdef_t; + + +//================================================================================= + +// max surfaces per-skin +// This is an arbitry limit. Vanilla Q3 only supported 32 surfaces in skins but failed to +// enforce the maximum limit when reading skin files. It was possile to use more than 32 +// surfaces which accessed out of bounds memory past end of skin->surfaces hunk block. +#define MAX_SKIN_SURFACES 256 + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + skinSurface_t *surfaces; // dynamically allocated array of surfaces +} skin_t; + + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + bool hasSurface; + float surface[4]; +} fog_t; + +typedef struct { + orientationr_t orientation; + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + bool isPortal; // true if this view is through a portal + bool isMirror; // the portal is a mirror, invert the face culling + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[4]; + vec3_t visBounds[2]; + float zFar; + stereoFrame_t stereoFrame; +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_MD3, + SF_MDR, + SF_IQM, + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0x7fffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned sort; // bit combination for fast compares + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +typedef struct srfGridMesh_s { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + + // culling information + vec3_t meshBounds[2]; + vec3_t localOrigin; + float meshRadius; + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + int lodFixed; + int lodStitched; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; + drawVert_t verts[1]; // variable sized +} srfGridMesh_t; + + + +#define VERTEXSIZE 8 +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits; + + // triangle definitions (no normals at points) + int numPoints; + int numIndices; + int ofsIndices; + float points[1][VERTEXSIZE]; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; + + +// misc_models in maps are turned into direct geometry by q3map +typedef struct { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + + // culling information (FIXME: use this!) + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + + // triangle definitions + int numIndexes; + int *indexes; + + int numVerts; + drawVert_t *verts; +} srfTriangles_t; + +// inter-quake-model +typedef struct { + int num_vertexes; + int num_triangles; + int num_frames; + int num_surfaces; + int num_joints; + int num_poses; + struct srfIQModel_s *surfaces; + + float *positions; + float *texcoords; + float *normals; + float *tangents; + byte *blendIndexes; + union { + float *f; + byte *b; + } blendWeights; + byte *colors; + int *triangles; + + // depending upon the exporter, blend indices and weights might be int/float + // as opposed to the recommended byte/byte, for example Noesis exports + // int/float whereas the official IQM tool exports byte/byte + byte blendWeightsType; // IQM_UBYTE or IQM_FLOAT + + int *jointParents; + float *jointMats; + float *poseMats; + float *bounds; + char *names; +} iqmData_t; + +// inter-quake-model surface +typedef struct srfIQModel_s { + surfaceType_t surfaceType; + char name[MAX_QPATH]; + shader_t *shader; + iqmData_t *data; + int first_vertex, num_vertexes; + int first_triangle, num_triangles; +} srfIQModel_t; + + +extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *); + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +typedef struct msurface_s { + int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + + +#define CONTENTS_NODE -1 +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + msurface_t **firstmarksurface; + int nummarksurfaces; +} mnode_t; + +typedef struct { + vec3_t bounds[2]; // for culling + msurface_t *firstSurface; + int numSurfaces; +} bmodel_t; + +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int dataSize; + + int numShaders; + dshader_t *shaders; + + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + int numsurfaces; + msurface_t *surfaces; + + int nummarksurfaces; + msurface_t **marksurfaces; + + int numfogs; + fog_t *fogs; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + byte *lightGridData; + + + int numClusters; + int clusterBytes; + const byte *vis; // may be passed in by CM_LoadMap to save space + + byte *novis; // clusterBytes of 0xff + + char *entityString; + char *entityParsePoint; +} world_t; + +//====================================================================== + +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, + MOD_MDR, + MOD_IQM +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + md3Header_t *md3[MD3_MAX_LODS]; // only if type == MOD_MESH + void *modelData; // only if type == (MOD_MDR | MOD_IQM) + + int numLods; +} model_t; + + +#define MAX_MOD_KNOWN 1024 + +void R_ModelInit (void); +model_t *R_GetModelByHandle( qhandle_t hModel ); +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f (void); + +//==================================================== + +#define MAX_DRAWIMAGES 2048 +#define MAX_SKINS 1024 + + +#define MAX_DRAWSURFS 0x10000 +#define DRAWSURF_MASK (MAX_DRAWSURFS-1) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +0 - 1 : dlightmap index +//2 : used to be clipped flag REMOVED - 03.21.00 rad +2 - 6 : fog index +11 - 20 : entity index +21 - 31 : sorted shader index + + TTimo - 1.32 +0-1 : dlightmap index +2-6 : fog index +7-16 : entity index +17-30 : sorted shader index +*/ +#define QSORT_FOGNUM_SHIFT 2 +#define QSORT_REFENTITYNUM_SHIFT 7 +#define QSORT_SHADERNUM_SHIFT (QSORT_REFENTITYNUM_SHIFT+REFENTITYNUM_BITS) +#if (QSORT_SHADERNUM_SHIFT+SHADERNUM_BITS) > 32 + #error "Need to update sorting, too many bits." +#endif + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) + + +// the renderer front end should never modify glstate_t +typedef struct { + int currenttextures[2]; + int currenttmu; + bool finishCalled; + int texEnv[2]; + int faceCulling; + unsigned long glStateBits; +} glstate_t; + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + float c_overDraw; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t orientation; + backEndCounters_t pc; + bool isHyperspace; + trRefEntity_t *currentEntity; + bool skyRenderedThisView; // flag for drawing sun + + bool projection2D; // if true, drawstretchpic doesn't need to change modes + byte color2D[4]; + bool vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering +} backEndState_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +typedef struct { + bool registered; // cleared at shutdown, set at beginRegistration + + int visCount; // incremented every time a new vis cluster is entered + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int frameSceneNum; // zeroed at RE_BeginFrame + + bool worldMapLoaded; + world_t *world; + + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load + + image_t *defaultImage; + image_t *scratchImage[32]; + image_t *fogImage; + image_t *dlightImage; // inverse-quare highlight for projective adding + image_t *flareImage; + image_t *whiteImage; // full of 0xff + image_t *identityLightImage; // full of tr.identityLightByte + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *projectionShadowShader; + + shader_t *flareShader; + shader_t *sunShader; + + int numLightmaps; + image_t **lightmaps; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + int shiftedEntityNum; // currentEntityNum << QSORT_REFENTITYNUM_SHIFT + model_t *currentModel; + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t orientation; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + vec3_t sunLight; // from the sky shader for this level + vec3_t sunDirection; + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + vec4_t clipRegion; // 2D clipping region + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + + int numImages; + image_t *images[MAX_DRAWIMAGES]; + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + + int numSkins; + skin_t *skins[MAX_SKINS]; + + float sinTable[FUNCTABLE_SIZE]; + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; +} trGlobals_t; + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init + +// +// cvars +// +extern cvar_t *r_flareSize; +extern cvar_t *r_flareFade; +// coefficient for the flare intensity falloff function. +#define FLARE_STDCOEFF "150" +extern cvar_t *r_flareCoeff; + +extern cvar_t *r_railWidth; +extern cvar_t *r_railCoreWidth; +extern cvar_t *r_railSegmentLength; + +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew +extern cvar_t *r_ignoreFastPath; // allows us to ignore our Tess fast paths + +extern cvar_t *r_znear; // near Z clip plane +extern cvar_t *r_zproj; // z distance of projection plane +extern cvar_t *r_stereoSeparation; // separation of cameras for stereo rendering + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; + +extern cvar_t *r_primitives; // "0" = based on compiled vertex array existance + // "1" = glDrawElemet tristrips + // "2" = glDrawElements triangles + // "-1" = no drawing + +extern cvar_t *r_inGameVideo; // controls whether in game video should be draw +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_gamma; +extern cvar_t *r_displayRefresh; // optional display refresh option +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; + +extern cvar_t *r_ext_texture_filter_anisotropic; +extern cvar_t *r_ext_max_anisotropy; + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_roundImagesDown; +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +extern cvar_t *r_fullbright; // avoid lightmap pass +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance +extern cvar_t *r_uiFullScreen; // ui is running fullscreen + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_anaglyphMode; + +extern cvar_t *r_greyscale; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; +extern cvar_t *r_mapOverBrightBits; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; + +extern cvar_t *r_printShaders; + +extern cvar_t *r_marksOnTriangleMeshes; + +//==================================================================== + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, bool isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ); + +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, int fogIndex, int dlightMap ); + + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld (vec3_t local, vec3_t world); +void R_LocalPointToWorld (vec3_t local, vec3_t world); +int R_CullLocalBox (vec3_t bounds[2]); +int R_CullPointAndRadius( vec3_t origin, float radius ); +int R_CullLocalPointAndRadius( vec3_t origin, float radius ); + +void R_SetupProjection(viewParms_t *dest, float zProj, bool computeFrustum); +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *orientation ); + +/* +** GL wrapper/helper functions +*/ +void GL_Bind( image_t *image ); +void GL_SetDefaultState (void); +void GL_SelectTexture( int unit ); +void GL_TextureMode( const char *string ); +void GL_CheckErrors( void ); +void GL_State( unsigned long stateVector ); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_BITS 0x70000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE + +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, bool dirty); +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, bool dirty); + +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_BeginRegistration( glconfig_t *glconfig ); +void RE_LoadWorldMap( const char *mapname ); +void RE_SetWorldVisData( const byte *vis ); +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +void RE_Shutdown( bool destroyWindow ); + +bool R_GetEntityToken( char *buffer, int size ); + +model_t *R_AllocModel( void ); + +void R_Init( void ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 +const void *RB_TakeScreenshotCmd( const void *data ); +void R_ScreenShot_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +int R_SumOfUsedImages( void ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + +int R_ComputeLOD( trRefEntity_t *ent ); + +const void *RB_TakeVideoFrameCmd( const void *data ); + +// +// tr_shader.c +// +shader_t *R_FindShader( const char *name, int lightmapIndex, bool mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +shader_t *R_GetShaderByState( int index, long *cycleTime ); +shader_t *R_FindShaderByName( const char *name ); +void R_InitShaders( void ); +void R_ShaderList_f( void ); +void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset); + +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ +typedef byte color4ub_t[4]; + +typedef struct stageVars +{ + color4ub_t colors[SHADER_MAX_VERTEXES]; + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + + +typedef struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16); + vec4_t xyz[SHADER_MAX_VERTEXES] QALIGN(16); + vec4_t normal[SHADER_MAX_VERTEXES] QALIGN(16); + vec2_t texCoords[SHADER_MAX_VERTEXES][2] QALIGN(16); + color4ub_t vertexColors[SHADER_MAX_VERTEXES] QALIGN(16); + int vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16); + + stageVars_t svars QALIGN(16); + + color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16); + + shader_t *shader; + double shaderTime; + int fogNum; + + int dlightBits; // or together of all vertexDlightBits + + int numIndexes; + int numVertexes; + + // info extracted from current shader + int numPasses; + void (*currentStageIteratorFunc)( void ); + shaderStage_t **xstages; +} shaderCommands_t; + +extern shaderCommands_t tess; + +void RB_BeginSurface(shader_t *shader, int fogNum ); +void RB_EndSurface(void); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} + +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); +void RB_StageIteratorVertexLitTexture( void ); +void RB_StageIteratorLightmappedMultitexture( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ); + +void RB_ShowImages( void ); + + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); +bool R_inPVS( const vec3_t p1, const vec3_t p2 ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ); +void RB_AddDlightFlares( void ); +void RB_RenderFlares (void); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *orientation ); +bool R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ + +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( float scale, shader_t *shader ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); + +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ + +#define PATCH_STITCHING + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ); +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ); +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ); +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ); + +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_InitNextFrame( void ); + +void RE_ClearScene( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_RenderScene( const refdef_t *fd ); + +/* +============================================================= + +UNCOMPRESSING BONES + +============================================================= +*/ + +#define MC_BITS_X (16) +#define MC_BITS_Y (16) +#define MC_BITS_Z (16) +#define MC_BITS_VECT (16) + +#define MC_SCALE_X (1.0f/64) +#define MC_SCALE_Y (1.0f/64) +#define MC_SCALE_Z (1.0f/64) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp); + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ); +void RB_MDRSurfaceAnim( mdrSurface_t *surface ); +bool R_LoadIQM (model_t *mod, void *buffer, int filesize, const char *name ); +void R_AddIQMSurfaces( trRefEntity_t *ent ); +void RB_IQMSurfaceAnim( surfaceType_t *surface ); +int R_IQMLerpTag( orientation_t *tag, iqmData_t *data, + int startFrame, int endFrame, + float frac, const char *tagName ); + +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcEnvironmentTexCoords( float *dstTexCoords ); +void RB_CalcFogTexCoords( float *dstTexCoords ); +void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords ); +void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords ); +void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords ); +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords ); +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords ); +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +void RB_CalcModulateAlphasByFog( unsigned char *dstColors ); +void RB_CalcModulateRGBAsByFog( unsigned char *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcAlphaFromEntity( unsigned char *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords ); +void RB_CalcColorFromEntity( unsigned char *dstColors ); +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcSpecularAlpha( unsigned char *alphas ); +void RB_CalcDiffuseColor( unsigned char *colors ); + +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#define MAX_RENDER_COMMANDS 0x40000 + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; +} stretchPicCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +} drawSurfsCommand_t; + +typedef struct { + int commandId; + int x; + int y; + int width; + int height; + char *fileName; + bool jpeg; +} screenshotCommand_t; + +typedef struct { + int commandId; + int width; + int height; + byte *captureBuffer; + byte *encodeBuffer; + bool motionJpeg; +} videoFrameCommand_t; + +typedef struct +{ + int commandId; + + GLboolean rgba[4]; +} colorMaskCommand_t; + +typedef struct +{ + int commandId; +} clearDepthCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS, + RC_SCREENSHOT, + RC_VIDEOFRAME, + RC_COLORMASK, + RC_CLEARDEPTH +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc +#define MAX_POLYS 600 +#define MAX_POLYVERTS 3000 + +// all of the information needed by the back end must be +// contained in a backEndData_t +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; + dlight_t dlights[MAX_DLIGHTS]; + trRefEntity_t entities[MAX_REFENTITIES]; + srfPoly_t *polys;//[MAX_POLYS]; + polyVert_t *polyVerts;//[MAX_POLYVERTS]; + renderCommandList_t commands; +} backEndData_t; + +extern int max_polys; +extern int max_polyverts; + +extern backEndData_t *backEndData; // the second one may not be allocated + + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_IssuePendingRenderCommands( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); + +void RE_SetColor( const float *rgba ); +void RE_SetClipRegion( const float *region ); +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, + unsigned char *image_buffer, int padding); +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding); +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, bool motionJpeg ); + + +#endif //TR_LOCAL_H diff --git a/src/renderergl1/tr_main.cpp b/src/renderergl1/tr_main.cpp new file mode 100644 index 0000000..1be6ae7 --- /dev/null +++ b/src/renderergl1/tr_main.cpp @@ -0,0 +1,1394 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_main.c -- main control flow for each frame + +#include "tr_local.h" + +#include // memcpy + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +refimport_t ri; + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox (vec3_t bounds[2]) { + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // transform into world space + for (i = 0 ; i < 8 ; i++) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + VectorCopy( tr.orientation.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.orientation.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.orientation.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.orientation.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for (i = 0 ; i < 4 ; i++) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for (j = 0 ; j < 8 ; j++) { + dists[j] = DotProduct(transformed[j], frust->normal); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +} + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( vec3_t pt, float radius ) +{ + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( vec3_t pt, float radius ) +{ + int i; + float dist; + cplane_t *frust; + bool mightBeClipped = false; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // check against frustum planes + for (i = 0 ; i < 4 ; i++) + { + frust = &tr.viewParms.frustum[i]; + + dist = DotProduct( pt, frust->normal) - frust->dist; + if ( dist < -radius ) + { + return CULL_OUT; + } + else if ( dist <= radius ) + { + mightBeClipped = true; + } + } + + if ( mightBeClipped ) + { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld (vec3_t local, vec3_t world) { + world[0] = local[0] * tr.orientation.axis[0][0] + local[1] * tr.orientation.axis[1][0] + local[2] * tr.orientation.axis[2][0]; + world[1] = local[0] * tr.orientation.axis[0][1] + local[1] * tr.orientation.axis[1][1] + local[2] * tr.orientation.axis[2][1]; + world[2] = local[0] * tr.orientation.axis[0][2] + local[1] * tr.orientation.axis[1][2] + local[2] * tr.orientation.axis[2][2]; +} + +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld (vec3_t local, vec3_t world) { + world[0] = local[0] * tr.orientation.axis[0][0] + local[1] * tr.orientation.axis[1][0] + local[2] * tr.orientation.axis[2][0] + tr.orientation.origin[0]; + world[1] = local[0] * tr.orientation.axis[0][1] + local[1] * tr.orientation.axis[1][1] + local[2] * tr.orientation.axis[2][1] + tr.orientation.origin[1]; + world[2] = local[0] * tr.orientation.axis[0][2] + local[1] * tr.orientation.axis[1][2] + local[2] * tr.orientation.axis[2][2] + tr.orientation.origin[2]; +} + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal (vec3_t world, vec3_t local) { + local[0] = DotProduct(world, tr.orientation.axis[0]); + local[1] = DotProduct(world, tr.orientation.axis[1]); + local[2] = DotProduct(world, tr.orientation.axis[2]); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth; + window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *orientation ) { + float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *orientation = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, orientation->origin ); + + VectorCopy( ent->e.axis[0], orientation->axis[0] ); + VectorCopy( ent->e.axis[1], orientation->axis[1] ); + VectorCopy( ent->e.axis[2], orientation->axis[2] ); + + glMatrix[0] = orientation->axis[0][0]; + glMatrix[4] = orientation->axis[1][0]; + glMatrix[8] = orientation->axis[2][0]; + glMatrix[12] = orientation->origin[0]; + + glMatrix[1] = orientation->axis[0][1]; + glMatrix[5] = orientation->axis[1][1]; + glMatrix[9] = orientation->axis[2][1]; + glMatrix[13] = orientation->origin[1]; + + glMatrix[2] = orientation->axis[0][2]; + glMatrix[6] = orientation->axis[1][2]; + glMatrix[10] = orientation->axis[2][2]; + glMatrix[14] = orientation->origin[2]; + + glMatrix[3] = 0; + glMatrix[7] = 0; + glMatrix[11] = 0; + glMatrix[15] = 1; + + myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, orientation->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->orientation.origin, orientation->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + } else { + axisLength = 1.0f; + } + + orientation->viewOrigin[0] = DotProduct( delta, orientation->axis[0] ) * axisLength; + orientation->viewOrigin[1] = DotProduct( delta, orientation->axis[1] ) * axisLength; + orientation->viewOrigin[2] = DotProduct( delta, orientation->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer (void) +{ + float viewerMatrix[16]; + vec3_t origin; + + Com_Memset (&tr.orientation, 0, sizeof(tr.orientation)); + tr.orientation.axis[0][0] = 1; + tr.orientation.axis[1][1] = 1; + tr.orientation.axis[2][2] = 1; + VectorCopy (tr.viewParms.orientation.origin, tr.orientation.viewOrigin); + + // transform by the camera placement + VectorCopy( tr.viewParms.orientation.origin, origin ); + + viewerMatrix[0] = tr.viewParms.orientation.axis[0][0]; + viewerMatrix[4] = tr.viewParms.orientation.axis[0][1]; + viewerMatrix[8] = tr.viewParms.orientation.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.orientation.axis[1][0]; + viewerMatrix[5] = tr.viewParms.orientation.axis[1][1]; + viewerMatrix[9] = tr.viewParms.orientation.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.orientation.axis[2][0]; + viewerMatrix[6] = tr.viewParms.orientation.axis[2][1]; + viewerMatrix[10] = tr.viewParms.orientation.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.orientation.modelMatrix ); + + tr.viewParms.world = tr.orientation; + +} + +/* +** SetFarClip +*/ +static void R_SetFarClip( void ) +{ + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + tr.viewParms.zFar = 2048; + return; + } + + // + // set far clipping planes dynamically + // + farthestCornerDistance = 0; + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + vec3_t vecTo; + float distance; + + if ( i & 1 ) + { + v[0] = tr.viewParms.visBounds[0][0]; + } + else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) + { + v[1] = tr.viewParms.visBounds[0][1]; + } + else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) + { + v[2] = tr.viewParms.visBounds[0][2]; + } + else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + VectorSubtract( v, tr.viewParms.orientation.origin, vecTo ); + + distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2]; + + if ( distance > farthestCornerDistance ) + { + farthestCornerDistance = distance; + } + } + tr.viewParms.zFar = sqrt( farthestCornerDistance ); +} + +/* +================= +R_SetupFrustum + +Set up the culling frustum planes for the current view using the results we got from computing the first two rows of +the projection matrix. +================= +*/ +void R_SetupFrustum (viewParms_t *dest, float xmin, float xmax, float ymax, float zProj, float stereoSep) +{ + vec3_t ofsorigin; + float oppleg, adjleg, length; + int i; + + if(stereoSep == 0 && xmin == -xmax) + { + // symmetric case can be simplified + VectorCopy(dest->orientation.origin, ofsorigin); + + length = sqrt(xmax * xmax + zProj * zProj); + oppleg = xmax / length; + adjleg = zProj / length; + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, adjleg, dest->orientation.axis[1], dest->frustum[0].normal); + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -adjleg, dest->orientation.axis[1], dest->frustum[1].normal); + } + else + { + // In stereo rendering, due to the modification of the projection matrix, dest->orientation.origin is not the + // actual origin that we're rendering so offset the tip of the view pyramid. + VectorMA(dest->orientation.origin, stereoSep, dest->orientation.axis[1], ofsorigin); + + oppleg = xmax + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->orientation.axis[0], oppleg / length, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, zProj / length, dest->orientation.axis[1], dest->frustum[0].normal); + + oppleg = xmin + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->orientation.axis[0], -oppleg / length, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -zProj / length, dest->orientation.axis[1], dest->frustum[1].normal); + } + + length = sqrt(ymax * ymax + zProj * zProj); + oppleg = ymax / length; + adjleg = zProj / length; + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[2].normal); + VectorMA(dest->frustum[2].normal, adjleg, dest->orientation.axis[2], dest->frustum[2].normal); + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[3].normal); + VectorMA(dest->frustum[3].normal, -adjleg, dest->orientation.axis[2], dest->frustum[3].normal); + + for (i=0 ; i<4 ; i++) { + dest->frustum[i].type = PLANE_NON_AXIAL; + dest->frustum[i].dist = DotProduct (ofsorigin, dest->frustum[i].normal); + SetPlaneSignbits( &dest->frustum[i] ); + } +} + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection(viewParms_t *dest, float zProj, bool computeFrustum) +{ + float xmin, xmax, ymin, ymax; + float width, height, stereoSep = r_stereoSeparation->value; + + /* + * offset the view origin of the viewer for stereo rendering + * by setting the projection matrix appropriately. + */ + + if(stereoSep != 0) + { + if(dest->stereoFrame == STEREO_LEFT) + stereoSep = zProj / stereoSep; + else if(dest->stereoFrame == STEREO_RIGHT) + stereoSep = zProj / -stereoSep; + else + stereoSep = 0; + } + + ymax = zProj * tan(dest->fovY * M_PI / 360.0f); + ymin = -ymax; + + xmax = zProj * tan(dest->fovX * M_PI / 360.0f); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + + dest->projectionMatrix[0] = 2 * zProj / width; + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = (xmax + xmin + 2 * stereoSep) / width; + dest->projectionMatrix[12] = 2 * zProj * stereoSep / width; + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 * zProj / height; + dest->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + dest->projectionMatrix[13] = 0; + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = -1; + dest->projectionMatrix[15] = 0; + + // Now that we have all the data for the projection matrix we can also setup the view frustum. + if(computeFrustum) + R_SetupFrustum(dest, xmin, xmax, ymax, zProj, stereoSep); +} + +/* +=============== +R_SetupProjectionZ + +Sets the z-component transformation part in the projection matrix +=============== +*/ +void R_SetupProjectionZ(viewParms_t *dest) +{ + float zNear, zFar, depth; + + zNear = r_znear->value; + zFar = dest->zFar; + depth = zFar - zNear; + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = -( zFar + zNear ) / depth; + dest->projectionMatrix[14] = -2 * zFar * zNear / depth; +} + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(local, surface->axis[i]); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(in, surface->axis[i]); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) { + srfTriangles_t *tri; + srfPoly_t *poly; + drawVert_t *v1, *v2, *v3; + vec4_t plane4; + + if (!surfType) { + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } + switch (*surfType) { + case SF_FACE: + *plane = ((srfSurfaceFace_t *)surfType)->plane; + return; + case SF_TRIANGLES: + tri = (srfTriangles_t *)surfType; + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns true if it should be mirrored +================= +*/ +bool R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, bool *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != REFENTITYNUM_WORLD ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.orientation ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.orientation.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orientation.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = true; + return true; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.oldframe ) { + // if a speed is specified + if ( e->e.frame ) { + // continuous rotate + d = (tr.refdef.time/1000.0f) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else { + // bobbing rotate, with skinNum being the rotation offset + d = sin( tr.refdef.time * 0.003f ); + d = e->e.skinNum + d * 4; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + } + else if ( e->e.skinNum ) { + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = false; + return true; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" ); + + return false; +} + +static bool IsMirror( const drawSurf_t *drawSurf, int entityNum ) +{ + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != REFENTITYNUM_WORLD ) + { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.orientation ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.orientation.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orientation.origin ); + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) + { + return true; + } + + return false; + } + return false; +} + +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static bool SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 100000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + RB_BeginSurface( shader, fogNum ); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + + assert( tess.numVertexes < 128 ); + + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.orientation.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) + { + pointFlags |= (1 << (j*2)); + } + else if ( clip[j] <= -clip[3] ) + { + pointFlags |= ( 1 << (j*2+1)); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) + { + return true; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal; + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.orientation.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) + { + shortest = len; + } + + if ( DotProduct( normal, tess.normal[tess.indexes[i]] ) >= 0 ) + { + numTriangles--; + } + } + if ( !numTriangles ) + { + return true; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) + { + return false; + } + + if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) ) + { + return true; + } + + return false; +} + +/* +======================== +R_MirrorViewBySurface + +Returns true if another view has been rendered +======================== +*/ +bool R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if (tr.viewParms.isPortal) { + ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" ); + return false; + } + + if ( r_noportals->integer || (r_fastsky->integer == 1) ) { + return false; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return false; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = true; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return false; // bad portal, no portalentity + } + + R_MirrorPoint (oldParms.orientation.origin, &surface, &camera, newParms.orientation.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector (oldParms.orientation.axis[0], &surface, &camera, newParms.orientation.axis[0]); + R_MirrorVector (oldParms.orientation.axis[1], &surface, &camera, newParms.orientation.axis[1]); + R_MirrorVector (oldParms.orientation.axis[2], &surface, &camera, newParms.orientation.axis[2]); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView (&newParms); + + tr.viewParms = oldParms; + + return true; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + if ( ent->e.renderfx & RF_CROSSHAIR ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +=============== +R_Radix +=============== +*/ +static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest ) +{ + int count[ 256 ] = { 0 }; + int index[ 256 ]; + int i; + unsigned char *sortKey = NULL; + unsigned char *end = NULL; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + end = sortKey + ( size * sizeof( drawSurf_t ) ); + for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) ) + ++count[ *sortKey ]; + + index[ 0 ] = 0; + + for( i = 1; i < 256; ++i ) + index[ i ] = index[ i - 1 ] + count[ i - 1 ]; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) ) + dest[ index[ *sortKey ]++ ] = source[ i ]; +} + +/* +=============== +R_RadixSort + +Radix sort with 4 byte size buckets +=============== +*/ +static void R_RadixSort( drawSurf_t *source, int size ) +{ + static drawSurf_t scratch[ MAX_DRAWSURFS ]; +#ifdef Q3_LITTLE_ENDIAN + R_Radix( 0, size, source, scratch ); + R_Radix( 1, size, scratch, source ); + R_Radix( 2, size, source, scratch ); + R_Radix( 3, size, scratch, source ); +#else + R_Radix( 3, size, source, scratch ); + R_Radix( 2, size, scratch, source ); + R_Radix( 1, size, source, scratch ); + R_Radix( 0, size, scratch, source ); +#endif //Q3_LITTLE_ENDIAN +} + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap ) { + int index; + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + tr.refdef.drawSurfs[index].surface = surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ]; + *entityNum = ( sort >> QSORT_REFENTITYNUM_SHIFT ) & REFENTITYNUM_MASK; + *dlightMap = sort & 3; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + int i; + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // sort the drawsurfs by sort type, then orientation, then shader + R_RadixSort( drawSurfs, numDrawSurfs ); + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces (void) { + trRefEntity_t *ent; + shader_t *shader; + + if ( !r_drawentities->integer ) { + return; + } + + for ( tr.currentEntityNum = 0; + tr.currentEntityNum < tr.refdef.num_entities; + tr.currentEntityNum++ ) { + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + + ent->needDlights = false; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal) { + continue; + } + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_BEAM: + case RT_LIGHTNING: + case RT_RAIL_CORE: + case RT_RAIL_RINGS: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + continue; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 ); + break; + + case RT_MODEL: + // we must set up parts of tr.orientation for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.orientation ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if (!tr.currentModel) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + case MOD_MDR: + R_MDRAddAnimSurfaces( ent ); + break; + case MOD_IQM: + R_AddIQMSurfaces( ent ); + break; + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; + case MOD_BAD: // null model axis + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + break; + } + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } + } + +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + + // dynamically compute far clip plane distance + R_SetFarClip(); + + // we know the size of the clipping volume. Now set the rest of the projection matrix. + R_SetupProjectionZ (&tr.viewParms); + + R_AddEntitySurfaces (); +} + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color&1, (color>>1)&1, (color>>2)&1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // draw wireframe outline + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglDepthRange( 0, 0 ); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + qglDepthRange( 0, 1 ); +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + R_IssuePendingRenderCommands(); + + GL_Bind( tr.whiteImage); + GL_Cull( CT_FRONT_SIDED ); + ri.CM_DrawDebugSurface( R_DebugPolygon ); +} + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView (viewParms_t *parms) { + int firstDrawSurf; + int numDrawSurfs; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupProjection(&tr.viewParms, r_zproj->value, true); + + R_GenerateDrawSurfs(); + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + numDrawSurfs = tr.refdef.numDrawSurfs; + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; + } + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_DebugGraphics(); +} diff --git a/src/renderergl1/tr_marks.cpp b/src/renderergl1/tr_marks.cpp new file mode 100644 index 0000000..e2c3583 --- /dev/null +++ b/src/renderergl1/tr_marks.cpp @@ -0,0 +1,459 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_marks.c -- polygon projection on the world polygons + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon) { + float dists[MAX_VERTS_ON_POLY+4] = { 0 }; + int sides[MAX_VERTS_ON_POLY+4] = { 0 }; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ (i+1) % numInPoints ]; + + d = dists[i] - dists[i+1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for (j=0 ; j<3 ; j++) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + (*numOutPoints)++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) { + + int s, c; + msurface_t *surf, **mark; + + // do the tail recursion in a loop + while ( node->contents == -1 ) { + s = BoxOnPlaneSide( mins, maxs, node->plane ); + if (s == 1) { + node = node->children[0]; + } else if (s == 2) { + node = node->children[1]; + } else { + R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir); + node = node->children[1]; + } + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + // + if (*listlength >= listsize) break; + // + surf = *mark; + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + surf->viewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if (*(surf->data) == SF_FACE) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &(( srfSurfaceFace_t * ) surf->data)->plane ); + if (s == 1 || s == 2) { + surf->viewCount = tr.viewCount; + } else if (DotProduct((( srfSurfaceFace_t * ) surf->data)->plane.normal, dir) > -0.5) { + // don't add faces that make sharp angles with the projection direction + surf->viewCount = tr.viewCount; + } + } + else if (*(surfaceType_t *) (surf->data) != SF_GRID && + *(surfaceType_t *) (surf->data) != SF_TRIANGLES) + surf->viewCount = tr.viewCount; + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if (surf->viewCount != tr.viewCount) { + surf->viewCount = tr.viewCount; + list[*listlength] = (surfaceType_t *) surf->data; + (*listlength)++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + (*returnedPoints) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + (*returnedFragments); + mf->firstPoint = (*returnedPoints); + mf->numPoints = numClipPoints; + Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + + (*returnedPoints) += numClipPoints; + (*returnedFragments)++; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY+2]; + float dists[MAX_VERTS_ON_POLY+2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfGridMesh_t *cv; + drawVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + int *indexes; + + if (numPoints <= 0) { + return 0; + } + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract(points[(i+1)%numPoints], points[i], v1); + VectorAdd(points[i], projection, v2); + VectorSubtract(points[i], v2, v2); + CrossProduct(v1, v2, normals[i]); + VectorNormalizeFast(normals[i]); + dists[i] = DotProduct(normals[i], points[i]); + } + // add near and far clipping planes for projection + VectorCopy(projectionDir, normals[numPoints]); + dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; + VectorCopy(projectionDir, normals[numPoints+1]); + VectorInverse(normals[numPoints+1]); + dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if (*surfaces[i] == SF_GRID) { + + cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy(dv[0].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.1) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy(dv[1].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.05) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } + else if (*surfaces[i] == SF_FACE) { + + srfSurfaceFace_t *surf = ( srfSurfaceFace_t * ) surfaces[i]; + + // check the normal of this face + if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { + continue; + } + + indexes = (int *)( (byte *)surf + surf->ofsIndices ); + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { + v = surf->points[0] + VERTEXSIZE * indexes[k+j];; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); + } + + // add the fragments of this face + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) { + + srfTriangles_t *surf = (srfTriangles_t *) surfaces[i]; + + for (k = 0; k < surf->numIndexes; k += 3) + { + for(j = 0; j < 3; j++) + { + v = surf->verts[surf->indexes[k + j]].xyz; + VectorMA(v, MARKER_OFFSET, surf->verts[surf->indexes[k + j]].normal, clipPoints[0][j]); + } + + // add the fragments of this face + R_AddMarkFragments(3, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); + if(returnedFragments == maxFragments) + { + return returnedFragments; // not enough space for more fragments + } + } + } + } + return returnedFragments; +} diff --git a/src/renderergl1/tr_mesh.cpp b/src/renderergl1/tr_mesh.cpp new file mode 100644 index 0000000..d8ca7e0 --- /dev/null +++ b/src/renderergl1/tr_mesh.cpp @@ -0,0 +1,413 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_mesh.c: triangle model functions + +#include "tr_local.h" + +static float ProjectRadius( float r, vec3_t location ) +{ + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.orientation.axis[0], tr.viewParms.orientation.origin ); + dist = DotProduct( tr.viewParms.orientation.axis[0], location ) - c; + + if ( dist <= 0 ) + return 0; + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) + pr = 1.0f; + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( md3Header_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + md3Frame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + oldFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_ComputeLOD + +================= +*/ +int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + md3Frame_t *frame; + mdrHeader_t *mdr; + mdrFrame_t *mdrframe; + int lod; + + if ( tr.currentModel->numLods < 2 ) + { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } + else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + + if(tr.currentModel->type == MOD_MDR) + { + int frameSize; + mdr = (mdrHeader_t *) tr.currentModel->modelData; + frameSize = (size_t) (&((mdrFrame_t *)0)->bones[mdr->numBones]); + + mdrframe = (mdrFrame_t *) ((byte *) mdr + mdr->ofsFrames + frameSize * ent->e.frame); + + radius = RadiusFromBounds(mdrframe->bounds[0], mdrframe->bounds[1]); + } + else + { + frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + } + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) + { + lodscale = r_lodscale->value; + if (lodscale > 20) lodscale = 20; + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = static_cast(flod); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= tr.currentModel->numLods ) + { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) + lod = tr.currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +int R_ComputeFogNum( md3Header_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + md3Frame_t *md3Frame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + md3Frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - md3Frame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + md3Frame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + md3Header_t *header = NULL; + md3Surface_t *surface = NULL; + md3Shader_t *md3Shader = NULL; + shader_t *shader = NULL; + int cull; + int lod; + int fogNum; + bool personalModel; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->md3[0]->numFrames; + ent->e.oldframe %= tr.currentModel->md3[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->md3[0]->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->md3[0]->numFrames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + header = tr.currentModel->md3[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel ( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + // + // draw all surfaces + // + surface = (md3Surface_t *)( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j].name, surface->name ) ) { + shader = skin->surfaces[j].shader; + break; + } + } + if (shader == tr.defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name); + } + else if (shader->defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name); + } + } else if ( surface->numShaders <= 0 ) { + shader = tr.defaultShader; + } else { + md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); + md3Shader += ent->e.skinNum % surface->numShaders; + shader = tr.shaders[ md3Shader->shaderIndex ]; + } + + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t*)surface, tr.shadowShader, 0, false ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t*)surface, tr.projectionShadowShader, 0, false ); + } + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, false ); + } + + surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd ); + } + +} diff --git a/src/renderergl1/tr_model.cpp b/src/renderergl1/tr_model.cpp new file mode 100644 index 0000000..5afd51d --- /dev/null +++ b/src/renderergl1/tr_model.cpp @@ -0,0 +1,1120 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_models.c -- model loading and caching + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +static bool R_LoadMD3(model_t *mod, int lod, void *buffer, const char *name ); +static bool R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name ); + +/* +==================== +R_RegisterMD3 +==================== +*/ +qhandle_t R_RegisterMD3(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + int lod; + int ident; + bool loaded = false; + int numLoaded; + char filename[MAX_QPATH], namebuf[MAX_QPATH+20]; + char *fext, defex[] = "md3"; + + numLoaded = 0; + + strcpy(filename, name); + + fext = strchr(filename, '.'); + if(!fext) + fext = defex; + else + { + *fext = '\0'; + fext++; + } + + for (lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod--) + { + if(lod) + Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext); + else + Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext); + + ri.FS_ReadFile( namebuf, &buf.v ); + if(!buf.u) + continue; + + ident = LittleLong(* (unsigned *) buf.u); + if (ident == MD3_IDENT) + loaded = R_LoadMD3(mod, lod, buf.u, name); + else + ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name); + + ri.FS_FreeFile(buf.v); + + if(loaded) + { + mod->numLods++; + numLoaded++; + } + else + break; + } + + if(numLoaded) + { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for(lod--; lod >= 0; lod--) + { + mod->numLods++; + mod->md3[lod] = mod->md3[lod + 1]; + } + + return mod->index; + } + + ri.Printf(PRINT_DEVELOPER, "R_RegisterMD3: couldn't load %s\n", name); + + mod->type = MOD_BAD; + return 0; +} + +/* +==================== +R_RegisterMDR +==================== +*/ +qhandle_t R_RegisterMDR(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + int ident; + bool loaded = false; + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + mod->type = MOD_BAD; + return 0; + } + + ident = LittleLong(*(unsigned *)buf.u); + if(ident == MDR_IDENT) + loaded = R_LoadMDR(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"R_RegisterMDR: couldn't load mdr file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; +} + +/* +==================== +R_RegisterIQM +==================== +*/ +qhandle_t R_RegisterIQM(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + bool loaded = false; + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + mod->type = MOD_BAD; + return 0; + } + + loaded = R_LoadIQM(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"R_RegisterIQM: couldn't load iqm file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; +} + + +struct modelExtToLoaderMap_t +{ + const char *ext; + qhandle_t (*ModelLoader)( const char *, model_t * ); +}; + +// Note that the ordering indicates the order of preference used +// when there are multiple models of different formats available +static modelExtToLoaderMap_t modelLoaders[ ] = +{ + { "iqm", R_RegisterIQM }, + { "mdr", R_RegisterMDR }, + { "md3", R_RegisterMD3 } +}; + +static int numModelLoaders = ARRAY_LEN(modelLoaders); + +//=============================================================================== + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) { + model_t *mod; + + // out of range gets the defualt model + if ( index < 1 || index >= tr.numModels ) { + return tr.models[0]; + } + + mod = tr.models[index]; + + return mod; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = (model_t*)ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low ); + mod->index = tr.numModels; + tr.models[tr.numModels] = mod; + tr.numModels++; + + return mod; +} + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +qhandle_t RE_RegisterModel( const char *name ) { + model_t *mod; + qhandle_t hModel; + bool orgNameFailed = false; + int orgLoader = -1; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + char altName[ MAX_QPATH ]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Model name exceeds MAX_QPATH\n" ); + return 0; + } + + // + // search the currently loaded models + // + for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { + mod = tr.models[hModel]; + if ( !strcmp( mod->name, name ) ) { + if( mod->type == MOD_BAD ) { + return 0; + } + return hModel; + } + } + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + + + R_IssuePendingRenderCommands(); + + mod->type = MOD_BAD; + mod->numLods = 0; + + // + // load the files + // + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numModelLoaders; i++ ) + { + if( !Q_stricmp( ext, modelLoaders[ i ].ext ) ) + { + // Load + hModel = modelLoaders[ i ].ModelLoader( localName, mod ); + break; + } + } + + // A loader was found + if( i < numModelLoaders ) + { + if( !hModel ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = true; + orgLoader = i; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return mod->index; + } + } + } + + // Try and find a suitable match using all + // the model formats supported + for( i = 0; i < numModelLoaders; i++ ) + { + if (i == orgLoader) + continue; + + Com_sprintf( altName, sizeof (altName), "%s.%s", localName, modelLoaders[ i ].ext ); + + // Load + hModel = modelLoaders[ i ].ModelLoader( altName, mod ); + + if( hModel ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } + + return hModel; +} + +/* +================= +R_LoadMD3 +================= +*/ +static bool R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name ) { + int i, j; + md3Header_t *pinmodel; + md3Frame_t *frame; + md3Surface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; + int version; + int size; + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD3_VERSION) { + ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION); + return false; + } + + mod->type = MOD_MESH; + size = LittleLong(pinmodel->ofsEnd); + mod->dataSize += size; + mod->md3[lod] = (md3Header_t*)ri.Hunk_Alloc( size, h_low ); + + Com_Memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) ); + + LL(mod->md3[lod]->ident); + LL(mod->md3[lod]->version); + LL(mod->md3[lod]->numFrames); + LL(mod->md3[lod]->numTags); + LL(mod->md3[lod]->numSurfaces); + LL(mod->md3[lod]->ofsFrames); + LL(mod->md3[lod]->ofsTags); + LL(mod->md3[lod]->ofsSurfaces); + LL(mod->md3[lod]->ofsEnd); + + if ( mod->md3[lod]->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has no frames\n", mod_name ); + return false; + } + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) { + + LL(surf->ident); + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts >= SHADER_MAX_VERTEXES ) { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i verts on %s (%i).\n", + mod_name, SHADER_MAX_VERTEXES - 1, surf->name[0] ? surf->name : "a surface", + surf->numVerts ); + return false; + } + if ( surf->numTriangles*3 >= SHADER_MAX_INDEXES ) { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i triangles on %s (%i).\n", + mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, surf->name[0] ? surf->name : "a surface", + surf->numTriangles ); + return false; + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } + + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, true ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + return true; +} + + + +/* +================= +R_LoadMDR +================= +*/ +static bool R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) +{ + int i, j, k, l; + mdrHeader_t *pinmodel, *mdr; + mdrFrame_t *frame; + mdrLOD_t *lod, *curlod; + mdrSurface_t *surf, *cursurf; + mdrTriangle_t *tri, *curtri; + mdrVertex_t *v, *curv; + mdrWeight_t *weight, *curweight; + mdrTag_t *tag, *curtag; + size_t size; + shader_t *sh; + + pinmodel = (mdrHeader_t *)buffer; + + pinmodel->version = LittleLong(pinmodel->version); + if (pinmodel->version != MDR_VERSION) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION); + return false; + } + + size = LittleLong(pinmodel->ofsEnd); + + if(size > filesize) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name); + return false; + } + + mod->type = MOD_MDR; + + LL(pinmodel->numFrames); + LL(pinmodel->numBones); + LL(pinmodel->ofsFrames); + + // This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame + // over and over again, we'll uncompress it in this function already, so we must adjust the size of the target mdr. + if(pinmodel->ofsFrames < 0) + { + // mdrFrame_t is larger than mdrCompFrame_t: + size += pinmodel->numFrames * sizeof(frame->name); + // now add enough space for the uncompressed bones. + size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t))); + } + + // simple bounds check + if(pinmodel->numBones < 0 || + sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + mod->dataSize += size; + mod->modelData = mdr = (mdrHeader_t*)ri.Hunk_Alloc( size, h_low ); + + // Copy all the values over from the file and fix endian issues in the process, if necessary. + + mdr->ident = LittleLong(pinmodel->ident); + mdr->version = pinmodel->version; // Don't need to swap byte order on this one, we already did above. + Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name)); + mdr->numFrames = pinmodel->numFrames; + mdr->numBones = pinmodel->numBones; + mdr->numLODs = LittleLong(pinmodel->numLODs); + mdr->numTags = LittleLong(pinmodel->numTags); + // We don't care about the other offset values, we'll generate them ourselves while loading. + + mod->numLods = mdr->numLODs; + + if ( mdr->numFrames < 1 ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name); + return false; + } + + /* The first frame will be put into the first free space after the header */ + frame = (mdrFrame_t *)(mdr + 1); + mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr); + + if (pinmodel->ofsFrames < 0) + { + mdrCompFrame_t *cframe; + + // compressed model... + cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames); + + for(i = 0; i < mdr->numFrames; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]); + } + + frame->radius = LittleFloat(cframe->radius); + frame->name[0] = '\0'; // No name supplied in the compressed version. + + for(j = 0; j < mdr->numBones; j++) + { + for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++) + { + // Do swapping for the uncompressing functions. They seem to use shorts + // values only, so I assume this will work. Never tested it on other + // platforms, though. + + ((unsigned short *)(cframe->bones[j].Comp))[k] = + LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] ); + } + + /* Now do the actual uncompressing */ + MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp); + } + + // Next Frame... + cframe = (mdrCompFrame_t *) &cframe->bones[j]; + frame = (mdrFrame_t *) &frame->bones[j]; + } + } + else + { + mdrFrame_t *curframe; + + // uncompressed model... + // + + curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames); + + // swap all the frames + for ( i = 0 ; i < mdr->numFrames ; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]); + } + + frame->radius = LittleFloat(curframe->radius); + Q_strncpyz(frame->name, curframe->name, sizeof(frame->name)); + + for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++) + { + ((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] ); + } + + curframe = (mdrFrame_t *) &curframe->bones[mdr->numBones]; + frame = (mdrFrame_t *) &frame->bones[mdr->numBones]; + } + } + + // frame should now point to the first free address after all frames. + lod = (mdrLOD_t *) frame; + mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr); + + curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs)); + + // swap all the LOD's + for ( l = 0 ; l < mdr->numLODs ; l++) + { + // simple bounds check + if((byte *) (lod + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + lod->numSurfaces = LittleLong(curlod->numSurfaces); + + // swap all the surfaces + surf = (mdrSurface_t *) (lod + 1); + lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod); + cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces)); + + for ( i = 0 ; i < lod->numSurfaces ; i++) + { + // simple bounds check + if((byte *) (surf + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + // first do some copying stuff + + surf->ident = SF_MDR; + Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name)); + Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader)); + + surf->ofsHeader = (byte *) mdr - (byte *) surf; + + surf->numVerts = LittleLong(cursurf->numVerts); + surf->numTriangles = LittleLong(cursurf->numTriangles); + // numBoneReferences and BoneReferences generally seem to be unused + + // now do the checks that may fail. + if ( surf->numVerts >= SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on %s (%i).\n", + mod_name, SHADER_MAX_VERTEXES - 1, surf->name[0] ? surf->name : "a surface", + surf->numVerts ); + return false; + } + if ( surf->numTriangles*3 >= SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on %s (%i).\n", + mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, surf->name[0] ? surf->name : "a surface", + surf->numTriangles ); + return false; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // register the shaders + sh = R_FindShader(surf->shader, LIGHTMAP_NONE, true); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + + // now copy the vertexes. + v = (mdrVertex_t *) (surf + 1); + surf->ofsVerts = (int)((byte *) v - (byte *) surf); + curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts)); + + for(j = 0; j < surf->numVerts; j++) + { + LL(curv->numWeights); + + // simple bounds check + if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + v->normal[0] = LittleFloat(curv->normal[0]); + v->normal[1] = LittleFloat(curv->normal[1]); + v->normal[2] = LittleFloat(curv->normal[2]); + + v->texCoords[0] = LittleFloat(curv->texCoords[0]); + v->texCoords[1] = LittleFloat(curv->texCoords[1]); + + v->numWeights = curv->numWeights; + weight = &v->weights[0]; + curweight = &curv->weights[0]; + + // Now copy all the weights + for(k = 0; k < v->numWeights; k++) + { + weight->boneIndex = LittleLong(curweight->boneIndex); + weight->boneWeight = LittleFloat(curweight->boneWeight); + + weight->offset[0] = LittleFloat(curweight->offset[0]); + weight->offset[1] = LittleFloat(curweight->offset[1]); + weight->offset[2] = LittleFloat(curweight->offset[2]); + + weight++; + curweight++; + } + + v = (mdrVertex_t *) weight; + curv = (mdrVertex_t *) curweight; + } + + // we know the offset to the triangles now: + tri = (mdrTriangle_t *) v; + surf->ofsTriangles = (int)((byte *) tri - (byte *) surf); + curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles)); + + // simple bounds check + if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + for(j = 0; j < surf->numTriangles; j++) + { + tri->indexes[0] = LittleLong(curtri->indexes[0]); + tri->indexes[1] = LittleLong(curtri->indexes[1]); + tri->indexes[2] = LittleLong(curtri->indexes[2]); + + tri++; + curtri++; + } + + // tri now points to the end of the surface. + surf->ofsEnd = (byte *) tri - (byte *) surf; + surf = (mdrSurface_t *) tri; + + // find the next surface. + cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd)); + } + + // surf points to the next lod now. + lod->ofsEnd = (int)((byte *) surf - (byte *) lod); + lod = (mdrLOD_t *) surf; + + // find the next LOD. + curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd)); + } + + // lod points to the first tag now, so update the offset too. + tag = (mdrTag_t *) lod; + mdr->ofsTags = (int)((byte *) tag - (byte *) mdr); + curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags)); + + // simple bounds check + if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + for (i = 0 ; i < mdr->numTags ; i++) + { + tag->boneIndex = LittleLong(curtag->boneIndex); + Q_strncpyz(tag->name, curtag->name, sizeof(tag->name)); + + tag++; + curtag++; + } + + // And finally we know the real offset to the end. + mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr); + + // phew! we're done. + + return true; +} + + + +//============================================================================= + +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { + + R_Init(); + + *glconfigOut = glConfig; + + R_IssuePendingRenderCommands(); + + tr.viewCluster = -1; // force markleafs to regenerate + R_ClearFlares(); + RE_ClearScene(); + + tr.registered = true; +} + +//============================================================================= + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) { + model_t *mod; + + // leave a space for NULL model + tr.numModels = 0; + + mod = R_AllocModel(); + mod->type = MOD_BAD; +} + + +/* +================ +R_Modellist_f +================ +*/ +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < tr.numModels; i++ ) { + mod = tr.models[i]; + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->md3[j] && mod->md3[j] != mod->md3[j-1] ) { + lods++; + } + } + ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + total += mod->dataSize; + } + ri.Printf( PRINT_ALL, "%8i : Total models\n", total ); + +#if 0 // not working right with new hunk + if ( tr.world ) { + ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name ); + } +#endif +} + + +//============================================================================= + + +/* +================ +R_GetTag +================ +*/ +static md3Tag_t *R_GetTag( md3Header_t *mod, int frame, const char *tagName ) { + md3Tag_t *tag; + int i; + + if ( frame >= mod->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mod->numFrames - 1; + } + + tag = (md3Tag_t *)((byte *)mod + mod->ofsTags) + frame * mod->numTags; + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) { + if ( !strcmp( tag->name, tagName ) ) { + return tag; // found it + } + } + + return NULL; +} + +md3Tag_t *R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, md3Tag_t * dest) +{ + int i, j, k; + int frameSize; + mdrFrame_t *frame; + mdrTag_t *tag; + + if ( framenum >= mod->numFrames ) + { + // it is possible to have a bad frame while changing models, so don't error + framenum = mod->numFrames - 1; + } + + tag = (mdrTag_t *)((byte *)mod + mod->ofsTags); + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) + { + if ( !strcmp( tag->name, tagName ) ) + { + Q_strncpyz(dest->name, tag->name, sizeof(dest->name)); + + // uncompressed model... + // + frameSize = (intptr_t)( &((mdrFrame_t *)0)->bones[ mod->numBones ] ); + frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize ); + + for (j = 0; j < 3; j++) + { + for (k = 0; k < 3; k++) + dest->axis[j][k] = frame->bones[tag->boneIndex].matrix[k][j]; + } + + dest->origin[0] = frame->bones[tag->boneIndex].matrix[0][3]; + dest->origin[1] = frame->bones[tag->boneIndex].matrix[1][3]; + dest->origin[2] = frame->bones[tag->boneIndex].matrix[2][3]; + + return dest; + } + } + + return NULL; +} + +/* +================ +R_LerpTag +================ +*/ +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ) { + md3Tag_t *start, *end; + md3Tag_t start_space, end_space; + int i; + float frontLerp, backLerp; + model_t *model; + + model = R_GetModelByHandle( handle ); + if ( !model->md3[0] ) + { + if(model->type == MOD_MDR) + { + start = R_GetAnimTag((mdrHeader_t *) model->modelData, startFrame, tagName, &start_space); + end = R_GetAnimTag((mdrHeader_t *) model->modelData, endFrame, tagName, &end_space); + } + else if( model->type == MOD_IQM ) { + return R_IQMLerpTag( tag, (iqmData_t*)model->modelData, + startFrame, endFrame, + frac, tagName ); + } else { + start = end = NULL; + } + } + else + { + start = R_GetTag( model->md3[0], startFrame, tagName ); + end = R_GetTag( model->md3[0], endFrame, tagName ); + } + + if ( !start || !end ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return false; + } + + frontLerp = frac; + backLerp = 1.0f - frac; + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp; + } + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); + return true; +} + + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + + model = R_GetModelByHandle( handle ); + + if(model->type == MOD_BRUSH) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + + return; + } else if (model->type == MOD_MESH) { + md3Header_t *header; + md3Frame_t *frame; + + header = model->md3[0]; + frame = (md3Frame_t *) ((byte *)header + header->ofsFrames); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; + } else if (model->type == MOD_MDR) { + mdrHeader_t *header; + mdrFrame_t *frame; + + header = (mdrHeader_t *)model->modelData; + frame = (mdrFrame_t *) ((byte *)header + header->ofsFrames); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; + } else if(model->type == MOD_IQM) { + iqmData_t *iqmData; + + iqmData = (iqmData_t*)model->modelData; + + if(iqmData->bounds) + { + VectorCopy(iqmData->bounds, mins); + VectorCopy(iqmData->bounds + 3, maxs); + return; + } + } + + VectorClear( mins ); + VectorClear( maxs ); +} diff --git a/src/renderergl1/tr_model_iqm.cpp b/src/renderergl1/tr_model_iqm.cpp new file mode 100644 index 0000000..680aecd --- /dev/null +++ b/src/renderergl1/tr_model_iqm.cpp @@ -0,0 +1,1187 @@ +/* +=========================================================================== +Copyright (C) 2011 Thilo Schulz +Copyright (C) 2011 Matthias Bentrup +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +// 3x4 identity matrix +static float identityMatrix[12] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0 +}; + +static bool IQM_CheckRange( iqmHeader_t *header, int offset, + int count,int size ) { + // return true if the range specified by offset, count and size + // doesn't fit into the file + return ( count <= 0 || + offset < 0 || + offset > header->filesize || + offset + count * size < 0 || + offset + count * size > header->filesize ); +} +// "multiply" 3x4 matrices, these are assumed to be the top 3 rows +// of a 4x4 matrix with the last row = (0 0 0 1) +static void Matrix34Multiply( float *a, float *b, float *out ) { + out[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8]; + out[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9]; + out[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10]; + out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3]; + out[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8]; + out[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9]; + out[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10]; + out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7]; + out[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; + out[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; + out[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; + out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; +} +static void Matrix34Multiply_OnlySetOrigin( float *a, float *b, float *out ) { + out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3]; + out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7]; + out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; +} +static void InterpolateMatrix( float *a, float *b, float lerp, float *mat ) { + float unLerp = 1.0f - lerp; + + mat[ 0] = a[ 0] * unLerp + b[ 0] * lerp; + mat[ 1] = a[ 1] * unLerp + b[ 1] * lerp; + mat[ 2] = a[ 2] * unLerp + b[ 2] * lerp; + mat[ 3] = a[ 3] * unLerp + b[ 3] * lerp; + mat[ 4] = a[ 4] * unLerp + b[ 4] * lerp; + mat[ 5] = a[ 5] * unLerp + b[ 5] * lerp; + mat[ 6] = a[ 6] * unLerp + b[ 6] * lerp; + mat[ 7] = a[ 7] * unLerp + b[ 7] * lerp; + mat[ 8] = a[ 8] * unLerp + b[ 8] * lerp; + mat[ 9] = a[ 9] * unLerp + b[ 9] * lerp; + mat[10] = a[10] * unLerp + b[10] * lerp; + mat[11] = a[11] * unLerp + b[11] * lerp; +} +static void JointToMatrix( vec4_t rot, vec3_t scale, vec3_t trans, + float *mat ) { + float xx = 2.0f * rot[0] * rot[0]; + float yy = 2.0f * rot[1] * rot[1]; + float zz = 2.0f * rot[2] * rot[2]; + float xy = 2.0f * rot[0] * rot[1]; + float xz = 2.0f * rot[0] * rot[2]; + float yz = 2.0f * rot[1] * rot[2]; + float wx = 2.0f * rot[3] * rot[0]; + float wy = 2.0f * rot[3] * rot[1]; + float wz = 2.0f * rot[3] * rot[2]; + + mat[ 0] = scale[0] * (1.0f - (yy + zz)); + mat[ 1] = scale[0] * (xy - wz); + mat[ 2] = scale[0] * (xz + wy); + mat[ 3] = trans[0]; + mat[ 4] = scale[1] * (xy + wz); + mat[ 5] = scale[1] * (1.0f - (xx + zz)); + mat[ 6] = scale[1] * (yz - wx); + mat[ 7] = trans[1]; + mat[ 8] = scale[2] * (xz - wy); + mat[ 9] = scale[2] * (yz + wx); + mat[10] = scale[2] * (1.0f - (xx + yy)); + mat[11] = trans[2]; +} +static void Matrix34Invert( float *inMat, float *outMat ) +{ + vec3_t trans; + float invSqrLen, *v; + + outMat[ 0] = inMat[ 0]; outMat[ 1] = inMat[ 4]; outMat[ 2] = inMat[ 8]; + outMat[ 4] = inMat[ 1]; outMat[ 5] = inMat[ 5]; outMat[ 6] = inMat[ 9]; + outMat[ 8] = inMat[ 2]; outMat[ 9] = inMat[ 6]; outMat[10] = inMat[10]; + + v = outMat + 0; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + v = outMat + 4; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + v = outMat + 8; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + + trans[0] = inMat[ 3]; + trans[1] = inMat[ 7]; + trans[2] = inMat[11]; + + outMat[ 3] = -DotProduct(outMat + 0, trans); + outMat[ 7] = -DotProduct(outMat + 4, trans); + outMat[11] = -DotProduct(outMat + 8, trans); +} + +/* +================= +R_LoadIQM + +Load an IQM model and compute the joint matrices for every frame. +================= +*/ +bool R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_name ) { + iqmHeader_t *header; + iqmVertexArray_t *vertexarray; + iqmTriangle_t *triangle; + iqmMesh_t *mesh; + iqmJoint_t *joint; + iqmPose_t *pose; + iqmBounds_t *bounds; + unsigned short *framedata; + char *str; + int i, j; + float jointInvMats[IQM_MAX_JOINTS * 12] = {0.0f}; + float *mat, *matInv; + size_t size, joint_names; + iqmData_t *iqmData; + srfIQModel_t *surface; + char meshName[MAX_QPATH]; + byte blendIndexesType, blendWeightsType; + + if( filesize < sizeof(iqmHeader_t) ) { + return false; + } + + header = (iqmHeader_t *)buffer; + if( Q_strncmp( header->magic, IQM_MAGIC, sizeof(header->magic) ) ) { + return false; + } + + LL( header->version ); + if( header->version != IQM_VERSION ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s is a unsupported IQM version (%d), only version %d is supported.\n", + mod_name, header->version, IQM_VERSION); + return false; + } + + LL( header->filesize ); + if( header->filesize > filesize || header->filesize > 16<<20 ) { + return false; + } + + LL( header->flags ); + LL( header->num_text ); + LL( header->ofs_text ); + LL( header->num_meshes ); + LL( header->ofs_meshes ); + LL( header->num_vertexarrays ); + LL( header->num_vertexes ); + LL( header->ofs_vertexarrays ); + LL( header->num_triangles ); + LL( header->ofs_triangles ); + LL( header->ofs_adjacency ); + LL( header->num_joints ); + LL( header->ofs_joints ); + LL( header->num_poses ); + LL( header->ofs_poses ); + LL( header->num_anims ); + LL( header->ofs_anims ); + LL( header->num_frames ); + LL( header->num_framechannels ); + LL( header->ofs_frames ); + LL( header->ofs_bounds ); + LL( header->num_comment ); + LL( header->ofs_comment ); + LL( header->num_extensions ); + LL( header->ofs_extensions ); + + // check ioq3 joint limit + if ( header->num_joints > IQM_MAX_JOINTS ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %d joints (%d).\n", + mod_name, IQM_MAX_JOINTS, header->num_joints); + return false; + } + + blendIndexesType = blendWeightsType = IQM_UBYTE; + + // check and swap vertex arrays + if( IQM_CheckRange( header, header->ofs_vertexarrays, + header->num_vertexarrays, + sizeof(iqmVertexArray_t) ) ) { + return false; + } + vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays); + for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) { + int n, *intPtr; + + if( vertexarray->size <= 0 || vertexarray->size > 4 ) { + return false; + } + + // total number of values + n = header->num_vertexes * vertexarray->size; + + switch( vertexarray->format ) { + case IQM_BYTE: + case IQM_UBYTE: + // 1 byte, no swapping necessary + if( IQM_CheckRange( header, vertexarray->offset, + n, sizeof(byte) ) ) { + return false; + } + break; + case IQM_INT: + case IQM_UINT: + case IQM_FLOAT: + // 4-byte swap + if( IQM_CheckRange( header, vertexarray->offset, + n, sizeof(float) ) ) { + return false; + } + intPtr = (int *)((byte *)header + vertexarray->offset); + for( j = 0; j < n; j++, intPtr++ ) { + LL( *intPtr ); + } + break; + default: + // not supported + return false; + break; + } + + switch( vertexarray->type ) { + case IQM_POSITION: + case IQM_NORMAL: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 3 ) { + return false; + } + break; + case IQM_TANGENT: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 4 ) { + return false; + } + break; + case IQM_TEXCOORD: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 2 ) { + return false; + } + break; + case IQM_BLENDINDEXES: + if( (vertexarray->format != IQM_INT && + vertexarray->format != IQM_UBYTE) || + vertexarray->size != 4 ) { + return false; + } + blendIndexesType = vertexarray->format; + break; + case IQM_BLENDWEIGHTS: + if( (vertexarray->format != IQM_FLOAT && + vertexarray->format != IQM_UBYTE) || + vertexarray->size != 4 ) { + return false; + } + blendWeightsType = vertexarray->format; + break; + case IQM_COLOR: + if( vertexarray->format != IQM_UBYTE || + vertexarray->size != 4 ) { + return false; + } + break; + } + } + + // check and swap triangles + if( IQM_CheckRange( header, header->ofs_triangles, + header->num_triangles, sizeof(iqmTriangle_t) ) ) { + return false; + } + triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles); + for( i = 0; i < header->num_triangles; i++, triangle++ ) { + LL( triangle->vertex[0] ); + LL( triangle->vertex[1] ); + LL( triangle->vertex[2] ); + + if( triangle->vertex[0] > header->num_vertexes || + triangle->vertex[1] > header->num_vertexes || + triangle->vertex[2] > header->num_vertexes ) { + return false; + } + } + + // check and swap meshes + if( IQM_CheckRange( header, header->ofs_meshes, + header->num_meshes, sizeof(iqmMesh_t) ) ) { + return false; + } + mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes); + for( i = 0; i < header->num_meshes; i++, mesh++) { + LL( mesh->name ); + LL( mesh->material ); + LL( mesh->first_vertex ); + LL( mesh->num_vertexes ); + LL( mesh->first_triangle ); + LL( mesh->num_triangles ); + + if ( mesh->name < header->num_text ) { + Q_strncpyz( meshName, (char*)header + header->ofs_text + mesh->name, sizeof (meshName) ); + } else { + meshName[0] = '\0'; + } + + // check ioq3 limits + if ( mesh->num_vertexes >= SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i verts on %s (%i).\n", + mod_name, SHADER_MAX_VERTEXES - 1, meshName[0] ? meshName : "a surface", + mesh->num_vertexes ); + return false; + } + if ( mesh->num_triangles*3 >= SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i triangles on %s (%i).\n", + mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, meshName[0] ? meshName : "a surface", + mesh->num_triangles ); + return false; + } + + if( mesh->first_vertex >= header->num_vertexes || + mesh->first_vertex + mesh->num_vertexes > header->num_vertexes || + mesh->first_triangle >= header->num_triangles || + mesh->first_triangle + mesh->num_triangles > header->num_triangles || + mesh->name >= header->num_text || + mesh->material >= header->num_text ) { + return false; + } + } + + if( header->num_poses != header->num_joints && header->num_poses != 0 ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has %d poses and %d joints, must have the same number or 0 poses\n", + mod_name, header->num_poses, header->num_joints ); + return false; + } + + joint_names = 0; + + if ( header->num_joints ) + { + // check and swap joints + if( IQM_CheckRange( header, header->ofs_joints, + header->num_joints, sizeof(iqmJoint_t) ) ) { + return false; + } + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + LL( joint->name ); + LL( joint->parent ); + LL( joint->translate[0] ); + LL( joint->translate[1] ); + LL( joint->translate[2] ); + LL( joint->rotate[0] ); + LL( joint->rotate[1] ); + LL( joint->rotate[2] ); + LL( joint->rotate[3] ); + LL( joint->scale[0] ); + LL( joint->scale[1] ); + LL( joint->scale[2] ); + + if( joint->parent < -1 || + joint->parent >= (int)header->num_joints || + joint->name >= (int)header->num_text ) { + return false; + } + joint_names += strlen( (char *)header + header->ofs_text + + joint->name ) + 1; + } + } + + if ( header->num_poses ) + { + // check and swap poses + if( IQM_CheckRange( header, header->ofs_poses, + header->num_poses, sizeof(iqmPose_t) ) ) { + return false; + } + pose = (iqmPose_t *)((byte *)header + header->ofs_poses); + for( i = 0; i < header->num_poses; i++, pose++ ) { + LL( pose->parent ); + LL( pose->mask ); + LL( pose->channeloffset[0] ); + LL( pose->channeloffset[1] ); + LL( pose->channeloffset[2] ); + LL( pose->channeloffset[3] ); + LL( pose->channeloffset[4] ); + LL( pose->channeloffset[5] ); + LL( pose->channeloffset[6] ); + LL( pose->channeloffset[7] ); + LL( pose->channeloffset[8] ); + LL( pose->channeloffset[9] ); + LL( pose->channelscale[0] ); + LL( pose->channelscale[1] ); + LL( pose->channelscale[2] ); + LL( pose->channelscale[3] ); + LL( pose->channelscale[4] ); + LL( pose->channelscale[5] ); + LL( pose->channelscale[6] ); + LL( pose->channelscale[7] ); + LL( pose->channelscale[8] ); + LL( pose->channelscale[9] ); + } + } + + if (header->ofs_bounds) + { + // check and swap model bounds + if(IQM_CheckRange(header, header->ofs_bounds, + header->num_frames, sizeof(*bounds))) + { + return false; + } + bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds); + for(i = 0; i < header->num_frames; i++) + { + LL(bounds->bbmin[0]); + LL(bounds->bbmin[1]); + LL(bounds->bbmin[2]); + LL(bounds->bbmax[0]); + LL(bounds->bbmax[1]); + LL(bounds->bbmax[2]); + + bounds++; + } + } + + // allocate the model and copy the data + size = sizeof(iqmData_t); + size += header->num_meshes * sizeof( srfIQModel_t ); + size += header->num_joints * 12 * sizeof( float ); // joint mats + size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats + if(header->ofs_bounds) + size += header->num_frames * 6 * sizeof(float); // model bounds + size += header->num_vertexes * 3 * sizeof(float); // positions + size += header->num_vertexes * 2 * sizeof(float); // texcoords + size += header->num_vertexes * 3 * sizeof(float); // normals + size += header->num_vertexes * 4 * sizeof(float); // tangents + size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes + size += header->num_vertexes * 4 * sizeof(byte); // colors + size += header->num_joints * sizeof(int); // parents + size += header->num_triangles * 3 * sizeof(int); // triangles + size += joint_names; // joint names + + // blendWeights + if (blendWeightsType == IQM_FLOAT) { + size += header->num_vertexes * 4 * sizeof(float); + } else { + size += header->num_vertexes * 4 * sizeof(byte); + } + + mod->type = MOD_IQM; + iqmData = (iqmData_t *)ri.Hunk_Alloc( size, h_low ); + mod->modelData = iqmData; + + // fill header + iqmData->num_vertexes = header->num_vertexes; + iqmData->num_triangles = header->num_triangles; + iqmData->num_frames = header->num_frames; + iqmData->num_surfaces = header->num_meshes; + iqmData->num_joints = header->num_joints; + iqmData->num_poses = header->num_poses; + iqmData->blendWeightsType = blendWeightsType; + iqmData->surfaces = (srfIQModel_t *)(iqmData + 1); + iqmData->jointMats = (float *) (iqmData->surfaces + iqmData->num_surfaces); + iqmData->poseMats = iqmData->jointMats + 12 * header->num_joints; + if(header->ofs_bounds) + { + iqmData->bounds = iqmData->poseMats + 12 * header->num_poses * header->num_frames; + iqmData->positions = iqmData->bounds + 6 * header->num_frames; + } + else + iqmData->positions = iqmData->poseMats + 12 * header->num_poses * header->num_frames; + iqmData->texcoords = iqmData->positions + 3 * header->num_vertexes; + iqmData->normals = iqmData->texcoords + 2 * header->num_vertexes; + iqmData->tangents = iqmData->normals + 3 * header->num_vertexes; + iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes); + + if(blendWeightsType == IQM_FLOAT) { + iqmData->blendWeights.f = (float *)(iqmData->blendIndexes + 4 * header->num_vertexes); + iqmData->colors = (byte *)(iqmData->blendWeights.f + 4 * header->num_vertexes); + } else { + iqmData->blendWeights.b = iqmData->blendIndexes + 4 * header->num_vertexes; + iqmData->colors = iqmData->blendWeights.b + 4 * header->num_vertexes; + } + + iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes); + iqmData->triangles = iqmData->jointParents + header->num_joints; + iqmData->names = (char *)(iqmData->triangles + 3 * header->num_triangles); + + if ( header->num_joints == 0 ) + iqmData->jointMats = NULL; + + if ( header->num_poses == 0 ) + iqmData->poseMats = NULL; + + // calculate joint matrices and their inverses + // joint inverses are needed only until the pose matrices are calculated + mat = iqmData->jointMats; + matInv = jointInvMats; + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + float baseFrame[12], invBaseFrame[12]; + + JointToMatrix( joint->rotate, joint->scale, joint->translate, baseFrame ); + Matrix34Invert( baseFrame, invBaseFrame ); + + if ( joint->parent >= 0 ) + { + Matrix34Multiply( iqmData->jointMats + 12 * joint->parent, baseFrame, mat ); + mat += 12; + Matrix34Multiply( invBaseFrame, jointInvMats + 12 * joint->parent, matInv ); + matInv += 12; + } + else + { + Com_Memcpy( mat, baseFrame, sizeof(baseFrame) ); + mat += 12; + Com_Memcpy( matInv, invBaseFrame, sizeof(invBaseFrame) ); + matInv += 12; + } + } + + // calculate pose matrices + framedata = (unsigned short *)((byte *)header + header->ofs_frames); + mat = iqmData->poseMats; + for( i = 0; i < header->num_frames; i++ ) { + pose = (iqmPose_t *)((byte *)header + header->ofs_poses); + for( j = 0; j < header->num_poses; j++, pose++ ) { + vec3_t translate; + vec4_t rotate; + vec3_t scale; + float mat1[12], mat2[12]; + + translate[0] = pose->channeloffset[0]; + if( pose->mask & 0x001) + translate[0] += *framedata++ * pose->channelscale[0]; + translate[1] = pose->channeloffset[1]; + if( pose->mask & 0x002) + translate[1] += *framedata++ * pose->channelscale[1]; + translate[2] = pose->channeloffset[2]; + if( pose->mask & 0x004) + translate[2] += *framedata++ * pose->channelscale[2]; + + rotate[0] = pose->channeloffset[3]; + if( pose->mask & 0x008) + rotate[0] += *framedata++ * pose->channelscale[3]; + rotate[1] = pose->channeloffset[4]; + if( pose->mask & 0x010) + rotate[1] += *framedata++ * pose->channelscale[4]; + rotate[2] = pose->channeloffset[5]; + if( pose->mask & 0x020) + rotate[2] += *framedata++ * pose->channelscale[5]; + rotate[3] = pose->channeloffset[6]; + if( pose->mask & 0x040) + rotate[3] += *framedata++ * pose->channelscale[6]; + + scale[0] = pose->channeloffset[7]; + if( pose->mask & 0x080) + scale[0] += *framedata++ * pose->channelscale[7]; + scale[1] = pose->channeloffset[8]; + if( pose->mask & 0x100) + scale[1] += *framedata++ * pose->channelscale[8]; + scale[2] = pose->channeloffset[9]; + if( pose->mask & 0x200) + scale[2] += *framedata++ * pose->channelscale[9]; + + // construct transformation matrix + JointToMatrix( rotate, scale, translate, mat1 ); + + if( pose->parent >= 0 ) { + Matrix34Multiply( iqmData->jointMats + 12 * pose->parent, + mat1, mat2 ); + } else { + Com_Memcpy( mat2, mat1, sizeof(mat1) ); + } + + Matrix34Multiply( mat2, jointInvMats + 12 * j, mat ); + mat += 12; + } + } + + // register shaders + // overwrite the material offset with the shader index + mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes); + surface = iqmData->surfaces; + str = (char *)header + header->ofs_text; + for( i = 0; i < header->num_meshes; i++, mesh++, surface++ ) { + surface->surfaceType = SF_IQM; + Q_strncpyz(surface->name, str + mesh->name, sizeof (surface->name)); + Q_strlwr(surface->name); // lowercase the surface name so skin compares are faster + surface->shader = R_FindShader( str + mesh->material, LIGHTMAP_NONE, true ); + if( surface->shader->defaultShader ) + surface->shader = tr.defaultShader; + surface->data = iqmData; + surface->first_vertex = mesh->first_vertex; + surface->num_vertexes = mesh->num_vertexes; + surface->first_triangle = mesh->first_triangle; + surface->num_triangles = mesh->num_triangles; + } + + // copy vertexarrays and indexes + vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays); + for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) { + int n; + + // total number of values + n = header->num_vertexes * vertexarray->size; + + switch( vertexarray->type ) { + case IQM_POSITION: + Com_Memcpy( iqmData->positions, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_NORMAL: + Com_Memcpy( iqmData->normals, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_TANGENT: + Com_Memcpy( iqmData->tangents, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_TEXCOORD: + Com_Memcpy( iqmData->texcoords, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_BLENDINDEXES: + if( blendIndexesType == IQM_INT ) { + int *data = (int*)((byte*)header + vertexarray->offset); + for ( j = 0; j < n; j++ ) { + iqmData->blendIndexes[j] = (byte)data[j]; + } + } else { + Com_Memcpy( iqmData->blendIndexes, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + } + break; + case IQM_BLENDWEIGHTS: + if( blendWeightsType == IQM_FLOAT ) { + Com_Memcpy( iqmData->blendWeights.f, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + } else { + Com_Memcpy( iqmData->blendWeights.b, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + } + break; + case IQM_COLOR: + Com_Memcpy( iqmData->colors, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + break; + } + } + + // copy joint parents + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + iqmData->jointParents[i] = joint->parent; + } + + // copy triangles + triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles); + for( i = 0; i < header->num_triangles; i++, triangle++ ) { + iqmData->triangles[3*i+0] = triangle->vertex[0]; + iqmData->triangles[3*i+1] = triangle->vertex[1]; + iqmData->triangles[3*i+2] = triangle->vertex[2]; + } + + // copy joint names + str = iqmData->names; + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + char *name = (char *)header + header->ofs_text + + joint->name; + int len = strlen( name ) + 1; + Com_Memcpy( str, name, len ); + str += len; + } + + // copy model bounds + if(header->ofs_bounds) + { + mat = iqmData->bounds; + bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds); + for(i = 0; i < header->num_frames; i++) + { + mat[0] = bounds->bbmin[0]; + mat[1] = bounds->bbmin[1]; + mat[2] = bounds->bbmin[2]; + mat[3] = bounds->bbmax[0]; + mat[4] = bounds->bbmax[1]; + mat[5] = bounds->bbmax[2]; + + mat += 6; + bounds++; + } + } + + return true; +} + +/* +============= +R_CullIQM +============= +*/ +static int R_CullIQM( iqmData_t *data, trRefEntity_t *ent ) { + vec3_t bounds[2]; + vec_t *oldBounds, *newBounds; + int i; + + if (!data->bounds) { + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + } + + // compute bounds pointers + oldBounds = data->bounds + 6*ent->e.oldframe; + newBounds = data->bounds + 6*ent->e.frame; + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldBounds[i] < newBounds[i] ? oldBounds[i] : newBounds[i]; + bounds[1][i] = oldBounds[i+3] > newBounds[i+3] ? oldBounds[i+3] : newBounds[i+3]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_ComputeIQMFogNum + +================= +*/ +int R_ComputeIQMFogNum( iqmData_t *data, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + const vec_t *bounds; + const vec_t defaultBounds[6] = { -8, -8, -8, 8, 8, 8 }; + vec3_t diag, center; + vec3_t localOrigin; + vec_t radius; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + if (data->bounds) { + bounds = data->bounds + 6*ent->e.frame; + } else { + bounds = defaultBounds; + } + VectorSubtract( bounds+3, bounds, diag ); + VectorMA( bounds, 0.5f, diag, center ); + VectorAdd( ent->e.origin, center, localOrigin ); + radius = 0.5f * VectorLength( diag ); + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddIQMSurfaces + +Add all surfaces of this model +================= +*/ +void R_AddIQMSurfaces( trRefEntity_t *ent ) { + iqmData_t *data; + srfIQModel_t *surface; + int i, j; + bool personalModel; + int cull; + int fogNum; + shader_t *shader; + skin_t *skin; + + data = (iqmData_t*)tr.currentModel->modelData; + surface = data->surfaces; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= data->num_frames; + ent->e.oldframe %= data->num_frames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= data->num_frames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= data->num_frames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddIQMSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullIQM ( data, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeIQMFogNum( data, ent ); + + for ( i = 0 ; i < data->num_surfaces ; i++ ) { + if(ent->e.customShader) + shader = R_GetShaderByHandle( ent->e.customShader ); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j].name, surface->name)) + { + shader = skin->surfaces[j].shader; + break; + } + } + } else { + shader = surface->shader; + } + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t*)surface, tr.shadowShader, 0, 0 ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t*)surface, tr.projectionShadowShader, 0, 0 ); + } + + if( !personalModel ) { + R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, 0 ); + } + + surface++; + } +} + + +static void ComputePoseMats( iqmData_t *data, int frame, int oldframe, + float backlerp, float *mat ) { + float *mat1, *mat2; + int *joint = data->jointParents; + int i; + + if ( data->num_poses == 0 ) { + for( i = 0; i < data->num_joints; i++, joint++ ) { + if( *joint >= 0 ) { + Matrix34Multiply( mat + 12 * *joint, + identityMatrix, mat + 12*i ); + } else { + Com_Memcpy( mat + 12*i, identityMatrix, 12 * sizeof(float) ); + } + } + return; + } + + if ( oldframe == frame ) { + mat1 = data->poseMats + 12 * data->num_poses * frame; + for( i = 0; i < data->num_poses; i++, joint++ ) { + if( *joint >= 0 ) { + Matrix34Multiply( mat + 12 * *joint, + mat1 + 12*i, mat + 12*i ); + } else { + Com_Memcpy( mat + 12*i, mat1 + 12*i, 12 * sizeof(float) ); + } + } + } else { + mat1 = data->poseMats + 12 * data->num_poses * frame; + mat2 = data->poseMats + 12 * data->num_poses * oldframe; + + for( i = 0; i < data->num_poses; i++, joint++ ) { + if( *joint >= 0 ) { + float tmpMat[12]; + InterpolateMatrix( mat1 + 12*i, mat2 + 12*i, + backlerp, tmpMat ); + Matrix34Multiply( mat + 12 * *joint, + tmpMat, mat + 12*i ); + + } else { + InterpolateMatrix( mat1 + 12*i, mat2 + 12*i, + backlerp, mat ); + } + } + } +} + +static void ComputeJointMats( iqmData_t *data, int frame, int oldframe, + float backlerp, float *mat ) { + float *mat1; + int i; + + ComputePoseMats( data, frame, oldframe, backlerp, mat ); + + for( i = 0; i < data->num_joints; i++ ) { + float outmat[12]; + mat1 = mat + 12 * i; + + Com_Memcpy(outmat, mat1, sizeof(outmat)); + + Matrix34Multiply_OnlySetOrigin( outmat, data->jointMats + 12 * i, mat1 ); + } +} + + +/* +================= +RB_AddIQMSurfaces + +Compute vertices for this model surface +================= +*/ +void RB_IQMSurfaceAnim( surfaceType_t *surface ) { + srfIQModel_t *surf = (srfIQModel_t *)surface; + iqmData_t *data = surf->data; + float jointMats[IQM_MAX_JOINTS * 12]; + int i; + + vec4_t *outXYZ; + vec4_t *outNormal; + vec2_t (*outTexCoord)[2]; + color4ub_t *outColor; + + int frame = data->num_frames ? backEnd.currentEntity->e.frame % data->num_frames : 0; + int oldframe = data->num_frames ? backEnd.currentEntity->e.oldframe % data->num_frames : 0; + float backlerp = backEnd.currentEntity->e.backlerp; + + int *tri; + glIndex_t *ptr; + glIndex_t base; + + RB_CHECKOVERFLOW( surf->num_vertexes, surf->num_triangles * 3 ); + + outXYZ = &tess.xyz[tess.numVertexes]; + outNormal = &tess.normal[tess.numVertexes]; + outTexCoord = &tess.texCoords[tess.numVertexes]; + outColor = &tess.vertexColors[tess.numVertexes]; + + // compute interpolated joint matrices + if ( data->num_poses > 0 ) { + ComputePoseMats( data, frame, oldframe, backlerp, jointMats ); + } + + // transform vertexes and fill other data + for( i = 0; i < surf->num_vertexes; + i++, outXYZ++, outNormal++, outTexCoord++, outColor++ ) { + int j, k; + float vtxMat[12]; + float nrmMat[9]; + int vtx = i + surf->first_vertex; + float blendWeights[4]; + int numWeights; + + for ( numWeights = 0; numWeights < 4; numWeights++ ) { + if ( data->blendWeightsType == IQM_FLOAT ) + blendWeights[numWeights] = data->blendWeights.f[4*vtx + numWeights]; + else + blendWeights[numWeights] = (float)data->blendWeights.b[4*vtx + numWeights] / 255.0f; + + if ( blendWeights[numWeights] <= 0 ) + break; + } + + if ( data->num_poses == 0 || numWeights == 0 ) { + // no blend joint, use identity matrix. + Com_Memcpy( vtxMat, identityMatrix, 12 * sizeof (float) ); + } else { + // compute the vertex matrix by blending the up to + // four blend weights + Com_Memset( vtxMat, 0, 12 * sizeof (float) ); + for( j = 0; j < numWeights; j++ ) { + for( k = 0; k < 12; k++ ) { + vtxMat[k] += blendWeights[j] * jointMats[12*data->blendIndexes[4*vtx + j] + k]; + } + } + } + + // compute the normal matrix as transpose of the adjoint + // of the vertex matrix + nrmMat[ 0] = vtxMat[ 5]*vtxMat[10] - vtxMat[ 6]*vtxMat[ 9]; + nrmMat[ 1] = vtxMat[ 6]*vtxMat[ 8] - vtxMat[ 4]*vtxMat[10]; + nrmMat[ 2] = vtxMat[ 4]*vtxMat[ 9] - vtxMat[ 5]*vtxMat[ 8]; + nrmMat[ 3] = vtxMat[ 2]*vtxMat[ 9] - vtxMat[ 1]*vtxMat[10]; + nrmMat[ 4] = vtxMat[ 0]*vtxMat[10] - vtxMat[ 2]*vtxMat[ 8]; + nrmMat[ 5] = vtxMat[ 1]*vtxMat[ 8] - vtxMat[ 0]*vtxMat[ 9]; + nrmMat[ 6] = vtxMat[ 1]*vtxMat[ 6] - vtxMat[ 2]*vtxMat[ 5]; + nrmMat[ 7] = vtxMat[ 2]*vtxMat[ 4] - vtxMat[ 0]*vtxMat[ 6]; + nrmMat[ 8] = vtxMat[ 0]*vtxMat[ 5] - vtxMat[ 1]*vtxMat[ 4]; + + (*outTexCoord)[0][0] = data->texcoords[2*vtx + 0]; + (*outTexCoord)[0][1] = data->texcoords[2*vtx + 1]; + (*outTexCoord)[1][0] = (*outTexCoord)[0][0]; + (*outTexCoord)[1][1] = (*outTexCoord)[0][1]; + + (*outXYZ)[0] = + vtxMat[ 0] * data->positions[3*vtx+0] + + vtxMat[ 1] * data->positions[3*vtx+1] + + vtxMat[ 2] * data->positions[3*vtx+2] + + vtxMat[ 3]; + (*outXYZ)[1] = + vtxMat[ 4] * data->positions[3*vtx+0] + + vtxMat[ 5] * data->positions[3*vtx+1] + + vtxMat[ 6] * data->positions[3*vtx+2] + + vtxMat[ 7]; + (*outXYZ)[2] = + vtxMat[ 8] * data->positions[3*vtx+0] + + vtxMat[ 9] * data->positions[3*vtx+1] + + vtxMat[10] * data->positions[3*vtx+2] + + vtxMat[11]; + (*outXYZ)[3] = 1.0f; + + (*outNormal)[0] = + nrmMat[ 0] * data->normals[3*vtx+0] + + nrmMat[ 1] * data->normals[3*vtx+1] + + nrmMat[ 2] * data->normals[3*vtx+2]; + (*outNormal)[1] = + nrmMat[ 3] * data->normals[3*vtx+0] + + nrmMat[ 4] * data->normals[3*vtx+1] + + nrmMat[ 5] * data->normals[3*vtx+2]; + (*outNormal)[2] = + nrmMat[ 6] * data->normals[3*vtx+0] + + nrmMat[ 7] * data->normals[3*vtx+1] + + nrmMat[ 8] * data->normals[3*vtx+2]; + (*outNormal)[3] = 0.0f; + + (*outColor)[0] = data->colors[4*vtx+0]; + (*outColor)[1] = data->colors[4*vtx+1]; + (*outColor)[2] = data->colors[4*vtx+2]; + (*outColor)[3] = data->colors[4*vtx+3]; + } + + tri = data->triangles + 3 * surf->first_triangle; + ptr = &tess.indexes[tess.numIndexes]; + base = tess.numVertexes; + + for( i = 0; i < surf->num_triangles; i++ ) { + *ptr++ = base + (*tri++ - surf->first_vertex); + *ptr++ = base + (*tri++ - surf->first_vertex); + *ptr++ = base + (*tri++ - surf->first_vertex); + } + + tess.numIndexes += 3 * surf->num_triangles; + tess.numVertexes += surf->num_vertexes; +} + +int R_IQMLerpTag( orientation_t *tag, iqmData_t *data, + int startFrame, int endFrame, + float frac, const char *tagName ) { + float jointMats[IQM_MAX_JOINTS * 12]; + int joint; + char *names = data->names; + + // get joint number by reading the joint names + for( joint = 0; joint < data->num_joints; joint++ ) { + if( !strcmp( tagName, names ) ) + break; + names += strlen( names ) + 1; + } + if( joint >= data->num_joints ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return false; + } + + ComputeJointMats( data, startFrame, endFrame, frac, jointMats ); + + tag->axis[0][0] = jointMats[12 * joint + 0]; + tag->axis[1][0] = jointMats[12 * joint + 1]; + tag->axis[2][0] = jointMats[12 * joint + 2]; + tag->origin[0] = jointMats[12 * joint + 3]; + tag->axis[0][1] = jointMats[12 * joint + 4]; + tag->axis[1][1] = jointMats[12 * joint + 5]; + tag->axis[2][1] = jointMats[12 * joint + 6]; + tag->origin[1] = jointMats[12 * joint + 7]; + tag->axis[0][2] = jointMats[12 * joint + 8]; + tag->axis[1][2] = jointMats[12 * joint + 9]; + tag->axis[2][2] = jointMats[12 * joint + 10]; + tag->origin[2] = jointMats[12 * joint + 11]; + + return true; +} diff --git a/src/renderergl1/tr_scene.cpp b/src/renderergl1/tr_scene.cpp new file mode 100644 index 0000000..a6ed132 --- /dev/null +++ b/src/renderergl1/tr_scene.cpp @@ -0,0 +1,413 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +int r_numentities; +int r_firstSceneEntity; + +int r_numpolys; +int r_firstScenePoly; + +int r_numpolyverts; + + +/* +==================== +R_InitNextFrame + +==================== +*/ +void R_InitNextFrame( void ) { + backEndData->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + + r_numentities = 0; + r_firstSceneEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; + + tr.currentEntityNum = REFENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( (surfaceType_t*)poly, sh, poly->fogIndex, false ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + srfPoly_t *poly; + int i, j; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n"); + return; + } + + for ( j = 0; j < numPolys; j++ ) { + if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) { + /* + NOTE TTimo this was initially a PRINT_WARNING + but it happens a lot with high fighting scenes and particles + since we don't plan on changing the const and making for room for those effects + simply cut this message to developer only + */ + ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n"); + return; + } + + poly = &backEndData->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData->polyVerts[r_numpolyverts]; + + Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) ); + + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + poly->verts->modulate[0] = 255; + poly->verts->modulate[1] = 255; + poly->verts->modulate[2] = 255; + poly->verts->modulate[3] = 255; + } + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // if no world is loaded + if ( tr.world == NULL ) { + fogIndex = 0; + } + // see if it is in a fog volume + else if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; + } +} + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { + if ( !tr.registered ) { + return; + } + if ( r_numentities >= MAX_REFENTITIES ) { + ri.Printf(PRINT_DEVELOPER, "RE_AddRefEntityToScene: Dropping refEntity, reached MAX_REFENTITIES\n"); + return; + } + if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) { + static bool firstTime = true; + if (firstTime) { + firstTime = false; + ri.Printf( PRINT_WARNING, "RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n"); + } + return; + } + if ( (int)ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData->entities[r_numentities].e = *ent; + backEndData->entities[r_numentities].lightingCalculated = false; + + r_numentities++; +} + + +/* +===================== +RE_AddDynamicLightToScene + +===================== +*/ +void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) { + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + // these cards don't have the correct blend mode + if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + dl = &backEndData->dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; + dl->additive = additive; +} + +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, false ); +} + +/* +===================== +RE_AddAdditiveLightToScene + +===================== +*/ +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, true ); +} + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = ri.Milliseconds(); + + if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel"); + } + + Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.rdflags = fd->rdflags; + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = false; + if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) { + areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i]; + ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = true; + } + } + + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData->entities[r_firstSceneEntity]; + + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData->dlights[r_firstSceneDlight]; + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData->polys[r_firstScenePoly]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled + if ( r_dynamiclight->integer == 0 || + r_vertexLight->integer == 1 || + glConfig.hardwareType == GLHW_PERMEDIA2 ) { + tr.refdef.num_dlights = 0; + } + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + Com_Memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = false; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + parms.stereoFrame = tr.refdef.stereoFrame; + + VectorCopy( fd->vieworg, parms.orientation.origin ); + VectorCopy( fd->viewaxis[0], parms.orientation.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.orientation.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.orientation.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + R_RenderView( &parms ); + + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; + r_firstSceneEntity = r_numentities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; + + tr.frontEndMsec += ri.Milliseconds() - startTime; +} diff --git a/src/renderergl1/tr_shade.cpp b/src/renderergl1/tr_shade.cpp new file mode 100644 index 0000000..a4139eb --- /dev/null +++ b/src/renderergl1/tr_shade.cpp @@ -0,0 +1,1522 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_shade.c + +#include "tr_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + +/* +================ +R_ArrayElementDiscrete + +This is just for OpenGL conformance testing, it should never be the fastest +================ +*/ +static void APIENTRY R_ArrayElementDiscrete( GLint index ) { + qglColor4ubv( tess.svars.colors[ index ] ); + if ( glState.currenttmu ) { + qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] ); + qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] ); + } else { + qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] ); + } + qglVertex3fv( tess.xyz[ index ] ); +} + +/* +=================== +R_DrawStripElements + +=================== +*/ +static int c_vertexes; // for seeing how long our average strips are +static int c_begins; +static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) { + int i; + int last[3] = { -1, -1, -1 }; + bool even; + + c_begins++; + + if ( numIndexes <= 0 ) { + return; + } + + qglBegin( GL_TRIANGLE_STRIP ); + + // prime the strip + element( indexes[0] ); + element( indexes[1] ); + element( indexes[2] ); + c_vertexes += 3; + + last[0] = indexes[0]; + last[1] = indexes[1]; + last[2] = indexes[2]; + + even = false; + + for ( i = 3; i < numIndexes; i += 3 ) + { + // odd numbered triangle in potential strip + if ( !even ) + { + // check previous triangle to see if we're continuing a strip + if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + assert( indexes[i+2] < tess.numVertexes ); + even = true; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + + c_vertexes += 3; + + even = false; + } + } + else + { + // check previous triangle to see if we're continuing a strip + if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + + even = false; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + c_vertexes += 3; + + even = false; + } + } + + // cache the last three vertices + last[0] = indexes[i+0]; + last[1] = indexes[i+1]; + last[2] = indexes[i+2]; + } + + qglEnd(); +} + + + +/* +================== +R_DrawElements + +Optionally performs our own glDrawElements that looks for strip conditions +instead of using the single glDrawElements call that may be inefficient +without compiled vertex arrays. +================== +*/ +static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { + int primitives; + + primitives = r_primitives->integer; + + // default is to use triangles if compiled vertex arrays are present + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + + + if ( primitives == 2 ) { + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); + return; + } + + if ( primitives == 1 ) { + R_DrawStripElements( numIndexes, indexes, qglArrayElement ); + return; + } + + if ( primitives == 3 ) { + R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete ); + return; + } + + // anything else will cause no drawing +} + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + +shaderCommands_t tess; +static bool setArraysOnce; + +/* +================= +R_BindAnimatedImage + +================= +*/ +static void R_BindAnimatedImage( textureBundle_t *bundle ) { + + if ( bundle->isVideoMap ) { + ri.CIN_RunCinematic(bundle->videoMapHandle); + ri.CIN_UploadCinematic(bundle->videoMapHandle); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + GL_Bind( bundle->image[0] ); + return; + } + + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + int i = static_cast(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE) >> FUNCTABLE_SIZE2; + if ( i < 0 ) + { + i = 0; // may happen with shader time offsets + } + i %= bundle->numImageAnimations; + + GL_Bind( bundle->image[ i ] ); +} + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris (shaderCommands_t *input) { + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { + int i; + vec3_t temp; + + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + qglDepthRange( 0, 0 ); // never occluded + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + + qglBegin (GL_LINES); + for (i = 0 ; i < input->numVertexes ; i++) { + qglVertex3fv (input->xyz[i]); + VectorMA (input->xyz[i], 2, input->normal[i], temp); + qglVertex3fv (temp); + } + qglEnd (); + + qglDepthRange( 0, 1 ); +} + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum ) { + + shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader; + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.shader = state; + tess.fogNum = fogNum; + tess.dlightBits = 0; // will be OR'd in by surface functions + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + tess.currentStageIteratorFunc = state->optimalStageIteratorFunc; + + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) { + tess.shaderTime = tess.shader->clampTime; + } + + +} + +/* +=================== +DrawMultitextured + +output = t0 * t1 or t0 + t1 + +t0 = most upstream according to spec +t1 = most downstream according to spec +=================== +*/ +static void DrawMultitextured( shaderCommands_t *input, int stage ) { + shaderStage_t *pStage; + + pStage = tess.xstages[stage]; + + GL_State( pStage->stateBits ); + + // this is an ugly hack to work around a GeForce driver + // bug with multitexture and clip planes + if ( backEnd.viewParms.isPortal ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + + // + // base + // + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + + // + // lightmap/secondary pass + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( tess.shader->multitextureEnv ); + } + + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] ); + + R_BindAnimatedImage( &pStage->bundle[1] ); + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + //qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + qglDisable( GL_TEXTURE_2D ); + + GL_SelectTexture( 0 ); +} + + + +/* +=================== +ProjectDlightTexture + +Perform dynamic lighting with another rendering pass +=================== +*/ +#if idppc_altivec +static void ProjectDlightTexture_altivec( void ) { + int i, l; + vec_t origin0, origin1, origin2; + float texCoords0, texCoords1; + vector float floatColorVec0, floatColorVec1; + vector float modulateVec, colorVec, zero; + vector short colorShort; + vector signed int colorInt; + vector unsigned char floatColorVecPerm, modulatePerm, colorChar; + vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff); + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + vec3_t floatColor; + float modulate = 0.0f; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + // There has to be a better way to do this so that floatColor + // and/or modulate are already 16-byte aligned. + floatColorVecPerm = vec_lvsl(0,(float *)floatColor); + modulatePerm = vec_lvsl(0,(float *)&modulate); + modulatePerm = (vector unsigned char)vec_splat((vector unsigned int)modulatePerm,0); + zero = (vector float)vec_splat_s8(0); + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + origin0 = dl->transformed[0]; + origin1 = dl->transformed[1]; + origin2 = dl->transformed[2]; + radius = dl->radius; + scale = 1.0f / radius; + + if(r_greyscale->integer) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = floatColor[1] = floatColor[2] = luminance; + } + else if(r_greyscale->value) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = LERP(dl->color[0] * 255.0f, luminance, r_greyscale->value); + floatColor[1] = LERP(dl->color[1] * 255.0f, luminance, r_greyscale->value); + floatColor[2] = LERP(dl->color[2] * 255.0f, luminance, r_greyscale->value); + } + else + { + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + } + floatColorVec0 = vec_ld(0, floatColor); + floatColorVec1 = vec_ld(11, floatColor); + floatColorVec0 = vec_perm(floatColorVec0,floatColorVec0,floatColorVecPerm); + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + int clip = 0; + vec_t dist0, dist1, dist2; + + dist0 = origin0 - tess.xyz[i][0]; + dist1 = origin1 - tess.xyz[i][1]; + dist2 = origin2 - tess.xyz[i][2]; + + backEnd.pc.c_dlightVertexes++; + + texCoords0 = 0.5f + dist0 * scale; + texCoords1 = 0.5f + dist1 * scale; + + if( !r_dlightBacks->integer && + // dist . tess.normal[i] + ( dist0 * tess.normal[i][0] + + dist1 * tess.normal[i][1] + + dist2 * tess.normal[i][2] ) < 0.0f ) { + clip = 63; + } else { + if ( texCoords0 < 0.0f ) { + clip |= 1; + } else if ( texCoords0 > 1.0f ) { + clip |= 2; + } + if ( texCoords1 < 0.0f ) { + clip |= 4; + } else if ( texCoords1 > 1.0f ) { + clip |= 8; + } + texCoords[0] = texCoords0; + texCoords[1] = texCoords1; + + // modulate the strength based on the height and color + if ( dist2 > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist2 < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist2 = Q_fabs(dist2); + if ( dist2 < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * (radius - dist2) * scale; + } + } + } + clipBits[i] = clip; + + modulateVec = vec_ld(0,(float *)&modulate); + modulateVec = vec_perm(modulateVec,modulateVec,modulatePerm); + colorVec = vec_madd(floatColorVec0,modulateVec,zero); + colorInt = vec_cts(colorVec,0); // RGBx + colorShort = vec_pack(colorInt,colorInt); // RGBxRGBx + colorChar = vec_packsu(colorShort,colorShort); // RGBxRGBxRGBxRGBx + colorChar = vec_sel(colorChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255 + vec_ste((vector unsigned int)colorChar,0,(unsigned int *)colors); // store color + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} +#endif + + +static void ProjectDlightTexture_scalar( void ) { + int i, l; + vec3_t origin; + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + vec3_t floatColor; + float modulate = 0.0f; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + + if(r_greyscale->integer) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = floatColor[1] = floatColor[2] = luminance; + } + else if(r_greyscale->value) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = LERP(dl->color[0] * 255.0f, luminance, r_greyscale->value); + floatColor[1] = LERP(dl->color[1] * 255.0f, luminance, r_greyscale->value); + floatColor[2] = LERP(dl->color[2] * 255.0f, luminance, r_greyscale->value); + } + else + { + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + } + + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + int clip = 0; + vec3_t dist; + + VectorSubtract( origin, tess.xyz[i], dist ); + + backEnd.pc.c_dlightVertexes++; + + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[1] * scale; + + if( !r_dlightBacks->integer && + // dist . tess.normal[i] + ( dist[0] * tess.normal[i][0] + + dist[1] * tess.normal[i][1] + + dist[2] * tess.normal[i][2] ) < 0.0f ) { + clip = 63; + } else { + if ( texCoords[0] < 0.0f ) { + clip |= 1; + } else if ( texCoords[0] > 1.0f ) { + clip |= 2; + } + if ( texCoords[1] < 0.0f ) { + clip |= 4; + } else if ( texCoords[1] > 1.0f ) { + clip |= 8; + } + texCoords[0] = texCoords[0]; + texCoords[1] = texCoords[1]; + + // modulate the strength based on the height and color + if ( dist[2] > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist[2] < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist[2] = Q_fabs(dist[2]); + if ( dist[2] < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * (radius - dist[2]) * scale; + } + } + } + clipBits[i] = clip; + colors[0] = static_cast(floatColor[0] * modulate); + colors[1] = static_cast(floatColor[1] * modulate); + colors[2] = static_cast(floatColor[2] * modulate); + colors[3] = 255; + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} + +static void ProjectDlightTexture( void ) { +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + ProjectDlightTexture_altivec(); + return; + } +#endif + ProjectDlightTexture_scalar(); +} + + +/* +=================== +RB_FogPass + +Blends a fog texture on top of everything else +=================== +*/ +static void RB_FogPass( void ) { + fog_t *fog; + int i; + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] ); + + GL_Bind( tr.fogImage ); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + + R_DrawElements( tess.numIndexes, tess.indexes ); +} + +/* +=============== +ComputeColors +=============== +*/ +static void ComputeColors( shaderStage_t *pStage ) +{ + int i; + + // + // rgbGen + // + switch ( pStage->rgbGen ) + { + case CGEN_IDENTITY: + Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 ); + break; + case CGEN_LIGHTING_DIFFUSE: + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_EXACT_VERTEX: + Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor; + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) + { + Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; + tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; + tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0]; + tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1]; + tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2]; + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight; + tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight; + tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight; + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + } + + // + // alphaGen + // + switch ( pStage->alphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( pStage->rgbGen != CGEN_IDENTITY ) { + if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) || + pStage->rgbGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = 0xff; + } + } + } + break; + case AGEN_CONST: + if ( pStage->rgbGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = pStage->constantColor[3]; + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ENTITY: + RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_VERTEX: + if ( pStage->rgbGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3]; + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.orientation.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) + { + alpha = 0; + } + else if ( len > 1 ) + { + alpha = 0xff; + } + else + { + alpha = len * 0xff; + } + + tess.svars.colors[i][3] = alpha; + } + } + break; + } + + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) + { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } + + // if in greyscale rendering mode turn all color values into greyscale. + if(r_greyscale->integer) + { + int scale; + for(i = 0; i < tess.numVertexes; i++) + { + scale = LUMA(tess.svars.colors[i][0], tess.svars.colors[i][1], tess.svars.colors[i][2]); + tess.svars.colors[i][0] = tess.svars.colors[i][1] = tess.svars.colors[i][2] = scale; + } + } + else if(r_greyscale->value) + { + float scale; + + for(i = 0; i < tess.numVertexes; i++) + { + scale = LUMA(tess.svars.colors[i][0], tess.svars.colors[i][1], tess.svars.colors[i][2]); + tess.svars.colors[i][0] = LERP(tess.svars.colors[i][0], scale, r_greyscale->value); + tess.svars.colors[i][1] = LERP(tess.svars.colors[i][1], scale, r_greyscale->value); + tess.svars.colors[i][2] = LERP(tess.svars.colors[i][2], scale, r_greyscale->value); + } + } +} + +/* +=============== +ComputeTexCoords +=============== +*/ +static void ComputeTexCoords( shaderStage_t *pStage ) { + int i; + int b; + + for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) { + int tm; + + // + // generate the texture coordinates + // + switch ( pStage->bundle[b].tcGen ) + { + case TCGEN_IDENTITY: + Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes ); + break; + case TCGEN_TEXTURE: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1]; + } + break; + case TCGEN_LIGHTMAP: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1]; + } + break; + case TCGEN_VECTOR: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] ); + tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] ); + } + break; + case TCGEN_FOG: + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_ENVIRONMENT_MAPPED: + RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_BAD: + return; + } + + // + // alter texture coordinates + // + for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) { + switch ( pStage->bundle[b].texMods[tm].type ) + { + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCALE: + RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm], + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed, + ( float * ) tess.svars.texcoords[b] ); + break; + + default: + ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'", pStage->bundle[b].texMods[tm].type, tess.shader->name ); + break; + } + } + } +} + +/* +** RB_IterateStagesGeneric +*/ +static void RB_IterateStagesGeneric( shaderCommands_t *input ) +{ + int stage; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = tess.xstages[stage]; + + if ( !pStage ) + { + break; + } + + ComputeColors( pStage ); + ComputeTexCoords( pStage ); + + if ( !setArraysOnce ) + { + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors ); + } + + // + // do multitexture + // + if ( pStage->bundle[1].image[0] != 0 ) + { + DrawMultitextured( input, stage ); + } + else + { + if ( !setArraysOnce ) + { + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + } + + // + // set state + // + R_BindAnimatedImage( &pStage->bundle[0] ); + + GL_State( pStage->stateBits ); + + // + // draw + // + R_DrawElements( input->numIndexes, input->indexes ); + } + // allow skipping out to show just lightmaps during development + if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap ) ) + { + break; + } + } +} + + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) +{ + shaderCommands_t *input; + shader_t *shader; + + input = &tess; + shader = input->shader; + + RB_DeformTessGeometry(); + + // + // log this call + // + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + char *msg = (char*)va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name); + GLimp_LogComment( msg ); + } + + // + // set face culling appropriately + // + GL_Cull( shader->cullType ); + + // set polygon offset if necessary + if ( shader->polygonOffset ) + { + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + } + + // + // if there is only a single pass then we can enable color + // and texture arrays before we compile, otherwise we need + // to avoid compiling those arrays since they will change + // during multipass rendering + // + if ( tess.numPasses > 1 || shader->multitextureEnv ) + { + setArraysOnce = false; + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + } + else + { + setArraysOnce = true; + + qglEnableClientState( GL_COLOR_ARRAY); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + } + + // + // lock XYZ + // + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + if (qglLockArraysEXT) + { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // enable color and texcoord arrays after the lock if necessary + // + if ( !setArraysOnce ) + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_COLOR_ARRAY ); + } + + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + // + // reset polygon offset + // + if ( shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } +} + + +/* +** RB_StageIteratorVertexLitTexture +*/ +void RB_StageIteratorVertexLitTexture( void ) +{ + shaderCommands_t *input; + shader_t *shader; + + input = &tess; + shader = input->shader; + + // + // compute colors + // + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + + // + // log this call + // + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( (char*)va("--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + GL_Cull( shader->cullType ); + + // + // set arrays and lock + // + qglEnableClientState( GL_COLOR_ARRAY); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); + + if ( qglLockArraysEXT ) + { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // call special shade routine + // + R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); + GL_State( tess.xstages[0]->stateBits ); + R_DrawElements( input->numIndexes, input->indexes ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } +} + +//define REPLACE_MODE + +void RB_StageIteratorLightmappedMultitexture( void ) { + shaderCommands_t *input; + shader_t *shader; + + input = &tess; + shader = input->shader; + + // + // log this call + // + if ( r_logFile->integer ) { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( (char*)va("--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + GL_Cull( shader->cullType ); + + // + // set color, pointers, and lock + // + GL_State( GLS_DEFAULT ); + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); + +#ifdef REPLACE_MODE + qglDisableClientState( GL_COLOR_ARRAY ); + qglColor3f( 1, 1, 1 ); + qglShadeModel( GL_FLAT ); +#else + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 ); +#endif + + // + // select base stage + // + GL_SelectTexture( 0 ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); + + // + // configure second stage + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( GL_MODULATE ); + } + R_BindAnimatedImage( &tess.xstages[0]->bundle[1] ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] ); + + // + // lock arrays + // + if ( qglLockArraysEXT ) { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + qglDisable( GL_TEXTURE_2D ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + GL_SelectTexture( 0 ); +#ifdef REPLACE_MODE + GL_TexEnv( GL_MODULATE ); + qglShadeModel( GL_SMOOTH ); +#endif + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if ( qglUnlockArraysEXT ) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } +} + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if (input->numIndexes == 0) { + return; + } + + if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); + } + if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); + } + + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + // + // update performance counters + // + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + + // + // draw debugging stuff + // + if ( r_showtris->integer ) { + DrawTris (input); + } + if ( r_shownormals->integer ) { + DrawNormals (input); + } + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + + GLimp_LogComment( "----------\n" ); +} diff --git a/src/renderergl1/tr_shade_calc.cpp b/src/renderergl1/tr_shade_calc.cpp new file mode 100644 index 0000000..6ce47c8 --- /dev/null +++ b/src/renderergl1/tr_shade_calc.cpp @@ -0,0 +1,1212 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_shade_calc.c + +#include "tr_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif + + +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ static_cast( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) + +static float *TableForFunc( genFunc_t func ) +{ + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +static float EvalWaveForm( const waveForm_t *wf ) +{ + float *table; + + table = TableForFunc( wf->func ); + + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) +{ + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) + { + return 0; + } + + if ( glow > 1 ) + { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexCoords +*/ +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st ) +{ + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) +{ + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float *table; + + if ( ds->deformationWave.frequency == 0 ) + { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } + else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( normal ); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) { + int i; + const float *st = ( const float * ) tess.texCoords[0]; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + double now = backEnd.refdef.time * 0.001 * ds->bulgeSpeed; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) { + int64_t off; + float scale; + + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + byte color[4]; + float bottom, top; + vec3_t mid; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + CrossProduct( tess.normal[0], height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = 999999; + top = -999999; + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, (len-1), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + + color[0] = color[1] = color[2] = color[3] = 255; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625f; + fcol = col*0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.orientation.axis[0] ); + out[1] = DotProduct( in, backEnd.orientation.axis[1] ); + out[2] = DotProduct( in, backEnd.orientation.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count\n", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count\n", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.orientation.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.orientation.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.orientation.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.orientation.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i+=4 ) { + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); + mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); + mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + // compensate for scale in the axes if necessary + if ( backEnd.currentEntity->e.nonNormalizedAxes ) { + float axisLength; + axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + VectorScale(left, axisLength, left); + VectorScale(up, axisLength, up); + } + + RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +int edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.orientation.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.orientation.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * (v1[0] + v2[0]); + mid[j][1] = 0.5f * (v1[1] + v2[1]); + mid[j][2] = 0.5f * (v1[2] + v2[2]); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5 * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = &tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: + RB_ProjectionShadowDeform(); + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + break; + } + } +} + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcColorFromEntity +*/ +void RB_CalcColorFromEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + int c; + + if ( !backEnd.currentEntity ) + return; + + c = * ( int * ) backEnd.currentEntity->e.shaderRGBA; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} + +/* +** RB_CalcColorFromOneMinusEntity +*/ +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + unsigned char invModulate[4]; + int c; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + c = * ( int * ) invModulate; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} + +/* +** RB_CalcAlphaFromEntity +*/ +void RB_CalcAlphaFromEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcAlphaFromOneMinusEntity +*/ +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcWaveColor +*/ +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + int *colors = ( int * ) dstColors; + byte color[4]; + + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + v = static_cast(255 * glow); + color[0] = color[1] = color[2] = v; + color[3] = 255; + v = *(int *)color; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = v; + } +} + +/* +** RB_CalcWaveAlpha +*/ +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + dstColors[3] = v; + } +} + +/* +** RB_CalcModulateColorsByFog +*/ +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} + +/* +** RB_CalcModulateAlphasByFog +*/ +void RB_CalcModulateAlphasByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[3] *= f; + } +} + +/* +** RB_CalcModulateRGBAsByFog +*/ +void RB_CalcModulateRGBAsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2] = {{0.0f}}; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + colors[3] *= f; + } +} + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + bool eyeOutside; + fog_t *fog; + vec3_t local; + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.orientation.origin, backEnd.viewParms.orientation.origin, local ); + fogDistanceVector[0] = -backEnd.orientation.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.orientation.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.orientation.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.orientation.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.orientation.axis[0][0] + + fog->surface[1] * backEnd.orientation.axis[0][1] + fog->surface[2] * backEnd.orientation.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.orientation.axis[1][0] + + fog->surface[1] * backEnd.orientation.axis[1][1] + fog->surface[2] * backEnd.orientation.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.orientation.axis[2][0] + + fog->surface[1] * backEnd.orientation.axis[2][1] + fog->surface[2] * backEnd.orientation.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.orientation.origin, fog->surface ); + + eyeT = DotProduct( backEnd.orientation.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = true; + } else { + eyeOutside = false; + } + + fogDistanceVector[3] += 1.0/512; + + // calculate density for each point + for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 31.0/32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + + + +/* +** RB_CalcEnvironmentTexCoords +*/ +void RB_CalcEnvironmentTexCoords( float *st ) +{ + int i; + float *v, *normal; + vec3_t viewer, reflected; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorSubtract (backEnd.orientation.viewOrigin, v, viewer); + VectorNormalizeFast (viewer); + + d = DotProduct (normal, viewer); + + reflected[0] = normal[0]*2*d - viewer[0]; + reflected[1] = normal[1]*2*d - viewer[1]; + reflected[2] = normal[2]*2*d - viewer[2]; + + st[0] = 0.5 + reflected[1] * 0.5; + st[1] = 0.5 - reflected[2] * 0.5; + } +} + +/* +** RB_CalcTurbulentTexCoords +*/ +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) +{ + int i; + double now = ( wf->phase + tess.shaderTime * wf->frequency ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s + tr.sinTable[ ( static_cast( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( static_cast( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + } +} + +/* +** RB_CalcScaleTexCoords +*/ +void RB_CalcScaleTexCoords( const float scale[2], float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] *= scale[0]; + st[1] *= scale[1]; + } +} + +/* +** RB_CalcScrollTexCoords +*/ +void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) +{ + int i; + double timeScale = tess.shaderTime; + double adjustedScrollS = scrollSpeed[0] * timeScale; + double adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] += adjustedScrollS; + st[1] += adjustedScrollT; + } +} + +/* +** RB_CalcTransformTexCoords +*/ +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0]; + st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1]; + } +} + +/* +** RB_CalcRotateTexCoords +*/ +void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) +{ + double timeScale = tess.shaderTime; + double degs; + int i; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + i = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ i & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( i + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexCoords( &tmi, st ); +} + + +/* +** RB_CalcSpecularAlpha +** +** Calculates specular coefficient and places it in the alpha channel +*/ +vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically + +void RB_CalcSpecularAlpha( unsigned char *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int b; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + alphas += 3; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) { + float ilength; + + VectorSubtract( lightOrigin, v, lightDir ); +// ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) ); + VectorNormalizeFast( lightDir ); + + // calculate the specular color + d = DotProduct (normal, lightDir); +// d *= ilength; + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*2*d - lightDir[0]; + reflected[1] = normal[1]*2*d - lightDir[1]; + reflected[2] = normal[2]*2*d - lightDir[2]; + + VectorSubtract (backEnd.orientation.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + b = 0; + } else { + l = l*l; + l = l*l; + b = l * 255; + if (b > 255) { + b = 255; + } + } + + *alphas = b; + } +} + +/* +** RB_CalcDiffuseColor +** +** The basic vertex lighting calc +*/ +#if idppc_altivec +static void RB_CalcDiffuseColor_altivec( unsigned char *colors ) +{ + int i; + float *v, *normal; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t lightDir; + int numVertexes; + vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff); + vector float ambientLightVec; + vector float directedLightVec; + vector float lightDirVec; + vector float normalVec0, normalVec1; + vector float incomingVec0, incomingVec1, incomingVec2; + vector float zero, jVec; + vector signed int jVecInt; + vector signed short jVecShort; + vector unsigned char jVecChar, normalPerm; + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + // A lot of this could be simplified if we made sure + // entities light info was 16-byte aligned. + jVecChar = vec_lvsl(0, ent->ambientLight); + ambientLightVec = vec_ld(0, (vector float *)ent->ambientLight); + jVec = vec_ld(11, (vector float *)ent->ambientLight); + ambientLightVec = vec_perm(ambientLightVec,jVec,jVecChar); + + jVecChar = vec_lvsl(0, ent->directedLight); + directedLightVec = vec_ld(0,(vector float *)ent->directedLight); + jVec = vec_ld(11,(vector float *)ent->directedLight); + directedLightVec = vec_perm(directedLightVec,jVec,jVecChar); + + jVecChar = vec_lvsl(0, ent->lightDir); + lightDirVec = vec_ld(0,(vector float *)ent->lightDir); + jVec = vec_ld(11,(vector float *)ent->lightDir); + lightDirVec = vec_perm(lightDirVec,jVec,jVecChar); + + zero = (vector float)vec_splat_s8(0); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + normalPerm = vec_lvsl(0,normal); + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + normalVec0 = vec_ld(0,(vector float *)normal); + normalVec1 = vec_ld(11,(vector float *)normal); + normalVec0 = vec_perm(normalVec0,normalVec1,normalPerm); + incomingVec0 = vec_madd(normalVec0, lightDirVec, zero); + incomingVec1 = vec_sld(incomingVec0,incomingVec0,4); + incomingVec2 = vec_add(incomingVec0,incomingVec1); + incomingVec1 = vec_sld(incomingVec1,incomingVec1,4); + incomingVec2 = vec_add(incomingVec2,incomingVec1); + incomingVec0 = vec_splat(incomingVec2,0); + incomingVec0 = vec_max(incomingVec0,zero); + normalPerm = vec_lvsl(12,normal); + jVec = vec_madd(incomingVec0, directedLightVec, ambientLightVec); + jVecInt = vec_cts(jVec,0); // RGBx + jVecShort = vec_pack(jVecInt,jVecInt); // RGBxRGBx + jVecChar = vec_packsu(jVecShort,jVecShort); // RGBxRGBxRGBxRGBx + jVecChar = vec_sel(jVecChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255 + vec_ste((vector unsigned int)jVecChar,0,(unsigned int *)&colors[i*4]); // store color + } +} +#endif + +static void RB_CalcDiffuseColor_scalar( unsigned char *colors ) +{ + int i, j; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = static_cast(ambientLight[0] + incoming * directedLight[0]); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = j; + + j = static_cast(ambientLight[1] + incoming * directedLight[1]); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = j; + + j = static_cast(ambientLight[2] + incoming * directedLight[2]); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = j; + + colors[i*4+3] = 255; + } +} + +void RB_CalcDiffuseColor( unsigned char *colors ) +{ +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + RB_CalcDiffuseColor_altivec( colors ); + return; + } +#endif + RB_CalcDiffuseColor_scalar( colors ); +} diff --git a/src/renderergl1/tr_shader.cpp b/src/renderergl1/tr_shader.cpp new file mode 100644 index 0000000..db9f140 --- /dev/null +++ b/src/renderergl1/tr_shader.cpp @@ -0,0 +1,3158 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + +// tr_shader.c -- this file deals with the parsing and definition of shaders + +static char *s_shaderText; + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; + +#define FILE_HASH_SIZE 1024 +static shader_t* hashTable[FILE_HASH_SIZE]; + +#define MAX_SHADERTEXT_HASH 2048 +static char **shaderTextHashTable[MAX_SHADERTEXT_HASH]; + +/* +================ +return a hash value for the filename +================ +*/ +#ifdef __GNUCC__ + #warning TODO: check if long is ok here +#endif +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (size-1); + return hash; +} + +void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if (sh == NULL || sh == tr.defaultShader) { + h = RE_RegisterShaderLightMap(shaderName, 0); + sh = R_GetShaderByHandle(h); + } + if (sh == NULL || sh == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if (sh2 == NULL || sh2 == tr.defaultShader) { + h = RE_RegisterShaderLightMap(newShaderName, 0); + sh2 = R_GetShaderByHandle(h); + } + + if (sh2 == NULL || sh2 == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension(shaderName, strippedName, sizeof(strippedName)); + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + for (sh = hashTable[hash]; sh; sh = sh->next) { + if (Q_stricmp(sh->name, strippedName) == 0) { + if (sh != sh2) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if (timeOffset) { + sh2->timeOffset = atof(timeOffset); + } +} + +/* +=============== +ParseVector +=============== +*/ +static bool ParseVector( char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return false; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name ); + return false; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return false; + } + + return true; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "GT0" ) ) + { + return GLS_ATEST_GT_0; + } + else if ( !Q_stricmp( funcname, "LT128" ) ) + { + return GLS_ATEST_LT_80; + } + else if ( !Q_stricmp( funcname, "GE128" ) ) + { + return GLS_ATEST_GE_80; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_SRCBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_SRCBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) + { + return GLS_SRCBLEND_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) + { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_DSTBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_DSTBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_SRC_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "sin" ) ) + { + return GF_SIN; + } + else if ( !Q_stricmp( funcname, "square" ) ) + { + return GF_SQUARE; + } + else if ( !Q_stricmp( funcname, "triangle" ) ) + { + return GF_TRIANGLE; + } + else if ( !Q_stricmp( funcname, "sawtooth" ) ) + { + return GF_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) + { + return GF_INVERSE_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "noise" ) ) + { + return GF_NOISE; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( char **text, waveForm_t *wave ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( char *_text, shaderStage_t *stage ) +{ + const char *token; + char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // turb + // + if ( !Q_stricmp( token, "turb" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[1] = atof( token ); + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[1] = atof( token ); + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->rotateSpeed = atof( token ); + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) + { + tmi->type = TMOD_ENTITY_TRANSLATE; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + +/* +=================== +ParseStage +=================== +*/ +static bool ParseStage( shaderStage_t *stage, char **text ) +{ + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + bool depthMaskExplicit = false; + + stage->active = true; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" ); + return false; + } + + if ( token[0] == '}' ) + { + break; + } + // + // map + // + else if ( !Q_stricmp( token, "map" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return false; + } + + if ( !Q_stricmp( token, "$whiteimage" ) ) + { + stage->bundle[0].image[0] = tr.whiteImage; + continue; + } + else if ( !Q_stricmp( token, "$lightmap" ) ) + { + stage->bundle[0].isLightmap = true; + if ( shader.lightmapIndex < 0 || !tr.lightmaps ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + } + continue; + } + else + { + imgType_t type = IMGTYPE_COLORALPHA; + int /*imgFlags_t*/ flags = IMGFLAG_NONE; + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + stage->bundle[0].image[0] = R_FindImageFile( token, type, flags ); + + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return false; + } + } + } + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) + { + imgType_t type = IMGTYPE_COLORALPHA; + int/*imgFlags_t*/ flags = IMGFLAG_CLAMPTOEDGE; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return false; + } + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + stage->bundle[0].image[0] = R_FindImageFile( token, type, flags ); + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return false; + } + } + // + // animMap .... + // + else if ( !Q_stricmp( token, "animMap" ) ) + { + int totalImages = 0; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMap' keyword in shader '%s'\n", shader.name ); + return false; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { + int/*imgFlags_t*/ flags = IMGFLAG_NONE; + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + stage->bundle[0].image[num] = R_FindImageFile( token, IMGTYPE_COLORALPHA, flags ); + if ( !stage->bundle[0].image[num] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return false; + } + stage->bundle[0].numImageAnimations++; + } + totalImages++; + } + + if ( totalImages > MAX_IMAGE_ANIMATIONS ) { + ri.Printf( PRINT_WARNING, "WARNING: ignoring excess images for 'animMap' (found %d, max is %d) in shader '%s'\n", + totalImages, MAX_IMAGE_ANIMATIONS, shader.name ); + } + } + else if ( !Q_stricmp( token, "videoMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMap' keyword in shader '%s'\n", shader.name ); + return false; + } + stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader)); + if (stage->bundle[0].videoMapHandle != -1) { + stage->bundle[0].isVideoMap = true; + stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } else { + ri.Printf( PRINT_WARNING, "WARNING: could not load '%s' for 'videoMap' keyword in shader '%s'\n", token, shader.name ); + } + } + + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return false; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return false; + } + + if ( !Q_stricmp( token, "lequal" ) ) + { + depthFuncBits = 0; + } + else if ( !Q_stricmp( token, "equal" ) ) + { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) + { + stage->isDetail = true; + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) + { + depthMaskBits = 0; + } + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + vec3_t color; + + VectorClear( color ); + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->rgbGen = CGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "identityLighting" ) ) + { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->rgbGen = CGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertex" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX; + } + else if ( !Q_stricmp( token, "lightingDiffuse" ) ) + { + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->alphaGen = AGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->alphaGen = AGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->alphaGen = AGEN_VERTEX; + } + else if ( !Q_stricmp( token, "lightingSpecular" ) ) + { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } + else if ( !Q_stricmp( token, "portal" ) ) + { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + shader.portalRange = 256; + ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } + else + { + shader.portalRange = atof( token ); + } + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) + { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; + } + else if ( !Q_stricmp( token, "lightmap" ) ) + { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) + { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } + else if ( !Q_stricmp( token, "vector" ) ) + { + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + Q_strcat( buffer, sizeof (buffer), token ); + Q_strcat( buffer, sizeof (buffer), " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) + { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = true; + + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return false; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) + { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == AGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return true; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + ds = &shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = (deform_t)(DEFORM_TEXT0 + n); + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) + { + ds->deformationSpread = 1.0f / atof( token ); + } + else + { + ds->deformationSpread = 100.0f; + ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( char **text ) { + char *token; + const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + int/*imgFlags_t*/ imgFlags = IMGFLAG_MIPMAP | IMGFLAG_PICMIP; + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, imgFlags | IMGFLAG_CLAMPTOEDGE ); + + if ( !shader.sky.outerbox[i] ) { + shader.sky.outerbox[i] = tr.defaultImage; + } + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + shader.sky.cloudHeight = atof( token ); + if ( !shader.sky.cloudHeight ) { + shader.sky.cloudHeight = 512; + } + R_InitSkyTexCoords( shader.sky.cloudHeight ); + + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, imgFlags ); + if ( !shader.sky.innerbox[i] ) { + shader.sky.innerbox[i] = tr.defaultImage; + } + } + } + + shader.isSky = true; +} + + +/* +================= +ParseSort +================= +*/ +void ParseSort( char **text ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + }else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else { + shader.sort = atof( token ); + } +} + + + +// this table is also present in q3map + +struct infoParm_t { + const char *name; + unsigned clearSolid, surfaceFlags, contents; +}; + +infoParm_t infoParms[] = { + // server relevant contents + {"water", 1, 0, CONTENTS_WATER }, + {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging + {"lava", 1, 0, CONTENTS_LAVA }, // very damaging + {"playerclip", 1, 0, CONTENTS_PLAYERCLIP }, + {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP }, + {"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag + + // utility relevant attributes + {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes + {"trans", 0, 0, CONTENTS_TRANSLUCENT }, // don't eat contained surfaces + {"detail", 0, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"structural", 0, 0, CONTENTS_STRUCTURAL }, // force into structural bsp even if trnas + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"clusterportal", 1,0, CONTENTS_CLUSTERPORTAL }, // for bots + {"donotenter", 1, 0, CONTENTS_DONOTENTER }, // for bots + + {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering + {"sky", 0, SURF_SKY, 0 }, // emit light from an environment map + {"lightfilter", 0, SURF_LIGHTFILTER, 0 }, // filter light going through it + {"alphashadow", 0, SURF_ALPHASHADOW, 0 }, // test light on a per-pixel basis + {"hint", 0, SURF_HINT, 0 }, // use as a primary splitter + + // server attributes + {"slick", 0, SURF_SLICK, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nomarks", 0, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode + {"ladder", 0, SURF_LADDER, 0 }, + {"nodamage", 0, SURF_NODAMAGE, 0 }, + {"metalsteps", 0, SURF_METALSTEPS,0 }, + {"flesh", 0, SURF_FLESH, 0 }, + {"nosteps", 0, SURF_NOSTEPS, 0 }, + + // drawsurf attributes + {"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap) + {"pointlight", 0, SURF_POINTLIGHT, 0 }, // sample lighting at vertexes + {"nolightmap", 0, SURF_NOLIGHTMAP,0 }, // don't generate a lightmap + {"nodlight", 0, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights + {"dust", 0, SURF_DUST, 0} // leave a dust trail when walking on this surface +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( char **text ) { + char *token; + int numInfoParms = ARRAY_LEN( infoParms ); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; +#if 0 + if ( infoParms[i].clearSolid ) { + si->contents &= ~CONTENTS_SOLID; + } +#endif + break; + } + } +} + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static bool ParseShader( char **text ) +{ + char *token; + int s; + + s = 0; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return false; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name ); + return false; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + if ( s >= MAX_SHADER_STAGES ) { + ri.Printf( PRINT_WARNING, "WARNING: too many stages in shader %s (max is %i)\n", shader.name, MAX_SHADER_STAGES ); + return false; + } + + if ( !ParseStage( &stages[s], text ) ) + { + return false; + } + stages[s].active = true; + s++; + + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // sun parms + else if ( !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "q3map_sunExt" ) ) { + float a, b; + + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "deformVertexes" ) ) { + ParseDeform( text ); + continue; + } + else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "clampTime" ) ) { + token = COM_ParseExt( text, qfalse ); + if (token[0]) { + shader.clampTime = atof(token); + } + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only q3map or the server needs + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( !Q_stricmp( token, "nomipmaps" ) ) + { + shader.noMipMaps = true; + shader.noPicMip = true; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) + { + shader.noPicMip = true; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) + { + shader.polygonOffset = true; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) + { + shader.entityMergable = true; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) + { + if ( !ParseVector( text, 3, shader.fogParms.color ) ) { + return false; + } + + if ( r_greyscale->integer ) + { + float luminance; + + luminance = LUMA( shader.fogParms.color[0], shader.fogParms.color[1], shader.fogParms.color[2] ); + VectorSet( shader.fogParms.color, luminance, luminance, luminance ); + } + else if ( r_greyscale->value ) + { + float luminance; + + luminance = LUMA( shader.fogParms.color[0], shader.fogParms.color[1], shader.fogParms.color[2] ); + shader.fogParms.color[0] = LERP( shader.fogParms.color[0], luminance, r_greyscale->value ); + shader.fogParms.color[1] = LERP( shader.fogParms.color[1], luminance, r_greyscale->value ); + shader.fogParms.color[2] = LERP( shader.fogParms.color[2], luminance, r_greyscale->value ); + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms.depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp(token, "portal") ) + { + shader.sort = SS_PORTAL; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) + { + ParseSkyParms( text ); + continue; + } + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp(token, "light") ) + { + (void)COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull") ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) + { + shader.cullType = CT_TWO_SIDED; + } + else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) + { + shader.cullType = CT_BACK_SIDED; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) + { + ParseSort( text ); + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return false; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) { + return false; + } + + shader.explicitlyDefined = true; + + return true; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +/* +=================== +ComputeStageIteratorFunc + +See if we can use on of the simple fastpath stage functions, +otherwise set to the generic stage function +=================== +*/ +static void ComputeStageIteratorFunc( void ) +{ + shader.optimalStageIteratorFunc = RB_StageIteratorGeneric; + + // + // see if this should go into the sky path + // + if ( shader.isSky ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorSky; + return; + } + + if ( r_ignoreFastPath->integer ) + { + return; + } + + // + // see if this can go into the vertex lit fast path + // + if ( shader.numUnfoggedPasses == 1 ) + { + if ( stages[0].rgbGen == CGEN_LIGHTING_DIFFUSE ) + { + if ( stages[0].alphaGen == AGEN_IDENTITY ) + { + if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE ) + { + if ( !shader.polygonOffset ) + { + if ( !shader.multitextureEnv ) + { + if ( !shader.numDeforms ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTexture; + return; + } + } + } + } + } + } + } + + // + // see if this can go into an optimized LM, multitextured path + // + if ( shader.numUnfoggedPasses == 1 ) + { + if ( ( stages[0].rgbGen == CGEN_IDENTITY ) && ( stages[0].alphaGen == AGEN_IDENTITY ) ) + { + if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE && + stages[0].bundle[1].tcGen == TCGEN_LIGHTMAP ) + { + if ( !shader.polygonOffset ) + { + if ( !shader.numDeforms ) + { + if ( shader.multitextureEnv ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitexture; + } + } + } + } + } + } +} + +typedef struct { + int blendA; + int blendB; + + int multitextureEnv; + int multitextureBlend; +} collapse_t; + +static collapse_t collapse[] = { + { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, 0 }, + + { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, 0 }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, 0 }, + + { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE }, +#if 0 + { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, + GL_DECAL, 0 }, +#endif + { -1 } +}; + +/* +================ +CollapseMultitexture + +Attempt to combine two stages into a single multitexture stage +FIXME: I think modulated add + modulated add collapses incorrectly +================= +*/ +static bool CollapseMultitexture( void ) { + int abits, bbits; + int i; + textureBundle_t tmpBundle; + + if ( !qglActiveTextureARB ) { + return false; + } + + // make sure both stages are active + if ( !stages[0].active || !stages[1].active ) { + return false; + } + + // on voodoo2, don't combine different tmus + if ( glConfig.driverType == GLDRV_VOODOO ) { + if ( stages[0].bundle[0].image[0]->TMU == + stages[1].bundle[0].image[0]->TMU ) { + return false; + } + } + + abits = stages[0].stateBits; + bbits = stages[1].stateBits; + + // make sure that both stages have identical state other than blend modes + if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) != + ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) { + return false; + } + + abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + // search for a valid multitexture blend function + for ( i = 0; collapse[i].blendA != -1 ; i++ ) { + if ( abits == collapse[i].blendA + && bbits == collapse[i].blendB ) { + break; + } + } + + // nothing found + if ( collapse[i].blendA == -1 ) { + return false; + } + + // GL_ADD is a separate extension + if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) { + return false; + } + + // make sure waveforms have identical parameters + if ( ( stages[0].rgbGen != stages[1].rgbGen ) || + ( stages[0].alphaGen != stages[1].alphaGen ) ) { + return false; + } + + // an add collapse can only have identity colors + if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) { + return false; + } + + if ( stages[0].rgbGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].rgbWave, + &stages[1].rgbWave, + sizeof( stages[0].rgbWave ) ) ) + { + return false; + } + } + if ( stages[0].alphaGen == AGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].alphaWave, + &stages[1].alphaWave, + sizeof( stages[0].alphaWave ) ) ) + { + return false; + } + } + + + // make sure that lightmaps are in bundle 1 for 3dfx + if ( stages[0].bundle[0].isLightmap ) + { + tmpBundle = stages[0].bundle[0]; + stages[0].bundle[0] = stages[1].bundle[0]; + stages[0].bundle[1] = tmpBundle; + } + else + { + stages[0].bundle[1] = stages[1].bundle[0]; + } + + // set the new blend state bits + shader.multitextureEnv = collapse[i].multitextureEnv; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= collapse[i].multitextureBlend; + + // + // move down subsequent shaders + // + memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) ); + Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) ); + + return true; +} + +/* +============= + +FixRenderCommandList +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 +Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated +but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces +to be rendered with bad shaders. To fix this, need to go through all render commands and fix +sortedIndex. +============== +*/ +static void FixRenderCommandList( int newShader ) { + renderCommandList_t *cmdList = &backEndData->commands; + + if( cmdList ) { + const void *curCmd = cmdList->cmds; + + while ( 1 ) { + curCmd = PADP(curCmd, sizeof(void *)); + + switch ( *(const int *)curCmd ) { + case RC_SET_COLOR: + { + const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd; + curCmd = (const void *)(sc_cmd + 1); + break; + } + case RC_STRETCH_PIC: + { + const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd; + curCmd = (const void *)(sp_cmd + 1); + break; + } + case RC_DRAW_SURFS: + { + int i; + drawSurf_t *drawSurf; + shader_t *shader; + int fogNum; + int entityNum; + int dlightMap; + int sortedIndex; + const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd; + + for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) { + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap ); + sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1)); + if( sortedIndex >= newShader ) { + sortedIndex++; + drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + } + } + curCmd = (const void *)(ds_cmd + 1); + break; + } + case RC_DRAW_BUFFER: + { + const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd; + curCmd = (const void *)(db_cmd + 1); + break; + } + case RC_SWAP_BUFFERS: + { + const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd; + curCmd = (const void *)(sb_cmd + 1); + break; + } + case RC_END_OF_LIST: + default: + return; + } + } + } +} + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted relative to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i+1] = tr.sortedShaders[i]; + tr.sortedShaders[i+1]->sortedIndex++; + } + + // Arnout: fix rendercommandlist + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 + FixRenderCommandList( i+1 ); + + newShader->sortedIndex = i+1; + tr.sortedShaders[i+1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size, hash; + + if ( tr.numShaders == MAX_SHADERS ) { + ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); + return tr.defaultShader; + } + + newShader = (shader_t*)ri.Hunk_Alloc( sizeof( shader_t ), h_low ); + + *newShader = shader; + + if ( shader.sort <= SS_OPAQUE ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + break; + } + newShader->stages[i] = (shaderStage_t*)ri.Hunk_Alloc( sizeof( stages[i] ), h_low ); + *newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t ); + newShader->stages[i]->bundle[b].texMods = (texModInfo_t*)ri.Hunk_Alloc( size, h_low ); + Com_Memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + } + + SortNewShader(); + + hash = generateHashValue(newShader->name, FILE_HASH_SIZE); + newShader->next = hashTable[hash]; + hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. +================= +*/ +static void VertexLightingCollapse( void ) { + int stage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + Com_Memset( pStage, 0, sizeof( *pStage ) ); + } +} + +/* +=============== +InitShader +=============== +*/ +static void InitShader( const char *name, int lightmapIndex ) { + int i; + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, name, sizeof( shader.name ) ); + shader.lightmapIndex = lightmapIndex; + + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } +} + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage; + bool hasLightmapStage; + bool vertexLightmap; + + hasLightmapStage = false; + vertexLightmap = false; + + // + // set sky stuff appropriate + // + if ( shader.isSky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + // + // set appropriate stage information + // + for ( stage = 0; stage < MAX_SHADER_STAGES; ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name ); + pStage->active = false; + stage++; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) + { + int index; + + for(index = stage + 1; index < MAX_SHADER_STAGES; index++) + { + if(!stages[index].active) + break; + } + + if(index < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage)); + else + { + if(stage + 1 < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1)); + + Com_Memset(&stages[index - 1], 0, sizeof(*stages)); + } + + continue; + } + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = true; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + + // not a true lightmap but we want to leave existing + // behaviour in place and not print out a warning + //if (pStage->rgbGen == CGEN_VERTEX) { + // vertexLightmap = true; + //} + + + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) { + shader.sort = SS_SEE_THROUGH; + } else { + shader.sort = SS_BLEND0; + } + } + } + + stage++; + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) { + VertexLightingCollapse(); + stage = 1; + hasLightmapStage = false; + } + + // + // look for multitexture potential + // + if ( stage > 1 && CollapseMultitexture() ) { + stage--; + } + + if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) { + if (vertexLightmap) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); + } else { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + shader.lightmapIndex = LIGHTMAP_NONE; + } + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if (stage == 0 && !shader.isSky) + shader.sort = SS_FOG; + + // determine which stage iterator function is appropriate + ComputeStageIteratorFunc(); + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static char *FindShaderInShaderText( const char *shadername ) { + + char *token, *p; + + int i, hash; + + hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH); + + if(shaderTextHashTable[hash]) + { + for (i = 0; shaderTextHashTable[hash][i]; i++) + { + p = shaderTextHashTable[hash][i]; + token = COM_ParseExt(&p, qtrue); + + if(!Q_stricmp(token, shadername)) + return p; + } + } + + p = s_shaderText; + + if ( !p ) { + return NULL; + } + + // look for label + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( !Q_stricmp( token, shadername ) ) { + return p; + } + else { + // skip the definition + SkipBracedSection( &p, 0 ); + } + } + + return NULL; +} + + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( (name==NULL) || (name[0] == 0) ) { + return tr.defaultShader; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (Q_stricmp(sh->name, strippedName) == 0) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. + +=============== +*/ +shader_t *R_FindShader( const char *name, int lightmapIndex, bool mipRawImage ) { + char strippedName[MAX_QPATH]; + int hash; + char *shaderText; + image_t *image; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps + if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_BY_VERTEX; + } else if ( lightmapIndex < LIGHTMAP_2D ) { + // negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex]) + ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex ); + lightmapIndex = LIGHTMAP_BY_VERTEX; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh = hashTable[hash]; sh; sh = sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + !Q_stricmp(sh->name, strippedName)) { + // match found + return sh; + } + } + + InitShader( strippedName, lightmapIndex ); + + // FIXME: set these "need" values apropriately + shader.needsNormal = true; + shader.needsST1 = true; + shader.needsST2 = true; + shader.needsColor = true; + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + // enable this when building a pak file to get a global list + // of all explicit shaders + if ( r_printShaders->integer ) { + ri.Printf( PRINT_ALL, "*SHADER* %s\n", name ); + } + + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = true; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single supported image file + // + { + int/*imgFlags_t*/ flags; + + flags = IMGFLAG_NONE; + + if (mipRawImage) + { + flags |= IMGFLAG_MIPMAP | IMGFLAG_PICMIP; + } + else + { + flags |= IMGFLAG_CLAMPTOEDGE; + } + + image = R_FindImageFile( name, IMGTYPE_COLORALPHA, flags ); + if ( !image ) { + ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name ); + shader.defaultShader = true; + return FinishShader(); + } + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = true; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + + +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, bool mipRawImage) { + int hash; + shader_t *sh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // probably not necessary since this function + // only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D + // but better safe than sorry. + if ( lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_WHITEIMAGE; + } + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + // index by name + !Q_stricmp(sh->name, name)) { + // match found + return sh->index; + } + } + + InitShader( name, lightmapIndex ); + + // FIXME: set these "need" values apropriately + shader.needsNormal = true; + shader.needsST1 = true; + shader.needsST2 = true; + shader.needsColor = true; + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = true; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + sh = FinishShader(); + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, true ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, true ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, false ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f (void) { + int i; + int count; + shader_t *shader; + + ri.Printf (PRINT_ALL, "-----------------------\n"); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( ri.Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses ); + + if (shader->lightmapIndex >= 0 ) { + ri.Printf (PRINT_ALL, "L "); + } else { + ri.Printf (PRINT_ALL, " "); + } + if ( shader->multitextureEnv == GL_ADD ) { + ri.Printf( PRINT_ALL, "MT(a) " ); + } else if ( shader->multitextureEnv == GL_MODULATE ) { + ri.Printf( PRINT_ALL, "MT(m) " ); + } else if ( shader->multitextureEnv == GL_DECAL ) { + ri.Printf( PRINT_ALL, "MT(d) " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + if ( shader->explicitlyDefined ) { + ri.Printf( PRINT_ALL, "E " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) { + ri.Printf( PRINT_ALL, "gen " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) { + ri.Printf( PRINT_ALL, "sky " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitexture ) { + ri.Printf( PRINT_ALL, "lmmt" ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTexture ) { + ri.Printf( PRINT_ALL, "vlt " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->defaultShader ) { + ri.Printf (PRINT_ALL, ": %s (DEFAULTED)\n", shader->name); + } else { + ri.Printf (PRINT_ALL, ": %s\n", shader->name); + } + count++; + } + ri.Printf (PRINT_ALL, "%i total shaders\n", count); + ri.Printf (PRINT_ALL, "------------------\n"); +} + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 4096 +static void ScanAndLoadShaderFiles( void ) +{ + char **shaderFiles; + char *buffers[MAX_SHADER_FILES] = {0}; + char *p; + int numShaderFiles; + int i; + char *oldp, *token, *hashMem, *textEnd; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + char shaderName[MAX_QPATH]; + int shaderLine; + + long sum = 0, summand; + // scan for shader files + shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles ); + + if ( !shaderFiles || !numShaderFiles ) + { + ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaderFiles > MAX_SHADER_FILES ) { + numShaderFiles = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaderFiles; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename ); + summand = ri.FS_ReadFile( filename, (void **)&buffers[i] ); + + if ( !buffers[i] ) + ri.Error( ERR_DROP, "Couldn't load %s", filename ); + + // Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders. + p = buffers[i]; + COM_BeginParseSession(filename); + while(1) + { + token = COM_ParseExt(&p, qtrue); + + if(!*token) + break; + + Q_strncpyz(shaderName, token, sizeof(shaderName)); + shaderLine = COM_GetCurrentParseLine(); + + token = COM_ParseExt(&p, qtrue); + if(token[0] != '{' || token[1] != '\0') + { + ri.Printf(PRINT_WARNING, "WARNING: Ignoring shader file %s. Shader \"%s\" on line %d missing opening brace", + filename, shaderName, shaderLine); + if (token[0]) + { + ri.Printf(PRINT_WARNING, " (found \"%s\" on line %d)", token, COM_GetCurrentParseLine()); + } + ri.Printf(PRINT_WARNING, ".\n"); + ri.FS_FreeFile(buffers[i]); + buffers[i] = NULL; + break; + } + + if(!SkipBracedSection(&p, 1)) + { + ri.Printf(PRINT_WARNING, "WARNING: Ignoring shader file %s. Shader \"%s\" on line %d missing closing brace.\n", + filename, shaderName, shaderLine); + ri.FS_FreeFile(buffers[i]); + buffers[i] = NULL; + break; + } + } + + + if (buffers[i]) + sum += summand; + } + + // build single large buffer + s_shaderText = (char*)ri.Hunk_Alloc( sum + numShaderFiles*2, h_low ); + s_shaderText[ 0 ] = '\0'; + textEnd = s_shaderText; + + // free in reverse order, so the temp files are all dumped + for ( i = numShaderFiles - 1; i >= 0 ; i-- ) + { + if ( !buffers[i] ) + continue; + + strcat( textEnd, buffers[i] ); + strcat( textEnd, "\n" ); + textEnd += strlen( textEnd ); + ri.FS_FreeFile( buffers[i] ); + } + + COM_Compress( s_shaderText ); + + // free up memory + ri.FS_FreeFileList( shaderFiles ); + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + + p = s_shaderText; + // look for shader names + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTableSizes[hash]++; + size++; + SkipBracedSection(&p, 0); + } + + size += MAX_SHADERTEXT_HASH; + + hashMem = (char*)ri.Hunk_Alloc( size * sizeof(char *), h_low ); + + for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { + shaderTextHashTable[i] = (char **) hashMem; + hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *)); + } + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + + p = s_shaderText; + // look for shader names + while ( 1 ) { + oldp = p; + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; + + SkipBracedSection(&p, 0); + } + + return; + +} + + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + + // init the default shader + InitShader( "", LIGHTMAP_NONE ); + stages[0].bundle[0].image[0] = tr.defaultImage; + stages[0].active = true; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, true ); + tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, true ); + + // Hack to make fogging work correctly on flares. Fog colors are calculated + // in tr_flare.c already. + if(!tr.flareShader->defaultShader) + { + int index; + + for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++) + { + tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE; + tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE; + } + } + + tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, true ); +} + +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders( void ) { + ri.Printf( PRINT_ALL, "Initializing Shaders\n" ); + + Com_Memset(hashTable, 0, sizeof(hashTable)); + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); +} diff --git a/src/renderergl1/tr_shadows.cpp b/src/renderergl1/tr_shadows.cpp new file mode 100644 index 0000000..8d6ae56 --- /dev/null +++ b/src/renderergl1/tr_shadows.cpp @@ -0,0 +1,327 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES/3]; +static vec3_t shadowXyz[SHADER_MAX_VERTEXES]; + +void R_AddEdgeDef( int i1, int i2, int facing ) { + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +} + +void R_RenderShadowEdges( void ) { + int i; + +#if 0 + int numTris; + + // dumb way -- render every triangle's edges + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + if ( !facing[i] ) { + continue; + } + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( shadowXyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( shadowXyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i3 ] ); + qglVertex3fv( shadowXyz[ i3 ] ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( shadowXyz[ i1 ] ); + qglEnd(); + } +#else + int c, c2; + int j, k; + int i2; + int c_edges, c_rejected; + int hit[2]; + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( shadowXyz[ i ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( shadowXyz[ i2 ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } + } + } +#endif +} + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_ShadowTessEnd( void ) { + int i; + int numTris; + vec3_t lightDir; + GLboolean rgba[4]; + + if ( glConfig.stencilBits < 4 ) { + return; + } + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, shadowXyz[i] ); + } + + // decide which triangles face the light + Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + // draw the silhouette edges + + GL_Bind( tr.whiteImage ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglGetBooleanv(GL_COLOR_WRITEMASK, rgba); + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + GL_Cull( CT_BACK_SIDED ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + GL_Cull( CT_FRONT_SIDED ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + + + // reenable writing to the color buffer + qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]); +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + + GL_Bind( tr.whiteImage ); + + qglLoadIdentity (); + + qglColor3f( 0.6f, 0.6f, 0.6f ); + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); + +// qglColor3f( 1, 0, 0 ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd (); + + qglColor4f(1,1,1,1); + qglDisable( GL_STENCIL_TEST ); +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.orientation.axis[0][2]; + ground[1] = backEnd.orientation.axis[1][2]; + ground[2] = backEnd.orientation.axis[2][2]; + + groundDist = backEnd.orientation.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, (0.5 - d), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +} diff --git a/src/renderergl1/tr_sky.cpp b/src/renderergl1/tr_sky.cpp new file mode 100644 index 0000000..6dd9c45 --- /dev/null +++ b/src/renderergl1/tr_sky.cpp @@ -0,0 +1,796 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_sky.c +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + if (dv < 0.001) + continue; // don't divide by zero + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < sky_mins[0][axis]) + sky_mins[0][axis] = s; + if (t < sky_mins[1][axis]) + sky_mins[1][axis] = t; + if (s > sky_maxs[0][axis]) + sky_maxs[0][axis] = s; + if (t > sky_maxs[1][axis]) + sky_maxs[1][axis] = t; + } +} + +#define ON_EPSILON 0.1f // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + bool front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); + if (stage == 6) + { // fully clipped, so draw it + AddSkyPolygon (nump, vecs); + return; + } + + front = back = false; + norm = sky_clip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = true; + sides[i] = SIDE_FRONT; + } + else if (d < -ON_EPSILON) + { + back = true; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + ClipSkyPolygon (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumIndexes; i += 3 ) + { + for (j = 0 ; j < 3 ; j++) + { + VectorSubtract( input->xyz[input->indexes[i+j]], + backEnd.viewParms.orientation.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) +{ + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + b[0] = s*boxSize; + b[1] = t*boxSize; + b[2] = boxSize; + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + { + outXYZ[j] = -b[-k - 1]; + } + else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = (s+1)*0.5; + t = (t+1)*0.5; + if (s < sky_min) + { + s = sky_min; + } + else if (s > sky_max) + { + s = sky_max; + } + + if (t < sky_min) + { + t = sky_min; + } + else if (t > sky_max) + { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) + { + outSt[0] = s; + outSt[1] = t; + } +} + +static int sky_texorder[6] = {0,2,1,3,4,5}; +static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; +static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) +{ + int s, t; + + GL_Bind( image ); + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t < maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + qglBegin( GL_TRIANGLE_STRIP ); + + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + qglTexCoord2fv( s_skyTexCoords[t][s] ); + qglVertex3fv( s_skyPoints[t][s] ); + + qglTexCoord2fv( s_skyTexCoords[t+1][s] ); + qglVertex3fv( s_skyPoints[t+1][s] ); + } + + qglEnd(); + } +} + +static void DrawSkyBox( shader_t *shader ) +{ + int i; + + sky_min = 0; + sky_max = 1; + + Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for (i=0 ; i<6 ; i++) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky.outerbox[sky_texorder[i]], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], bool addIndexes ) +{ + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.orientation.origin, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if ( tess.numVertexes >= SHADER_MAX_VERTEXES ) + { + ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight-1; t++ ) + { + for ( s = 0; s < sWidth-1; s++ ) + { + tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) +{ + int i; + + for ( i =0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) // FIXME? shader->sky.fullClouds ) + { + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) + continue; + } + else + { + switch( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = static_cast(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS); + sky_mins_subd[1] = static_cast(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS); + sky_maxs_subd[0] = static_cast(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS); + sky_maxs_subd[1] = static_cast(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < MIN_T ) + sky_mins_subd[1] = MIN_T; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < MIN_T ) + sky_maxs_subd[1] = MIN_T; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) +{ + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->isSky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + + if ( shader->sky.cloudHeight ) + { + for ( i = 0; i < MAX_SHADER_STAGES; i++ ) + { + if ( !tess.xstages[i] ) { + break; + } + FillCloudBox( shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ((a)*(a)) +void R_InitSkyTexCoords( float heightCloud ) +{ + int i, s, t; + float radiusWorld = 4096; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = Q_acos( v[0] ); + tRad = Q_acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +** RB_DrawSun +*/ +void RB_DrawSun( float scale, shader_t *shader ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + byte sunColor[4] = { 255, 255, 255, 255 }; + + if ( !backEnd.skyRenderedThisView ) { + return; + } + + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + qglTranslatef (backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]); + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + size = dist * scale; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + RB_BeginSurface( shader, 0 ); + + RB_AddQuadStamp(origin, vec1, vec2, sunColor); + + RB_EndSurface(); + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) { + if ( r_fastsky->integer ) { + return; + } + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { + qglDepthRange( 1.0, 1.0 ); + } + + // draw the outer skybox + if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) { + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglPushMatrix (); + GL_State( 0 ); + GL_Cull( CT_FRONT_SIDED ); + qglTranslatef (backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]); + + DrawSkyBox( tess.shader ); + + qglPopMatrix(); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + RB_StageIteratorGeneric(); + + // draw the inner skybox + + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = true; +} diff --git a/src/renderergl1/tr_subs.cpp b/src/renderergl1/tr_subs.cpp new file mode 100644 index 0000000..ba93467 --- /dev/null +++ b/src/renderergl1/tr_subs.cpp @@ -0,0 +1,50 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_subs.c - common function replacements for modular renderer + +#include "tr_local.h" + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, msg); + Q_vsnprintf(text, sizeof(text), msg, argptr); + va_end(argptr); + + ri.Printf(PRINT_ALL, "%s", text); +} + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, error); + Q_vsnprintf(text, sizeof(text), error, argptr); + va_end(argptr); + + ri.Error(level, "%s", text); +} diff --git a/src/renderergl1/tr_surface.cpp b/src/renderergl1/tr_surface.cpp new file mode 100644 index 0000000..176dcb5 --- /dev/null +++ b/src/renderergl1/tr_surface.cpp @@ -0,0 +1,1239 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_surf.c +#include "tr_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface(tess.shader, tess.fogNum ); +} + + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.orientation.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2; + tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1; + + tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2; + tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2; + + tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1; + tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2; + + // constant color all the way around + // should this be identity and let the shader specify from entity? + * ( unsigned int * ) &tess.vertexColors[ndx] = + * ( unsigned int * ) &tess.vertexColors[ndx+1] = + * ( unsigned int * ) &tess.vertexColors[ndx+2] = + * ( unsigned int * ) &tess.vertexColors[ndx+3] = + * ( unsigned int * )color; + + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + if ( backEnd.currentEntity->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.orientation.axis[1], radius, left ); + VectorScale( backEnd.viewParms.orientation.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.orientation.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.orientation.axis[2], left ); + + VectorScale( backEnd.viewParms.orientation.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.orientation.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + + +/* +============= +RB_SurfacePolychain +============= +*/ +static void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + + +/* +============= +RB_SurfaceTriangles +============= +*/ +static void RB_SurfaceTriangles( srfTriangles_t *srf ) { + int i; + drawVert_t *dv; + float *xyz, *normal, *texCoords; + byte *color; + int dlightBits; + bool needsNormal; + + dlightBits = srf->dlightBits; + tess.dlightBits |= dlightBits; + + RB_CHECKOVERFLOW( srf->numVerts, srf->numIndexes ); + + for ( i = 0 ; i < srf->numIndexes ; i += 3 ) { + tess.indexes[ tess.numIndexes + i + 0 ] = tess.numVertexes + srf->indexes[ i + 0 ]; + tess.indexes[ tess.numIndexes + i + 1 ] = tess.numVertexes + srf->indexes[ i + 1 ]; + tess.indexes[ tess.numIndexes + i + 2 ] = tess.numVertexes + srf->indexes[ i + 2 ]; + } + tess.numIndexes += srf->numIndexes; + + dv = srf->verts; + xyz = tess.xyz[ tess.numVertexes ]; + normal = tess.normal[ tess.numVertexes ]; + texCoords = tess.texCoords[ tess.numVertexes ][0]; + color = tess.vertexColors[ tess.numVertexes ]; + needsNormal = tess.shader->needsNormal; + + for ( i = 0 ; i < srf->numVerts ; i++, dv++, xyz += 4, normal += 4, texCoords += 4, color += 4 ) { + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + + if ( needsNormal ) { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + + texCoords[2] = dv->lightmap[0]; + texCoords[3] = dv->lightmap[1]; + + *(int *)color = *(int *)dv->color; + } + + for ( i = 0 ; i < srf->numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i] = dlightBits; + } + + tess.numVertexes += srf->numVerts; +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +static void RB_SurfaceBeam( void ) +{ +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) + return; + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + for ( i = 0; i < NUM_BEAM_SEGS ; i++ ) + { + RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i ); +// VectorAdd( start_points[i], origin, start_points[i] ); + VectorAdd( start_points[i], direction, end_points[i] ); + } + + GL_Bind( tr.whiteImage ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + qglColor3f( 1, 0, 0 ); + + qglBegin( GL_TRIANGLE_STRIP ); + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + qglVertex3fv( start_points[ i % NUM_BEAM_SEGS] ); + qglVertex3fv( end_points[ i % NUM_BEAM_SEGS] ); + } + qglEnd(); +} + +//================================================================================ + +static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth ) +{ + float spanWidth2; + int vbase; + float t = len / 256.0f; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + // FIXME: use quad stamp? + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up ) +{ + int i; + vec3_t pos[4]; + vec3_t v; + int spanWidth = r_railWidth->integer; + float c, s; + float scale; + + if ( numSegs > 1 ) + numSegs--; + if ( !numSegs ) + return; + + scale = 0.25; + + for ( i = 0; i < 4; i++ ) + { + c = cos( DEG2RAD( 45 + i * 90 ) ); + s = sin( DEG2RAD( 45 + i * 90 ) ); + v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth; + v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth; + v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth; + VectorAdd( start, v, pos[i] ); + + if ( numSegs > 1 ) + { + // offset by 1 segment if we're doing a long distance shot + VectorAdd( pos[i], dir, pos[i] ); + } + } + + for ( i = 0; i < numSegs; i++ ) + { + int j; + + RB_CHECKOVERFLOW( 4, 6 ); + + for ( j = 0; j < 4; j++ ) + { + VectorCopy( pos[j], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = ( j < 2 ); + tess.texCoords[tess.numVertexes][0][1] = ( j && j != 3 ); + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorAdd( pos[j], dir, pos[j] ); + } + + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2; + } +} + +/* +** RB_SurfaceRailRinges +*/ +static void RB_SurfaceRailRings( void ) { + refEntity_t *e; + int numSegs; + int len; + vec3_t vec; + vec3_t right, up; + vec3_t start, end; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + MakeNormalVectors( vec, right, up ); + numSegs = ( len ) / r_railSegmentLength->value; + if ( numSegs <= 0 ) { + numSegs = 1; + } + + VectorScale( vec, r_railSegmentLength->value, vec ); + + DoRailDiscs( numSegs, start, vec, right, up ); +} + +/* +** RB_SurfaceRailCore +*/ +static void RB_SurfaceRailCore( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.orientation.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.orientation.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoRailCore( start, end, right, len, r_railCoreWidth->integer ); +} + +/* +** RB_SurfaceLightningBolt +*/ +static void RB_SurfaceLightningBolt( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + int i; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.orientation.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.orientation.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + for ( i = 0 ; i < 4 ; i++ ) { + vec3_t temp; + + DoRailCore( start, end, right, len, 8 ); + RotatePointAroundVector( temp, vec, right, 45 ); + VectorCopy( temp, right ); + } +} + +/* +** VectorArrayNormalize +* +* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0) +* This means that we don't have to worry about zero length or enormously long vectors. +*/ +static void VectorArrayNormalize(vec4_t *normals, unsigned int count) +{ +// assert(count); + +#if idppc + { + float half = 0.5; + float one = 1.0; + float *components = (float *)normals; + + // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, + // runs *much* faster than calling sqrt(). We'll use a single Newton-Raphson + // refinement step to get a little more precision. This seems to yeild results + // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5). + // (That is, for the given input range of about 0.6 to 2.0). + do { + float x, y, z; + float B, y0, y1; + + x = components[0]; + y = components[1]; + z = components[2]; + components += 4; + B = x*x + y*y + z*z; + +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y0) : "f" (B)); +#else + y0 = __frsqrte(B); +#endif + y1 = y0 + half*y0*(one - B*y0*y0); + + x = x * y1; + y = y * y1; + components[-4] = x; + z = z * y1; + components[-3] = y; + components[-2] = z; + } while(count--); + } +#else // No assembly version for this architecture, or C_ONLY defined + // given the input, it's safe to call VectorNormalizeFast + while (count--) { + VectorNormalizeFast(normals[0]); + normals++; + } +#endif + +} + + + +/* +** LerpMeshVertexes +*/ +#if idppc_altivec +static void LerpMeshVertexes_altivec(md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale QALIGN(16); + float newXyzScale QALIGN(16); + float oldNormalScale QALIGN(16); + float newNormalScale QALIGN(16); + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + vector signed short newNormalsVec0; + vector signed short newNormalsVec1; + vector signed int newNormalsIntVec; + vector float newNormalsFloatVec; + vector float newXyzScaleVec; + vector unsigned char newNormalsLoadPermute; + vector unsigned char newNormalsStorePermute; + vector float zero; + + newNormalsStorePermute = vec_lvsl(0,(float *)&newXyzScaleVec); + newXyzScaleVec = *(vector float *)&newXyzScale; + newXyzScaleVec = vec_perm(newXyzScaleVec,newXyzScaleVec,newNormalsStorePermute); + newXyzScaleVec = vec_splat(newXyzScaleVec,0); + newNormalsLoadPermute = vec_lvsl(0,newXyz); + newNormalsStorePermute = vec_lvsr(0,outXyz); + zero = (vector float)vec_splat_s8(0); + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + newNormalsLoadPermute = vec_lvsl(0,newXyz); + newNormalsStorePermute = vec_lvsr(0,outXyz); + newNormalsVec0 = vec_ld(0,newXyz); + newNormalsVec1 = vec_ld(16,newXyz); + newNormalsVec0 = vec_perm(newNormalsVec0,newNormalsVec1,newNormalsLoadPermute); + newNormalsIntVec = vec_unpackh(newNormalsVec0); + newNormalsFloatVec = vec_ctf(newNormalsIntVec,0); + newNormalsFloatVec = vec_madd(newNormalsFloatVec,newXyzScaleVec,zero); + newNormalsFloatVec = vec_perm(newNormalsFloatVec,newNormalsFloatVec,newNormalsStorePermute); + //outXyz[0] = newXyz[0] * newXyzScale; + //outXyz[1] = newXyz[1] * newXyzScale; + //outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + vec_ste(newNormalsFloatVec,0,outXyz); + vec_ste(newNormalsFloatVec,4,outXyz); + vec_ste(newNormalsFloatVec,8,outXyz); + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +} +#endif + +static void LerpMeshVertexes_scalar(md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +} + +static void LerpMeshVertexes(md3Surface_t *surf, float backlerp) +{ +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + LerpMeshVertexes_altivec( surf, backlerp ); + return; + } +#endif // idppc_altivec + LerpMeshVertexes_scalar( surf, backlerp ); +} + + +/* +============= +RB_SurfaceMesh +============= +*/ +static void RB_SurfaceMesh(md3Surface_t *surface) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 ); + + LerpMeshVertexes (surface, backlerp); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[Bob + j] = Doug + triangles[j]; + } + tess.numIndexes += indexes; + + texCoords = (float *) ((byte *)surface + surface->ofsSt); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j*2+0]; + tess.texCoords[Doug + j][0][1] = texCoords[j*2+1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} + + +/* +============== +RB_SurfaceFace +============== +*/ +static void RB_SurfaceFace( srfSurfaceFace_t *surf ) { + int i; + unsigned *indices; + glIndex_t *tessIndexes; + float *v; + float *normal; + int ndx; + int Bob; + int numPoints; + int dlightBits; + + RB_CHECKOVERFLOW( surf->numPoints, surf->numIndices ); + + dlightBits = surf->dlightBits; + tess.dlightBits |= dlightBits; + + indices = ( unsigned * ) ( ( ( char * ) surf ) + surf->ofsIndices ); + + Bob = tess.numVertexes; + tessIndexes = tess.indexes + tess.numIndexes; + for ( i = surf->numIndices-1 ; i >= 0 ; i-- ) { + tessIndexes[i] = indices[i] + Bob; + } + + tess.numIndexes += surf->numIndices; + + numPoints = surf->numPoints; + + if ( tess.shader->needsNormal ) { + normal = surf->plane.normal; + for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) { + VectorCopy( normal, tess.normal[ndx] ); + } + } + + for ( i = 0, v = surf->points[0], ndx = tess.numVertexes; i < numPoints; i++, v += VERTEXSIZE, ndx++ ) { + VectorCopy( v, tess.xyz[ndx]); + tess.texCoords[ndx][0][0] = v[3]; + tess.texCoords[ndx][0][1] = v[4]; + tess.texCoords[ndx][1][0] = v[5]; + tess.texCoords[ndx][1][1] = v[6]; + * ( unsigned int * ) &tess.vertexColors[ndx] = * ( unsigned int * ) &v[7]; + tess.vertexDlightBits[ndx] = dlightBits; + } + + + tess.numVertexes += surf->numPoints; +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + // never let it go negative + if ( r_lodCurveError->value < 0 ) { + return 0; + } + + world[0] = local[0] * backEnd.orientation.axis[0][0] + local[1] * backEnd.orientation.axis[1][0] + + local[2] * backEnd.orientation.axis[2][0] + backEnd.orientation.origin[0]; + world[1] = local[0] * backEnd.orientation.axis[0][1] + local[1] * backEnd.orientation.axis[1][1] + + local[2] * backEnd.orientation.axis[2][1] + backEnd.orientation.origin[1]; + world[2] = local[0] * backEnd.orientation.axis[0][2] + local[1] * backEnd.orientation.axis[1][2] + + local[2] * backEnd.orientation.axis[2][2] + backEnd.orientation.origin[2]; + + VectorSubtract( world, backEnd.viewParms.orientation.origin, world ); + d = DotProduct( world, backEnd.viewParms.orientation.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +static void RB_SurfaceGrid( srfGridMesh_t *cv ) { + int i, j; + float *xyz; + float *texCoords; + float *normal; + unsigned char *color; + drawVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int *vDlightBits; + bool needsNormal; + + dlightBits = cv->dlightBits; + tess.dlightBits |= dlightBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( cv->lodOrigin, cv->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < cv->width-1 ; i++ ) { + if ( cv->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = cv->width-1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < cv->height-1 ; i++ ) { + if ( cv->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = cv->height-1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; + texCoords = tess.texCoords[numVertexes][0]; + color = ( unsigned char * ) &tess.vertexColors[numVertexes]; + vDlightBits = &tess.vertexDlightBits[numVertexes]; + needsNormal = tess.shader->needsNormal; + + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + texCoords[2] = dv->lightmap[0]; + texCoords[3] = dv->lightmap[1]; + if ( needsNormal ) { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + * ( unsigned int * ) color = * ( unsigned int * ) dv->color; + *vDlightBits++ = dlightBits; + xyz += 4; + normal += 4; + texCoords += 4; + color += 4; + } + } + + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for (i = 0 ; i < h ; i++) { + for (j = 0 ; j < w ; j++) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i*lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes+1] = v3; + tess.indexes[numIndexes+2] = v1; + + tess.indexes[numIndexes+3] = v1; + tess.indexes[numIndexes+4] = v3; + tess.indexes[numIndexes+5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +static void RB_SurfaceAxis( void ) { + GL_Bind( tr.whiteImage ); + GL_State( GLS_DEFAULT ); + qglLineWidth( 3 ); + qglBegin( GL_LINES ); + qglColor3f( 1,0,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 16,0,0 ); + qglColor3f( 0,1,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,16,0 ); + qglColor3f( 0,0,1 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,0,16 ); + qglEnd(); + qglLineWidth( 1 ); +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +static void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch( backEnd.currentEntity->e.reType ) { + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_RAIL_CORE: + RB_SurfaceRailCore(); + break; + case RT_RAIL_RINGS: + RB_SurfaceRailRings(); + break; + case RT_LIGHTNING: + RB_SurfaceLightningBolt(); + break; + default: + RB_SurfaceAxis(); + break; + } +} + +static void RB_SurfaceBad( surfaceType_t *surfType ) { + ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + +static void RB_SurfaceFlare(srfFlare_t *surf) +{ + if (r_flares->integer) + RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal); +} + +static void RB_SurfaceSkip( void *surf ) { +} + + +void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { + (void(*)(void*))RB_SurfaceBad, // SF_BAD, + (void(*)(void*))RB_SurfaceSkip, // SF_SKIP, + (void(*)(void*))RB_SurfaceFace, // SF_FACE, + (void(*)(void*))RB_SurfaceGrid, // SF_GRID, + (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES, + (void(*)(void*))RB_SurfacePolychain, // SF_POLY, + (void(*)(void*))RB_SurfaceMesh, // SF_MD3, + (void(*)(void*))RB_MDRSurfaceAnim, // SF_MDR, + (void(*)(void*))RB_IQMSurfaceAnim, // SF_IQM, + (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, + (void(*)(void*))RB_SurfaceEntity // SF_ENTITY +}; diff --git a/src/renderergl1/tr_world.cpp b/src/renderergl1/tr_world.cpp new file mode 100644 index 0000000..7e59d51 --- /dev/null +++ b/src/renderergl1/tr_world.cpp @@ -0,0 +1,670 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + + + +/* +================= +R_CullTriSurf + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static bool R_CullTriSurf( srfTriangles_t *cv ) { + int boxCull; + + boxCull = R_CullLocalBox( cv->bounds ); + + if ( boxCull == CULL_OUT ) { + return true; + } + return false; +} + +/* +================= +R_CullGrid + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static bool R_CullGrid( srfGridMesh_t *cv ) { + int boxCull; + int sphereCull; + + if ( r_nocurves->integer ) { + return true; + } + + if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) { + sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius ); + } else { + sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius ); + } + + // check for trivial reject + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_patch_out++; + return true; + } + // check bounding box if necessary + else if ( sphereCull == CULL_CLIP ) + { + tr.pc.c_sphere_cull_patch_clip++; + + boxCull = R_CullLocalBox( cv->meshBounds ); + + if ( boxCull == CULL_OUT ) + { + tr.pc.c_box_cull_patch_out++; + return true; + } + else if ( boxCull == CULL_IN ) + { + tr.pc.c_box_cull_patch_in++; + } + else + { + tr.pc.c_box_cull_patch_clip++; + } + } + else + { + tr.pc.c_sphere_cull_patch_in++; + } + + return false; +} + + +/* +================ +R_CullSurface + +Tries to back face cull surfaces before they are lighted or +added to the sorting list. + +This will also allow mirrors on both sides of a model without recursion. +================ +*/ +static bool R_CullSurface( surfaceType_t *surface, shader_t *shader ) { + srfSurfaceFace_t *sface; + float d; + + if ( r_nocull->integer ) { + return false; + } + + if ( *surface == SF_GRID ) { + return R_CullGrid( (srfGridMesh_t *)surface ); + } + + if ( *surface == SF_TRIANGLES ) { + return R_CullTriSurf( (srfTriangles_t *)surface ); + } + + if ( *surface != SF_FACE ) { + return false; + } + + if ( shader->cullType == CT_TWO_SIDED ) { + return false; + } + + // face culling + if ( !r_facePlaneCull->integer ) { + return false; + } + + sface = ( srfSurfaceFace_t * ) surface; + d = DotProduct (tr.orientation.viewOrigin, sface->plane.normal); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( shader->cullType == CT_FRONT_SIDED ) { + if ( d < sface->plane.dist - 8 ) { + return true; + } + } else { + if ( d > sface->plane.dist + 8 ) { + return true; + } + } + + return false; +} + + +static int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits = dlightBits; + return dlightBits; +} + +static int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +} + + +static int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits = dlightBits; + return dlightBits; +#if 0 + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +#endif +} + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + + + +/* +====================== +R_AddWorldSurface +====================== +*/ +static void R_AddWorldSurface( msurface_t *surf, int dlightBits ) { + if ( surf->viewCount == tr.viewCount ) { + return; // already in this view + } + + surf->viewCount = tr.viewCount; + // FIXME: bmodel fog? + + // try to cull before dlighting or adding + if ( R_CullSurface( surf->data, surf->shader ) ) { + return; + } + + // check for dlighting + if ( dlightBits ) { + dlightBits = R_DlightSurface( surf, dlightBits ); + dlightBits = ( dlightBits != 0 ); + } + + R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits ); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + R_SetupEntityLighting( &tr.refdef, ent ); + R_DlightBmodel( bmodel ); + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + R_AddWorldSurface( bmodel->firstSurface + i, tr.currentEntity->needDlights ); + } +} + + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + + +/* +================ +R_RecursiveWorldNode +================ +*/ +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do { + int newDlights[2]; + + // if the node wasn't marked as potentially visible, exit + if (node->visframe != tr.visCount) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) { + int i; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + dlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + R_AddWorldSurface( surf, dlightBits ); + mark++; + } + } + +} + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( const vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + ri.Error (ERR_DROP, "R_PointInLeaf: bad model"); + } + + node = tr.world->nodes; + while( 1 ) { + if (node->contents != -1) { + break; + } + plane = node->plane; + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS (int cluster) { + if (!tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return tr.world->novis; + } + + return tr.world->vis + cluster * tr.world->clusterBytes; +} + +/* +================= +R_inPVS +================= +*/ +bool R_inPVS( const vec3_t p1, const vec3_t p2 ) { + mnode_t *leaf; + byte *vis; + + leaf = R_PointInLeaf( p1 ); + vis = ri.CM_ClusterPVS( leaf->cluster ); // why not R_ClusterPVS ?? + leaf = R_PointInLeaf( p2 ); + + if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) { + return false; + } + return true; +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +static void R_MarkLeaves (void) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + // if r_showcluster was just turned on, remark everything + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified + && !r_showcluster->modified ) { + return; + } + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = false; + if ( r_showcluster->integer ) { + ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS (tr.viewCluster); + + for (i=0,leaf=tr.world->nodes ; inumnodes ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if (parent->visframe == tr.visCount) + break; + parent->visframe = tr.visCount; + parent = parent->parent; + } while (parent); + } +} + + +/* +============= +R_AddWorldSurfaces +============= +*/ +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = REFENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + R_MarkLeaves (); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( tr.refdef.num_dlights > 32 ) { + tr.refdef.num_dlights = 32 ; + } + R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1 ); +} diff --git a/src/renderergl2/CMakeLists.txt b/src/renderergl2/CMakeLists.txt new file mode 100644 index 0000000..b821436 --- /dev/null +++ b/src/renderergl2/CMakeLists.txt @@ -0,0 +1,146 @@ +include("${CMAKE_SOURCE_DIR}/cmake/SDL2.cmake") + +find_package(OpenGL) + +set(PARENT_DIR ${CMAKE_SOURCE_DIR}/src) +include_directories( + ${PARENT_DIR}/jpeg-8c + ${PARENT_DIR}/renderercommon + ${PARENT_DIR}/nettle-3.3 + ${SDL2_INCLUDE_DIRS} + ) + +set(GLSL_SHADERS + glsl/bokeh_fp.glsl + glsl/bokeh_vp.glsl + glsl/calclevels4x_fp.glsl + glsl/calclevels4x_vp.glsl + glsl/depthblur_fp.glsl + glsl/depthblur_vp.glsl + glsl/dlight_fp.glsl + glsl/dlight_vp.glsl + glsl/down4x_fp.glsl + glsl/down4x_vp.glsl + glsl/fogpass_fp.glsl + glsl/fogpass_vp.glsl + glsl/generic_fp.glsl + glsl/generic_vp.glsl + glsl/lightall_fp.glsl + glsl/lightall_vp.glsl + glsl/pshadow_fp.glsl + glsl/pshadow_vp.glsl + glsl/shadowfill_fp.glsl + glsl/shadowfill_vp.glsl + glsl/shadowmask_fp.glsl + glsl/shadowmask_vp.glsl + glsl/ssao_fp.glsl + glsl/ssao_vp.glsl + glsl/texturecolor_fp.glsl + glsl/texturecolor_vp.glsl + glsl/tonemap_fp.glsl + glsl/tonemap_vp.glsl + ) + +# Converts ^^^GSLS shaders^^^ into const char* C-strings. +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/glsl/) +foreach(shader ${GLSL_SHADERS}) + set(in ${CMAKE_CURRENT_SOURCE_DIR}/${shader}) + + get_filename_component(out ${in} NAME_WE) + set(out ${CMAKE_CURRENT_BINARY_DIR}/glsl/${out}.c) + + get_filename_component(shader ${shader} NAME_WE) + message(STATUS "-- Generating embeded GLSL ${shader}") + + file(READ ${in} contents HEX) + file(WRITE ${out} "const char *fallbackShader_${shader} = \"") + + string(LENGTH "${contents}" contents_length) + math(EXPR contents_length "${contents_length} - 1") + + foreach(iter RANGE 0 ${contents_length} 2) + string(SUBSTRING ${contents} ${iter} 2 line) + file(APPEND "${out}" "\\x${line}") + endforeach() + file(APPEND "${out}" "\";\n") + + list(APPEND renderergl2_SHADERS "${out}") + set_source_files_properties(${out} PROPERTIES GENERATED TRUE) +endforeach(shader) + +set(renderergl2_SRCS + ${renderergl2_SHADERS} + tr_local.h + tr_dsa.h + tr_extramath.h + tr_extratypes.h + tr_fbo.h + tr_postprocess.h + tr_animation.cpp + tr_backend.cpp + tr_bsp.cpp + tr_cmds.cpp + tr_curve.cpp + tr_dsa.cpp + tr_extensions.cpp + tr_extramath.cpp + tr_fbo.cpp + tr_flares.cpp + tr_glsl.cpp + tr_image.cpp + tr_image_dds.cpp + tr_init.cpp + tr_light.cpp + tr_main.cpp + tr_marks.cpp + tr_mesh.cpp + tr_model.cpp + tr_model_iqm.cpp + tr_postprocess.cpp + tr_scene.cpp + tr_shade.cpp + tr_shade_calc.cpp + tr_shader.cpp + tr_shadows.cpp + tr_sky.cpp + tr_subs.cpp + tr_surface.cpp + tr_vbo.cpp + tr_world.cpp + ${CMAKE_SOURCE_DIR}/src/common/puff.cpp + ${CMAKE_SOURCE_DIR}/src/common/q_shared.c + ${CMAKE_SOURCE_DIR}/src/common/q_math.c + ) + +if(NOT USE_RENDERER_DLOPEN) + + add_library( + renderergl2 STATIC + ${renderergl2_SRCS}) + + target_link_libraries( + renderergl2 renderercommon + ${SDL2_LIBRARIES}) + +else(NOT USE_RENDERER_DLOPEN) + + add_library( + renderergl2 SHARED + ${renderergl2_SRCS} + ) + + target_link_libraries( + renderergl2 + renderercommon + ${FRAMEWORKS} + ${OPENGL_LIBRARIES} + ${SDL2_LIBRARIES} + ) + add_custom_command( + TARGET renderergl2 + POST_BUILD COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_CURRENT_BINARY_DIR}/librenderergl2${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/renderer_opengl2${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + +endif(NOT USE_RENDERER_DLOPEN) + diff --git a/src/renderergl2/glsl/bokeh_fp.glsl b/src/renderergl2/glsl/bokeh_fp.glsl new file mode 100644 index 0000000..d08816a --- /dev/null +++ b/src/renderergl2/glsl/bokeh_fp.glsl @@ -0,0 +1,70 @@ +uniform sampler2D u_TextureMap; + +uniform vec4 u_Color; + +uniform vec2 u_InvTexRes; +varying vec2 var_TexCoords; + +void main() +{ + vec4 color; + vec2 tc; + +#if 0 + float c[7] = float[7](1.0, 0.9659258263, 0.8660254038, 0.7071067812, 0.5, 0.2588190451, 0.0); + + tc = var_TexCoords + u_InvTexRes * vec2( c[0], c[6]); color = texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[1], c[5]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[2], c[4]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[3], c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[4], c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[5], c[1]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[6], c[0]); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2( c[1], -c[5]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[2], -c[4]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[3], -c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[4], -c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[5], -c[1]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[6], -c[0]); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2( -c[0], c[6]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[1], c[5]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[2], c[4]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[3], c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[4], c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[5], c[1]); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2( -c[1], -c[5]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[4]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[3], -c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[4], -c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[5], -c[1]); color += texture2D(u_TextureMap, tc); + + gl_FragColor = color * 0.04166667 * u_Color; +#endif + + float c[5] = float[5](1.0, 0.9238795325, 0.7071067812, 0.3826834324, 0.0); + + tc = var_TexCoords + u_InvTexRes * vec2( c[0], c[4]); color = texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[1], c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[2], c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[3], c[1]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[4], c[0]); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2( c[1], -c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[2], -c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[3], -c[1]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( c[4], -c[0]); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2( -c[0], c[4]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[1], c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[2], c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[3], c[1]); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2( -c[1], -c[3]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[2]); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( -c[3], -c[1]); color += texture2D(u_TextureMap, tc); + + gl_FragColor = color * 0.0625 * u_Color; +} diff --git a/src/renderergl2/glsl/bokeh_vp.glsl b/src/renderergl2/glsl/bokeh_vp.glsl new file mode 100644 index 0000000..bdaa74a --- /dev/null +++ b/src/renderergl2/glsl/bokeh_vp.glsl @@ -0,0 +1,13 @@ +attribute vec3 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform mat4 u_ModelViewProjectionMatrix; + +varying vec2 var_TexCoords; + + +void main() +{ + gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0); + var_TexCoords = attr_TexCoord0.st; +} diff --git a/src/renderergl2/glsl/calclevels4x_fp.glsl b/src/renderergl2/glsl/calclevels4x_fp.glsl new file mode 100644 index 0000000..8246c4b --- /dev/null +++ b/src/renderergl2/glsl/calclevels4x_fp.glsl @@ -0,0 +1,60 @@ +uniform sampler2D u_TextureMap; + +uniform vec4 u_Color; + +uniform vec2 u_InvTexRes; +varying vec2 var_TexCoords; + +const vec3 LUMINANCE_VECTOR = vec3(0.2125, 0.7154, 0.0721); //vec3(0.299, 0.587, 0.114); + +vec3 GetValues(vec2 offset, vec3 current) +{ + vec2 tc = var_TexCoords + u_InvTexRes * offset; + vec3 minAvgMax = texture2D(u_TextureMap, tc).rgb; + +#ifdef FIRST_PASS + + #if defined(USE_PBR) + minAvgMax *= minAvgMax; + #endif + + float lumi = max(dot(LUMINANCE_VECTOR, minAvgMax), 0.000001); + float loglumi = clamp(log2(lumi), -10.0, 10.0); + minAvgMax = vec3(loglumi * 0.05 + 0.5); +#endif + + return vec3(min(current.x, minAvgMax.x), current.y + minAvgMax.y, max(current.z, minAvgMax.z)); +} + +void main() +{ + vec3 current = vec3(1.0, 0.0, 0.0); + +#ifdef FIRST_PASS + current = GetValues(vec2( 0.0, 0.0), current); +#else + current = GetValues(vec2(-1.5, -1.5), current); + current = GetValues(vec2(-0.5, -1.5), current); + current = GetValues(vec2( 0.5, -1.5), current); + current = GetValues(vec2( 1.5, -1.5), current); + + current = GetValues(vec2(-1.5, -0.5), current); + current = GetValues(vec2(-0.5, -0.5), current); + current = GetValues(vec2( 0.5, -0.5), current); + current = GetValues(vec2( 1.5, -0.5), current); + + current = GetValues(vec2(-1.5, 0.5), current); + current = GetValues(vec2(-0.5, 0.5), current); + current = GetValues(vec2( 0.5, 0.5), current); + current = GetValues(vec2( 1.5, 0.5), current); + + current = GetValues(vec2(-1.5, 1.5), current); + current = GetValues(vec2(-0.5, 1.5), current); + current = GetValues(vec2( 0.5, 1.5), current); + current = GetValues(vec2( 1.5, 1.5), current); + + current.y *= 0.0625; +#endif + + gl_FragColor = vec4(current, 1.0f); +} diff --git a/src/renderergl2/glsl/calclevels4x_vp.glsl b/src/renderergl2/glsl/calclevels4x_vp.glsl new file mode 100644 index 0000000..bdaa74a --- /dev/null +++ b/src/renderergl2/glsl/calclevels4x_vp.glsl @@ -0,0 +1,13 @@ +attribute vec3 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform mat4 u_ModelViewProjectionMatrix; + +varying vec2 var_TexCoords; + + +void main() +{ + gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0); + var_TexCoords = attr_TexCoord0.st; +} diff --git a/src/renderergl2/glsl/depthblur_fp.glsl b/src/renderergl2/glsl/depthblur_fp.glsl new file mode 100644 index 0000000..d71b348 --- /dev/null +++ b/src/renderergl2/glsl/depthblur_fp.glsl @@ -0,0 +1,82 @@ +uniform sampler2D u_ScreenImageMap; +uniform sampler2D u_ScreenDepthMap; + +uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height +varying vec2 var_ScreenTex; + +//float gauss[8] = float[8](0.17, 0.17, 0.16, 0.14, 0.12, 0.1, 0.08, 0.06); +//float gauss[5] = float[5](0.30, 0.23, 0.097, 0.024, 0.0033); +float gauss[4] = float[4](0.40, 0.24, 0.054, 0.0044); +//float gauss[3] = float[3](0.60, 0.19, 0.0066); +#define BLUR_SIZE 4 + +#if !defined(USE_DEPTH) +//#define USE_GAUSS +#endif + +float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNear) +{ + float sampleZDivW = texture2D(depthMap, tex).r; + return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW); +} + +vec4 depthGaussian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFarDivZNear, float zFar, vec2 scale) +{ + +#if defined(USE_DEPTH) + float depthCenter = getLinearDepth(depthMap, tex, zFarDivZNear); + vec2 slope = vec2(dFdx(depthCenter), dFdy(depthCenter)) / vec2(dFdx(tex.x), dFdy(tex.y)); + scale /= clamp(zFarDivZNear * depthCenter / 32.0, 1.0, 2.0); +#endif + +#if defined(USE_HORIZONTAL_BLUR) + vec2 direction = vec2(scale.x * 2.0, 0.0); + vec2 nudge = vec2(0.0, scale.y * 0.5); +#else // if defined(USE_VERTICAL_BLUR) + vec2 direction = vec2(0.0, scale.y * 2.0); + vec2 nudge = vec2(-scale.x * 0.5, 0.0); +#endif + +#if defined(USE_GAUSS) + vec4 result = texture2D(imageMap, tex) * gauss[0]; + float total = gauss[0]; +#else + vec4 result = texture2D(imageMap, tex); + float total = 1.0; +#endif + + float zLimit = 5.0 / zFar; + int i, j; + for (i = 0; i < 2; i++) + { + for (j = 1; j < BLUR_SIZE; j++) + { + vec2 offset = direction * (float(j) - 0.25) + nudge; +#if defined(USE_DEPTH) + float depthSample = getLinearDepth(depthMap, tex + offset, zFarDivZNear); + float depthExpected = depthCenter + dot(slope, offset); + float useSample = float(abs(depthSample - depthExpected) < zLimit); +#else + float useSample = 1.0; +#endif +#if defined(USE_GAUSS) + result += texture2D(imageMap, tex + offset) * (gauss[j] * useSample); + total += gauss[j] * useSample; +#else + result += texture2D(imageMap, tex + offset) * useSample; + total += useSample; +#endif + nudge = -nudge; + } + + direction = -direction; + nudge = -nudge; + } + + return result / total; +} + +void main() +{ + gl_FragColor = depthGaussian1D(u_ScreenImageMap, u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x, u_ViewInfo.y, u_ViewInfo.zw); +} diff --git a/src/renderergl2/glsl/depthblur_vp.glsl b/src/renderergl2/glsl/depthblur_vp.glsl new file mode 100644 index 0000000..9c47660 --- /dev/null +++ b/src/renderergl2/glsl/depthblur_vp.glsl @@ -0,0 +1,16 @@ +attribute vec4 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height + +varying vec2 var_ScreenTex; + +void main() +{ + gl_Position = attr_Position; + vec2 wh = vec2(1.0) / u_ViewInfo.zw - vec2(1.0); + var_ScreenTex = (floor(attr_TexCoord0.xy * wh) + vec2(0.5)) * u_ViewInfo.zw; + + //vec2 screenCoords = gl_Position.xy / gl_Position.w; + //var_ScreenTex = screenCoords * 0.5 + 0.5; +} diff --git a/src/renderergl2/glsl/dlight_fp.glsl b/src/renderergl2/glsl/dlight_fp.glsl new file mode 100644 index 0000000..41be049 --- /dev/null +++ b/src/renderergl2/glsl/dlight_fp.glsl @@ -0,0 +1,32 @@ +uniform sampler2D u_DiffuseMap; + +uniform int u_AlphaTest; + +varying vec2 var_Tex1; +varying vec4 var_Color; + + +void main() +{ + vec4 color = texture2D(u_DiffuseMap, var_Tex1); + + float alpha = color.a * var_Color.a; + if (u_AlphaTest == 1) + { + if (alpha == 0.0) + discard; + } + else if (u_AlphaTest == 2) + { + if (alpha >= 0.5) + discard; + } + else if (u_AlphaTest == 3) + { + if (alpha < 0.5) + discard; + } + + gl_FragColor.rgb = color.rgb * var_Color.rgb; + gl_FragColor.a = alpha; +} diff --git a/src/renderergl2/glsl/dlight_vp.glsl b/src/renderergl2/glsl/dlight_vp.glsl new file mode 100644 index 0000000..c326bd7 --- /dev/null +++ b/src/renderergl2/glsl/dlight_vp.glsl @@ -0,0 +1,92 @@ +attribute vec3 attr_Position; +attribute vec4 attr_TexCoord0; +attribute vec3 attr_Normal; + +uniform vec4 u_DlightInfo; + +#if defined(USE_DEFORM_VERTEXES) +uniform int u_DeformGen; +uniform float u_DeformParams[5]; +uniform float u_Time; +#endif + +uniform vec4 u_Color; +uniform mat4 u_ModelViewProjectionMatrix; + +varying vec2 var_Tex1; +varying vec4 var_Color; + +#if defined(USE_DEFORM_VERTEXES) +vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st) +{ + if (u_DeformGen == 0) + { + return pos; + } + + float base = u_DeformParams[0]; + float amplitude = u_DeformParams[1]; + float phase = u_DeformParams[2]; + float frequency = u_DeformParams[3]; + float spread = u_DeformParams[4]; + + if (u_DeformGen == DGEN_BULGE) + { + phase *= st.x; + } + else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH) + { + phase += dot(pos.xyz, vec3(spread)); + } + + float value = phase + (u_Time * frequency); + float func; + + if (u_DeformGen == DGEN_WAVE_SIN) + { + func = sin(value * 2.0 * M_PI); + } + else if (u_DeformGen == DGEN_WAVE_SQUARE) + { + func = sign(0.5 - fract(value)); + } + else if (u_DeformGen == DGEN_WAVE_TRIANGLE) + { + func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0; + } + else if (u_DeformGen == DGEN_WAVE_SAWTOOTH) + { + func = fract(value); + } + else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH) + { + func = (1.0 - fract(value)); + } + else // if (u_DeformGen == DGEN_BULGE) + { + func = sin(value); + } + + return pos + normal * (base + func * amplitude); +} +#endif + +void main() +{ + vec3 position = attr_Position; + vec3 normal = attr_Normal; + +#if defined(USE_DEFORM_VERTEXES) + position = DeformPosition(position, normal, attr_TexCoord0.st); +#endif + + gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0); + + vec3 dist = u_DlightInfo.xyz - position; + + var_Tex1 = dist.xy * u_DlightInfo.a + vec2(0.5); + float dlightmod = step(0.0, dot(dist, normal)); + dlightmod *= clamp(2.0 * (1.0 - abs(dist.z) * u_DlightInfo.a), 0.0, 1.0); + + var_Color = u_Color * dlightmod; +} diff --git a/src/renderergl2/glsl/down4x_fp.glsl b/src/renderergl2/glsl/down4x_fp.glsl new file mode 100644 index 0000000..0f88fb2 --- /dev/null +++ b/src/renderergl2/glsl/down4x_fp.glsl @@ -0,0 +1,34 @@ +uniform sampler2D u_TextureMap; + +uniform vec2 u_InvTexRes; +varying vec2 var_TexCoords; + +void main() +{ + vec4 color; + vec2 tc; + + tc = var_TexCoords + u_InvTexRes * vec2(-1.5, -1.5); color = texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2(-0.5, -1.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 0.5, -1.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 1.5, -1.5); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2(-1.5, -0.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2(-0.5, -0.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 0.5, -0.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 1.5, -0.5); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2(-1.5, 0.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2(-0.5, 0.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 0.5, 0.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 1.5, 0.5); color += texture2D(u_TextureMap, tc); + + tc = var_TexCoords + u_InvTexRes * vec2(-1.5, 1.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2(-0.5, 1.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 0.5, 1.5); color += texture2D(u_TextureMap, tc); + tc = var_TexCoords + u_InvTexRes * vec2( 1.5, 1.5); color += texture2D(u_TextureMap, tc); + + color *= 0.0625; + + gl_FragColor = color; +} diff --git a/src/renderergl2/glsl/down4x_vp.glsl b/src/renderergl2/glsl/down4x_vp.glsl new file mode 100644 index 0000000..bdaa74a --- /dev/null +++ b/src/renderergl2/glsl/down4x_vp.glsl @@ -0,0 +1,13 @@ +attribute vec3 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform mat4 u_ModelViewProjectionMatrix; + +varying vec2 var_TexCoords; + + +void main() +{ + gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0); + var_TexCoords = attr_TexCoord0.st; +} diff --git a/src/renderergl2/glsl/fogpass_fp.glsl b/src/renderergl2/glsl/fogpass_fp.glsl new file mode 100644 index 0000000..e2ad465 --- /dev/null +++ b/src/renderergl2/glsl/fogpass_fp.glsl @@ -0,0 +1,9 @@ +uniform vec4 u_Color; + +varying float var_Scale; + +void main() +{ + gl_FragColor = u_Color; + gl_FragColor.a = sqrt(clamp(var_Scale, 0.0, 1.0)); +} diff --git a/src/renderergl2/glsl/fogpass_vp.glsl b/src/renderergl2/glsl/fogpass_vp.glsl new file mode 100644 index 0000000..c8ec9a9 --- /dev/null +++ b/src/renderergl2/glsl/fogpass_vp.glsl @@ -0,0 +1,117 @@ +attribute vec3 attr_Position; +attribute vec3 attr_Normal; + +attribute vec4 attr_TexCoord0; + +#if defined(USE_VERTEX_ANIMATION) +attribute vec3 attr_Position2; +attribute vec3 attr_Normal2; +#endif + +uniform vec4 u_FogDistance; +uniform vec4 u_FogDepth; +uniform float u_FogEyeT; + +#if defined(USE_DEFORM_VERTEXES) +uniform int u_DeformGen; +uniform float u_DeformParams[5]; +#endif + +uniform float u_Time; +uniform mat4 u_ModelViewProjectionMatrix; + +#if defined(USE_VERTEX_ANIMATION) +uniform float u_VertexLerp; +#endif + +uniform vec4 u_Color; + +varying float var_Scale; + +#if defined(USE_DEFORM_VERTEXES) +vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st) +{ + if (u_DeformGen == 0) + { + return pos; + } + + float base = u_DeformParams[0]; + float amplitude = u_DeformParams[1]; + float phase = u_DeformParams[2]; + float frequency = u_DeformParams[3]; + float spread = u_DeformParams[4]; + + if (u_DeformGen == DGEN_BULGE) + { + phase *= st.x; + } + else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH) + { + phase += dot(pos.xyz, vec3(spread)); + } + + float value = phase + (u_Time * frequency); + float func; + + if (u_DeformGen == DGEN_WAVE_SIN) + { + func = sin(value * 2.0 * M_PI); + } + else if (u_DeformGen == DGEN_WAVE_SQUARE) + { + func = sign(0.5 - fract(value)); + } + else if (u_DeformGen == DGEN_WAVE_TRIANGLE) + { + func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0; + } + else if (u_DeformGen == DGEN_WAVE_SAWTOOTH) + { + func = fract(value); + } + else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH) + { + func = (1.0 - fract(value)); + } + else // if (u_DeformGen == DGEN_BULGE) + { + func = sin(value); + } + + return pos + normal * (base + func * amplitude); +} +#endif + +float CalcFog(vec3 position) +{ + float s = dot(vec4(position, 1.0), u_FogDistance) * 8.0; + float t = dot(vec4(position, 1.0), u_FogDepth); + + float eyeOutside = float(u_FogEyeT < 0.0); + float fogged = float(t >= eyeOutside); + + t += 1e-6; + t *= fogged / (t - u_FogEyeT * eyeOutside); + + return s * t; +} + +void main() +{ +#if defined(USE_VERTEX_ANIMATION) + vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp); + vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp); +#else + vec3 position = attr_Position; + vec3 normal = attr_Normal; +#endif + +#if defined(USE_DEFORM_VERTEXES) + position.xyz = DeformPosition(position.xyz, normal, attr_TexCoord0.st); +#endif + + gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0); + + var_Scale = CalcFog(position) * u_Color.a * u_Color.a; +} diff --git a/src/renderergl2/glsl/generic_fp.glsl b/src/renderergl2/glsl/generic_fp.glsl new file mode 100644 index 0000000..c0a4940 --- /dev/null +++ b/src/renderergl2/glsl/generic_fp.glsl @@ -0,0 +1,33 @@ +uniform sampler2D u_DiffuseMap; + +uniform int u_AlphaTest; + +varying vec2 var_DiffuseTex; + +varying vec4 var_Color; + + +void main() +{ + vec4 color = texture2D(u_DiffuseMap, var_DiffuseTex); + + float alpha = color.a * var_Color.a; + if (u_AlphaTest == 1) + { + if (alpha == 0.0) + discard; + } + else if (u_AlphaTest == 2) + { + if (alpha >= 0.5) + discard; + } + else if (u_AlphaTest == 3) + { + if (alpha < 0.5) + discard; + } + + gl_FragColor.rgb = color.rgb * var_Color.rgb; + gl_FragColor.a = alpha; +} diff --git a/src/renderergl2/glsl/generic_vp.glsl b/src/renderergl2/glsl/generic_vp.glsl new file mode 100644 index 0000000..bbe08e8 --- /dev/null +++ b/src/renderergl2/glsl/generic_vp.glsl @@ -0,0 +1,239 @@ +attribute vec3 attr_Position; +attribute vec3 attr_Normal; + +#if defined(USE_VERTEX_ANIMATION) +attribute vec3 attr_Position2; +attribute vec3 attr_Normal2; +#endif + +attribute vec4 attr_Color; +attribute vec4 attr_TexCoord0; + +#if defined(USE_TCGEN) +attribute vec4 attr_TexCoord1; +#endif + +uniform vec4 u_DiffuseTexMatrix; +uniform vec4 u_DiffuseTexOffTurb; + +#if defined(USE_TCGEN) || defined(USE_RGBAGEN) +uniform vec3 u_LocalViewOrigin; +#endif + +#if defined(USE_TCGEN) +uniform int u_TCGen0; +uniform vec3 u_TCGen0Vector0; +uniform vec3 u_TCGen0Vector1; +#endif + +#if defined(USE_FOG) +uniform vec4 u_FogDistance; +uniform vec4 u_FogDepth; +uniform float u_FogEyeT; +uniform vec4 u_FogColorMask; +#endif + +#if defined(USE_DEFORM_VERTEXES) +uniform int u_DeformGen; +uniform float u_DeformParams[5]; +uniform float u_Time; +#endif + +uniform mat4 u_ModelViewProjectionMatrix; +uniform vec4 u_BaseColor; +uniform vec4 u_VertColor; + +#if defined(USE_RGBAGEN) +uniform int u_ColorGen; +uniform int u_AlphaGen; +uniform vec3 u_AmbientLight; +uniform vec3 u_DirectedLight; +uniform vec3 u_ModelLightDir; +uniform float u_PortalRange; +#endif + +#if defined(USE_VERTEX_ANIMATION) +uniform float u_VertexLerp; +#endif + +varying vec2 var_DiffuseTex; +varying vec4 var_Color; + +#if defined(USE_DEFORM_VERTEXES) +vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st) +{ + float base = u_DeformParams[0]; + float amplitude = u_DeformParams[1]; + float phase = u_DeformParams[2]; + float frequency = u_DeformParams[3]; + float spread = u_DeformParams[4]; + + if (u_DeformGen == DGEN_BULGE) + { + phase *= st.x; + } + else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH) + { + phase += dot(pos.xyz, vec3(spread)); + } + + float value = phase + (u_Time * frequency); + float func; + + if (u_DeformGen == DGEN_WAVE_SIN) + { + func = sin(value * 2.0 * M_PI); + } + else if (u_DeformGen == DGEN_WAVE_SQUARE) + { + func = sign(fract(0.5 - value)); + } + else if (u_DeformGen == DGEN_WAVE_TRIANGLE) + { + func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0; + } + else if (u_DeformGen == DGEN_WAVE_SAWTOOTH) + { + func = fract(value); + } + else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH) + { + func = (1.0 - fract(value)); + } + else // if (u_DeformGen == DGEN_BULGE) + { + func = sin(value); + } + + return pos + normal * (base + func * amplitude); +} +#endif + +#if defined(USE_TCGEN) +vec2 GenTexCoords(int TCGen, vec3 position, vec3 normal, vec3 TCGenVector0, vec3 TCGenVector1) +{ + vec2 tex = attr_TexCoord0.st; + + if (TCGen == TCGEN_LIGHTMAP) + { + tex = attr_TexCoord1.st; + } + else if (TCGen == TCGEN_ENVIRONMENT_MAPPED) + { + vec3 viewer = normalize(u_LocalViewOrigin - position); + vec2 ref = reflect(viewer, normal).yz; + tex.s = ref.x * -0.5 + 0.5; + tex.t = ref.y * 0.5 + 0.5; + } + else if (TCGen == TCGEN_VECTOR) + { + tex = vec2(dot(position, TCGenVector0), dot(position, TCGenVector1)); + } + + return tex; +} +#endif + +#if defined(USE_TCMOD) +vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix, vec4 offTurb) +{ + float amplitude = offTurb.z; + float phase = offTurb.w * 2.0 * M_PI; + vec2 st2; + st2.x = st.x * texMatrix.x + (st.y * texMatrix.z + offTurb.x); + st2.y = st.x * texMatrix.y + (st.y * texMatrix.w + offTurb.y); + + vec2 offsetPos = vec2(position.x + position.z, position.y); + + vec2 texOffset = sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(phase)); + + return st2 + texOffset * amplitude; +} +#endif + +#if defined(USE_RGBAGEN) +vec4 CalcColor(vec3 position, vec3 normal) +{ + vec4 color = u_VertColor * attr_Color + u_BaseColor; + + if (u_ColorGen == CGEN_LIGHTING_DIFFUSE) + { + float incoming = clamp(dot(normal, u_ModelLightDir), 0.0, 1.0); + + color.rgb = clamp(u_DirectedLight * incoming + u_AmbientLight, 0.0, 1.0); + } + + vec3 viewer = u_LocalViewOrigin - position; + + if (u_AlphaGen == AGEN_LIGHTING_SPECULAR) + { + vec3 lightDir = normalize(vec3(-960.0, 1980.0, 96.0) - position); + vec3 reflected = -reflect(lightDir, normal); + + color.a = clamp(dot(reflected, normalize(viewer)), 0.0, 1.0); + color.a *= color.a; + color.a *= color.a; + } + else if (u_AlphaGen == AGEN_PORTAL) + { + color.a = clamp(length(viewer) / u_PortalRange, 0.0, 1.0); + } + + return color; +} +#endif + +#if defined(USE_FOG) +float CalcFog(vec3 position) +{ + float s = dot(vec4(position, 1.0), u_FogDistance) * 8.0; + float t = dot(vec4(position, 1.0), u_FogDepth); + + float eyeOutside = float(u_FogEyeT < 0.0); + float fogged = float(t >= eyeOutside); + + t += 1e-6; + t *= fogged / (t - u_FogEyeT * eyeOutside); + + return s * t; +} +#endif + +void main() +{ +#if defined(USE_VERTEX_ANIMATION) + vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp); + vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp); +#else + vec3 position = attr_Position; + vec3 normal = attr_Normal; +#endif + +#if defined(USE_DEFORM_VERTEXES) + position = DeformPosition(position, normal, attr_TexCoord0.st); +#endif + + gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0); + +#if defined(USE_TCGEN) + vec2 tex = GenTexCoords(u_TCGen0, position, normal, u_TCGen0Vector0, u_TCGen0Vector1); +#else + vec2 tex = attr_TexCoord0.st; +#endif + +#if defined(USE_TCMOD) + var_DiffuseTex = ModTexCoords(tex, position, u_DiffuseTexMatrix, u_DiffuseTexOffTurb); +#else + var_DiffuseTex = tex; +#endif + +#if defined(USE_RGBAGEN) + var_Color = CalcColor(position, normal); +#else + var_Color = u_VertColor * attr_Color + u_BaseColor; +#endif + +#if defined(USE_FOG) + var_Color *= vec4(1.0) - u_FogColorMask * sqrt(clamp(CalcFog(position), 0.0, 1.0)); +#endif +} diff --git a/src/renderergl2/glsl/lightall_fp.glsl b/src/renderergl2/glsl/lightall_fp.glsl new file mode 100644 index 0000000..8e7c9b4 --- /dev/null +++ b/src/renderergl2/glsl/lightall_fp.glsl @@ -0,0 +1,429 @@ +uniform sampler2D u_DiffuseMap; + +#if defined(USE_LIGHTMAP) +uniform sampler2D u_LightMap; +#endif + +#if defined(USE_NORMALMAP) +uniform sampler2D u_NormalMap; +#endif + +#if defined(USE_DELUXEMAP) +uniform sampler2D u_DeluxeMap; +#endif + +#if defined(USE_SPECULARMAP) +uniform sampler2D u_SpecularMap; +#endif + +#if defined(USE_SHADOWMAP) +uniform sampler2D u_ShadowMap; +#endif + +#if defined(USE_CUBEMAP) +uniform samplerCube u_CubeMap; +#endif + +#if defined(USE_NORMALMAP) || defined(USE_DELUXEMAP) || defined(USE_SPECULARMAP) || defined(USE_CUBEMAP) +// y = deluxe, w = cube +uniform vec4 u_EnableTextures; +#endif + +#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP) +uniform vec3 u_PrimaryLightColor; +uniform vec3 u_PrimaryLightAmbient; +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) +uniform vec4 u_NormalScale; +uniform vec4 u_SpecularScale; +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) +#if defined(USE_CUBEMAP) +uniform vec4 u_CubeMapInfo; +#endif +#endif + +uniform int u_AlphaTest; + +varying vec4 var_TexCoords; + +varying vec4 var_Color; +#if (defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)) +varying vec4 var_ColorAmbient; +#endif + +#if (defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)) +varying vec4 var_Normal; +varying vec4 var_Tangent; +varying vec4 var_Bitangent; +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) +varying vec4 var_LightDir; +#endif + +#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP) +varying vec4 var_PrimaryLightDir; +#endif + + +#define EPSILON 0.00000001 + +#if defined(USE_PARALLAXMAP) +float SampleDepth(sampler2D normalMap, vec2 t) +{ + #if defined(SWIZZLE_NORMALMAP) + return 1.0 - texture2D(normalMap, t).r; + #else + return 1.0 - texture2D(normalMap, t).a; + #endif +} + +float RayIntersectDisplaceMap(vec2 dp, vec2 ds, sampler2D normalMap) +{ + const int linearSearchSteps = 16; + const int binarySearchSteps = 6; + + // current size of search window + float size = 1.0 / float(linearSearchSteps); + + // current depth position + float depth = 0.0; + + // best match found (starts with last position 1.0) + float bestDepth = 1.0; + + // texture depth at best depth + float texDepth = 0.0; + + float prevT = SampleDepth(normalMap, dp); + float prevTexDepth = prevT; + + // search front to back for first point inside object + for(int i = 0; i < linearSearchSteps - 1; ++i) + { + depth += size; + + float t = SampleDepth(normalMap, dp + ds * depth); + + if(bestDepth > 0.996) // if no depth found yet + if(depth >= t) + { + bestDepth = depth; // store best depth + texDepth = t; + prevTexDepth = prevT; + } + prevT = t; + } + + depth = bestDepth; + +#if !defined (USE_RELIEFMAP) + float div = 1.0 / (1.0 + (prevTexDepth - texDepth) * float(linearSearchSteps)); + bestDepth -= (depth - size - prevTexDepth) * div; +#else + // recurse around first point (depth) for closest match + for(int i = 0; i < binarySearchSteps; ++i) + { + size *= 0.5; + + float t = SampleDepth(normalMap, dp + ds * depth); + + if(depth >= t) + { + bestDepth = depth; + depth -= 2.0 * size; + } + + depth += size; + } +#endif + + return bestDepth; +} +#endif + +vec3 CalcDiffuse(vec3 diffuseAlbedo, float NH, float EH, float roughness) +{ +#if defined(USE_BURLEY) + // modified from https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf + float fd90 = -0.5 + EH * EH * roughness; + float burley = 1.0 + fd90 * 0.04 / NH; + burley *= burley; + return diffuseAlbedo * burley; +#else + return diffuseAlbedo; +#endif +} + +vec3 EnvironmentBRDF(float roughness, float NE, vec3 specular) +{ + // from http://community.arm.com/servlet/JiveServlet/download/96891546-19496/siggraph2015-mmg-renaldas-slides.pdf + float v = 1.0 - max(roughness, NE); + v *= v * v; + return vec3(v) + specular; +} + +vec3 CalcSpecular(vec3 specular, float NH, float EH, float roughness) +{ + // from http://community.arm.com/servlet/JiveServlet/download/96891546-19496/siggraph2015-mmg-renaldas-slides.pdf + float rr = roughness*roughness; + float rrrr = rr*rr; + float d = (NH * NH) * (rrrr - 1.0) + 1.0; + float v = (EH * EH) * (roughness + 0.5); + return specular * (rrrr / (4.0 * d * d * v)); +} + + +float CalcLightAttenuation(float point, float normDist) +{ + // zero light at 1.0, approximating q3 style + // also don't attenuate directional light + float attenuation = (0.5 * normDist - 1.5) * point + 1.0; + + // clamp attenuation + #if defined(NO_LIGHT_CLAMP) + attenuation = max(attenuation, 0.0); + #else + attenuation = clamp(attenuation, 0.0, 1.0); + #endif + + return attenuation; +} + + +void main() +{ + vec3 viewDir, lightColor, ambientColor, reflectance; + vec3 L, N, E, H; + float NL, NH, NE, EH, attenuation; + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + mat3 tangentToWorld = mat3(var_Tangent.xyz, var_Bitangent.xyz, var_Normal.xyz); + viewDir = vec3(var_Normal.w, var_Tangent.w, var_Bitangent.w); + E = normalize(viewDir); +#endif + + lightColor = var_Color.rgb; + +#if defined(USE_LIGHTMAP) + vec4 lightmapColor = texture2D(u_LightMap, var_TexCoords.zw); + #if defined(RGBM_LIGHTMAP) + lightmapColor.rgb *= lightmapColor.a; + #endif + #if defined(USE_PBR) && !defined(USE_FAST_LIGHT) + lightmapColor.rgb *= lightmapColor.rgb; + #endif + lightColor *= lightmapColor.rgb; +#endif + + vec2 texCoords = var_TexCoords.xy; + +#if defined(USE_PARALLAXMAP) + vec3 offsetDir = viewDir * tangentToWorld; + + offsetDir.xy *= -u_NormalScale.a / offsetDir.z; + + texCoords += offsetDir.xy * RayIntersectDisplaceMap(texCoords, offsetDir.xy, u_NormalMap); +#endif + + vec4 diffuse = texture2D(u_DiffuseMap, texCoords); + + float alpha = diffuse.a * var_Color.a; + if (u_AlphaTest == 1) + { + if (alpha == 0.0) + discard; + } + else if (u_AlphaTest == 2) + { + if (alpha >= 0.5) + discard; + } + else if (u_AlphaTest == 3) + { + if (alpha < 0.5) + discard; + } + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + L = var_LightDir.xyz; + #if defined(USE_DELUXEMAP) + L += (texture2D(u_DeluxeMap, var_TexCoords.zw).xyz - vec3(0.5)) * u_EnableTextures.y; + #endif + float sqrLightDist = dot(L, L); + L /= sqrt(sqrLightDist); + + #if defined(USE_LIGHT_VECTOR) + attenuation = CalcLightAttenuation(float(var_LightDir.w > 0.0), var_LightDir.w / sqrLightDist); + #else + attenuation = 1.0; + #endif + + #if defined(USE_NORMALMAP) + #if defined(SWIZZLE_NORMALMAP) + N.xy = texture2D(u_NormalMap, texCoords).ag - vec2(0.5); + #else + N.xy = texture2D(u_NormalMap, texCoords).rg - vec2(0.5); + #endif + N.xy *= u_NormalScale.xy; + N.z = sqrt(clamp((0.25 - N.x * N.x) - N.y * N.y, 0.0, 1.0)); + N = tangentToWorld * N; + #else + N = var_Normal.xyz; + #endif + + N = normalize(N); + + #if defined(USE_SHADOWMAP) + vec2 shadowTex = gl_FragCoord.xy * r_FBufScale; + float shadowValue = texture2D(u_ShadowMap, shadowTex).r; + + // surfaces not facing the light are always shadowed + shadowValue *= clamp(dot(N, var_PrimaryLightDir.xyz), 0.0, 1.0); + + #if defined(SHADOWMAP_MODULATE) + lightColor *= shadowValue * (1.0 - u_PrimaryLightAmbient.r) + u_PrimaryLightAmbient.r; + #endif + #endif + + #if !defined(USE_LIGHT_VECTOR) + ambientColor = lightColor; + float surfNL = clamp(dot(var_Normal.xyz, L), 0.0, 1.0); + + // reserve 25% ambient to avoid black areas on normalmaps + lightColor *= 0.75; + + // Scale the incoming light to compensate for the baked-in light angle + // attenuation. + lightColor /= max(surfNL, 0.25); + + // Recover any unused light as ambient, in case attenuation is over 4x or + // light is below the surface + ambientColor = max(ambientColor - lightColor * surfNL, vec3(0.0)); + #else + ambientColor = var_ColorAmbient.rgb; + #endif + + NL = clamp(dot(N, L), 0.0, 1.0); + NE = clamp(dot(N, E), 0.0, 1.0); + + #if defined(USE_SPECULARMAP) + vec4 specular = texture2D(u_SpecularMap, texCoords); + #else + vec4 specular = vec4(1.0); + #endif + specular *= u_SpecularScale; + + #if defined(USE_PBR) + diffuse.rgb *= diffuse.rgb; + #endif + + #if defined(USE_PBR) + // diffuse rgb is base color + // specular red is gloss + // specular green is metallicness + float gloss = specular.r; + float metal = specular.g; + specular.rgb = metal * diffuse.rgb + vec3(0.04 - 0.04 * metal); + diffuse.rgb *= 1.0 - metal; + #else + // diffuse rgb is diffuse + // specular rgb is specular reflectance at normal incidence + // specular alpha is gloss + float gloss = specular.a; + + // adjust diffuse by specular reflectance, to maintain energy conservation + diffuse.rgb *= vec3(1.0) - specular.rgb; + #endif + + #if defined(GLOSS_IS_GLOSS) + float roughness = exp2(-3.0 * gloss); + #elif defined(GLOSS_IS_SMOOTHNESS) + float roughness = 1.0 - gloss; + #elif defined(GLOSS_IS_ROUGHNESS) + float roughness = gloss; + #elif defined(GLOSS_IS_SHININESS) + float roughness = pow(2.0 / (8190.0 * gloss + 2.0), 0.25); + #endif + + reflectance = CalcDiffuse(diffuse.rgb, NH, EH, roughness); + + gl_FragColor.rgb = lightColor * reflectance * (attenuation * NL); + gl_FragColor.rgb += ambientColor * diffuse.rgb; + + #if defined(USE_CUBEMAP) + reflectance = EnvironmentBRDF(roughness, NE, specular.rgb); + + vec3 R = reflect(E, N); + + // parallax corrected cubemap (cheaper trick) + // from http://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ + vec3 parallax = u_CubeMapInfo.xyz + u_CubeMapInfo.w * viewDir; + + vec3 cubeLightColor = textureCubeLod(u_CubeMap, R + parallax, ROUGHNESS_MIPS * roughness).rgb * u_EnableTextures.w; + + // normalize cubemap based on last roughness mip (~diffuse) + // multiplying cubemap values by lighting below depends on either this or the cubemap being normalized at generation + //vec3 cubeLightDiffuse = max(textureCubeLod(u_CubeMap, N, ROUGHNESS_MIPS).rgb, 0.5 / 255.0); + //cubeLightColor /= dot(cubeLightDiffuse, vec3(0.2125, 0.7154, 0.0721)); + + #if defined(USE_PBR) + cubeLightColor *= cubeLightColor; + #endif + + // multiply cubemap values by lighting + // not technically correct, but helps make reflections look less unnatural + //cubeLightColor *= lightColor * (attenuation * NL) + ambientColor; + + gl_FragColor.rgb += cubeLightColor * reflectance; + #endif + + #if defined(USE_PRIMARY_LIGHT) || defined(SHADOWMAP_MODULATE) + vec3 L2, H2; + float NL2, EH2, NH2; + + L2 = var_PrimaryLightDir.xyz; + + // enable when point lights are supported as primary lights + //sqrLightDist = dot(L2, L2); + //L2 /= sqrt(sqrLightDist); + + NL2 = clamp(dot(N, L2), 0.0, 1.0); + H2 = normalize(L2 + E); + EH2 = clamp(dot(E, H2), 0.0, 1.0); + NH2 = clamp(dot(N, H2), 0.0, 1.0); + + reflectance = CalcSpecular(specular.rgb, NH2, EH2, roughness); + + // bit of a hack, with modulated shadowmaps, ignore diffuse + #if !defined(SHADOWMAP_MODULATE) + reflectance += CalcDiffuse(diffuse.rgb, NH2, EH2, roughness); + #endif + + lightColor = u_PrimaryLightColor; + + #if defined(USE_SHADOWMAP) + lightColor *= shadowValue; + #endif + + // enable when point lights are supported as primary lights + //lightColor *= CalcLightAttenuation(float(u_PrimaryLightDir.w > 0.0), u_PrimaryLightDir.w / sqrLightDist); + + gl_FragColor.rgb += lightColor * reflectance * NL2; + #endif + + #if defined(USE_PBR) + gl_FragColor.rgb = sqrt(gl_FragColor.rgb); + #endif + +#else + + gl_FragColor.rgb = diffuse.rgb * lightColor; + +#endif + + gl_FragColor.a = alpha; +} diff --git a/src/renderergl2/glsl/lightall_vp.glsl b/src/renderergl2/glsl/lightall_vp.glsl new file mode 100644 index 0000000..e5b3c4f --- /dev/null +++ b/src/renderergl2/glsl/lightall_vp.glsl @@ -0,0 +1,246 @@ +attribute vec4 attr_TexCoord0; +#if defined(USE_LIGHTMAP) || defined(USE_TCGEN) +attribute vec4 attr_TexCoord1; +#endif +attribute vec4 attr_Color; + +attribute vec3 attr_Position; +attribute vec3 attr_Normal; +attribute vec4 attr_Tangent; + +#if defined(USE_VERTEX_ANIMATION) +attribute vec3 attr_Position2; +attribute vec3 attr_Normal2; +attribute vec4 attr_Tangent2; +#endif + +#if defined(USE_LIGHT) && !defined(USE_LIGHT_VECTOR) +attribute vec3 attr_LightDirection; +#endif + +#if defined(USE_DELUXEMAP) +uniform vec4 u_EnableTextures; // x = normal, y = deluxe, z = specular, w = cube +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) +uniform vec3 u_ViewOrigin; +#endif + +#if defined(USE_TCGEN) +uniform int u_TCGen0; +uniform vec3 u_TCGen0Vector0; +uniform vec3 u_TCGen0Vector1; +uniform vec3 u_LocalViewOrigin; +#endif + +#if defined(USE_TCMOD) +uniform vec4 u_DiffuseTexMatrix; +uniform vec4 u_DiffuseTexOffTurb; +#endif + +uniform mat4 u_ModelViewProjectionMatrix; +uniform vec4 u_BaseColor; +uniform vec4 u_VertColor; + +#if defined(USE_MODELMATRIX) +uniform mat4 u_ModelMatrix; +#endif + +#if defined(USE_VERTEX_ANIMATION) +uniform float u_VertexLerp; +#endif + +#if defined(USE_LIGHT_VECTOR) +uniform vec4 u_LightOrigin; +uniform float u_LightRadius; +uniform vec3 u_DirectedLight; +uniform vec3 u_AmbientLight; +#endif + +#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP) +uniform vec4 u_PrimaryLightOrigin; +uniform float u_PrimaryLightRadius; +#endif + +varying vec4 var_TexCoords; + +varying vec4 var_Color; +#if defined(USE_LIGHT_VECTOR) && !defined(USE_FAST_LIGHT) +varying vec4 var_ColorAmbient; +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) +varying vec4 var_Normal; +varying vec4 var_Tangent; +varying vec4 var_Bitangent; +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) +varying vec4 var_LightDir; +#endif + +#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP) +varying vec4 var_PrimaryLightDir; +#endif + +#if defined(USE_TCGEN) +vec2 GenTexCoords(int TCGen, vec3 position, vec3 normal, vec3 TCGenVector0, vec3 TCGenVector1) +{ + vec2 tex = attr_TexCoord0.st; + + if (TCGen == TCGEN_LIGHTMAP) + { + tex = attr_TexCoord1.st; + } + else if (TCGen == TCGEN_ENVIRONMENT_MAPPED) + { + vec3 viewer = normalize(u_LocalViewOrigin - position); + vec2 ref = reflect(viewer, normal).yz; + tex.s = ref.x * -0.5 + 0.5; + tex.t = ref.y * 0.5 + 0.5; + } + else if (TCGen == TCGEN_VECTOR) + { + tex = vec2(dot(position, TCGenVector0), dot(position, TCGenVector1)); + } + + return tex; +} +#endif + +#if defined(USE_TCMOD) +vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix, vec4 offTurb) +{ + float amplitude = offTurb.z; + float phase = offTurb.w * 2.0 * M_PI; + vec2 st2; + st2.x = st.x * texMatrix.x + (st.y * texMatrix.z + offTurb.x); + st2.y = st.x * texMatrix.y + (st.y * texMatrix.w + offTurb.y); + + vec2 offsetPos = vec2(position.x + position.z, position.y); + + vec2 texOffset = sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(phase)); + + return st2 + texOffset * amplitude; +} +#endif + + +float CalcLightAttenuation(float point, float normDist) +{ + // zero light at 1.0, approximating q3 style + // also don't attenuate directional light + float attenuation = (0.5 * normDist - 1.5) * point + 1.0; + + // clamp attenuation + #if defined(NO_LIGHT_CLAMP) + attenuation = max(attenuation, 0.0); + #else + attenuation = clamp(attenuation, 0.0, 1.0); + #endif + + return attenuation; +} + + +void main() +{ +#if defined(USE_VERTEX_ANIMATION) + vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp); + vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp); + #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + vec3 tangent = mix(attr_Tangent.xyz, attr_Tangent2.xyz, u_VertexLerp); + #endif +#else + vec3 position = attr_Position; + vec3 normal = attr_Normal; + #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + vec3 tangent = attr_Tangent.xyz; + #endif +#endif + +#if defined(USE_TCGEN) + vec2 texCoords = GenTexCoords(u_TCGen0, position, normal, u_TCGen0Vector0, u_TCGen0Vector1); +#else + vec2 texCoords = attr_TexCoord0.st; +#endif + +#if defined(USE_TCMOD) + var_TexCoords.xy = ModTexCoords(texCoords, position, u_DiffuseTexMatrix, u_DiffuseTexOffTurb); +#else + var_TexCoords.xy = texCoords; +#endif + + gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0); + +#if defined(USE_MODELMATRIX) + position = (u_ModelMatrix * vec4(position, 1.0)).xyz; + normal = (u_ModelMatrix * vec4(normal, 0.0)).xyz; + #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + tangent = (u_ModelMatrix * vec4(tangent, 0.0)).xyz; + #endif +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + vec3 bitangent = cross(normal, tangent) * attr_Tangent.w; +#endif + +#if defined(USE_LIGHT_VECTOR) + vec3 L = u_LightOrigin.xyz - (position * u_LightOrigin.w); +#elif defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + vec3 L = attr_LightDirection; + #if defined(USE_MODELMATRIX) + L = (u_ModelMatrix * vec4(L, 0.0)).xyz; + #endif +#endif + +#if defined(USE_LIGHTMAP) + var_TexCoords.zw = attr_TexCoord1.st; +#endif + + var_Color = u_VertColor * attr_Color + u_BaseColor; + +#if defined(USE_LIGHT_VECTOR) + #if defined(USE_FAST_LIGHT) + float sqrLightDist = dot(L, L); + float NL = clamp(dot(normalize(normal), L) / sqrt(sqrLightDist), 0.0, 1.0); + float attenuation = CalcLightAttenuation(u_LightOrigin.w, u_LightRadius * u_LightRadius / sqrLightDist); + + var_Color.rgb *= u_DirectedLight * (attenuation * NL) + u_AmbientLight; + #else + var_ColorAmbient.rgb = u_AmbientLight * var_Color.rgb; + var_Color.rgb *= u_DirectedLight; + #if defined(USE_PBR) + var_ColorAmbient.rgb *= var_ColorAmbient.rgb; + #endif + #endif +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) && defined(USE_PBR) + var_Color.rgb *= var_Color.rgb; +#endif + +#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP) + var_PrimaryLightDir.xyz = u_PrimaryLightOrigin.xyz - (position * u_PrimaryLightOrigin.w); + var_PrimaryLightDir.w = u_PrimaryLightRadius * u_PrimaryLightRadius; +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + #if defined(USE_LIGHT_VECTOR) + var_LightDir = vec4(L, u_LightRadius * u_LightRadius); + #else + var_LightDir = vec4(L, 0.0); + #endif + #if defined(USE_DELUXEMAP) + var_LightDir -= u_EnableTextures.y * var_LightDir; + #endif +#endif + +#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) + vec3 viewDir = u_ViewOrigin - position; + // store view direction in tangent space to save on varyings + var_Normal = vec4(normal, viewDir.x); + var_Tangent = vec4(tangent, viewDir.y); + var_Bitangent = vec4(bitangent, viewDir.z); +#endif +} diff --git a/src/renderergl2/glsl/pshadow_fp.glsl b/src/renderergl2/glsl/pshadow_fp.glsl new file mode 100644 index 0000000..c196f48 --- /dev/null +++ b/src/renderergl2/glsl/pshadow_fp.glsl @@ -0,0 +1,78 @@ +uniform sampler2D u_ShadowMap; + +uniform vec3 u_LightForward; +uniform vec3 u_LightUp; +uniform vec3 u_LightRight; +uniform vec4 u_LightOrigin; +uniform float u_LightRadius; +varying vec3 var_Position; +varying vec3 var_Normal; + +void main() +{ + vec3 lightToPos = var_Position - u_LightOrigin.xyz; + vec2 st = vec2(-dot(u_LightRight, lightToPos), dot(u_LightUp, lightToPos)); + + float fade = length(st); + +#if defined(USE_DISCARD) + if (fade >= 1.0) + { + discard; + } +#endif + + fade = clamp(8.0 - fade * 8.0, 0.0, 1.0); + + st = st * 0.5 + vec2(0.5); + +#if defined(USE_SOLID_PSHADOWS) + float intensity = max(sign(u_LightRadius - length(lightToPos)), 0.0); +#else + float intensity = clamp((1.0 - dot(lightToPos, lightToPos) / (u_LightRadius * u_LightRadius)) * 2.0, 0.0, 1.0); +#endif + + float lightDist = length(lightToPos); + float dist; + +#if defined(USE_DISCARD) + if (dot(u_LightForward, lightToPos) <= 0.0) + { + discard; + } + + if (dot(var_Normal, lightToPos) > 0.0) + { + discard; + } +#else + intensity *= max(sign(dot(u_LightForward, lightToPos)), 0.0); + intensity *= max(sign(-dot(var_Normal, lightToPos)), 0.0); +#endif + + intensity *= fade; + + float part; +#if defined(USE_PCF) + part = float(texture2D(u_ShadowMap, st + vec2(-1.0/512.0, -1.0/512.0)).r != 1.0); + part += float(texture2D(u_ShadowMap, st + vec2( 1.0/512.0, -1.0/512.0)).r != 1.0); + part += float(texture2D(u_ShadowMap, st + vec2(-1.0/512.0, 1.0/512.0)).r != 1.0); + part += float(texture2D(u_ShadowMap, st + vec2( 1.0/512.0, 1.0/512.0)).r != 1.0); +#else + part = float(texture2D(u_ShadowMap, st).r != 1.0); +#endif + + if (part <= 0.0) + { + discard; + } + +#if defined(USE_PCF) + intensity *= part * 0.25; +#else + intensity *= part; +#endif + + gl_FragColor.rgb = vec3(0); + gl_FragColor.a = clamp(intensity, 0.0, 0.75); +} diff --git a/src/renderergl2/glsl/pshadow_vp.glsl b/src/renderergl2/glsl/pshadow_vp.glsl new file mode 100644 index 0000000..07a4985 --- /dev/null +++ b/src/renderergl2/glsl/pshadow_vp.glsl @@ -0,0 +1,15 @@ +attribute vec3 attr_Position; +attribute vec3 attr_Normal; + +uniform mat4 u_ModelViewProjectionMatrix; +varying vec3 var_Position; +varying vec3 var_Normal; + + +void main() +{ + gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0); + + var_Position = attr_Position; + var_Normal = attr_Normal; +} diff --git a/src/renderergl2/glsl/shadowfill_fp.glsl b/src/renderergl2/glsl/shadowfill_fp.glsl new file mode 100644 index 0000000..150f3d1 --- /dev/null +++ b/src/renderergl2/glsl/shadowfill_fp.glsl @@ -0,0 +1,41 @@ +uniform vec4 u_LightOrigin; +uniform float u_LightRadius; + +varying vec3 var_Position; + +void main() +{ +#if defined(USE_DEPTH) + float depth = length(u_LightOrigin.xyz - var_Position) / u_LightRadius; + #if 0 + // 32 bit precision + const vec4 bitSh = vec4( 256 * 256 * 256, 256 * 256, 256, 1); + const vec4 bitMsk = vec4( 0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); + + vec4 comp; + comp = depth * bitSh; + comp.xyz = fract(comp.xyz); + comp -= comp.xxyz * bitMsk; + gl_FragColor = comp; + #endif + + #if 1 + // 24 bit precision + const vec3 bitSh = vec3( 256 * 256, 256, 1); + const vec3 bitMsk = vec3( 0, 1.0 / 256.0, 1.0 / 256.0); + + vec3 comp; + comp = depth * bitSh; + comp.xy = fract(comp.xy); + comp -= comp.xxy * bitMsk; + gl_FragColor = vec4(comp, 1.0); + #endif + + #if 0 + // 8 bit precision + gl_FragColor = vec4(depth, depth, depth, 1); + #endif +#else + gl_FragColor = vec4(0, 0, 0, 1); +#endif +} diff --git a/src/renderergl2/glsl/shadowfill_vp.glsl b/src/renderergl2/glsl/shadowfill_vp.glsl new file mode 100644 index 0000000..7de901b --- /dev/null +++ b/src/renderergl2/glsl/shadowfill_vp.glsl @@ -0,0 +1,89 @@ +attribute vec3 attr_Position; +attribute vec3 attr_Normal; +attribute vec4 attr_TexCoord0; + +//#if defined(USE_VERTEX_ANIMATION) +attribute vec3 attr_Position2; +attribute vec3 attr_Normal2; +//#endif + +//#if defined(USE_DEFORM_VERTEXES) +uniform int u_DeformGen; +uniform float u_DeformParams[5]; +//#endif + +uniform float u_Time; +uniform mat4 u_ModelViewProjectionMatrix; + +uniform mat4 u_ModelMatrix; + +//#if defined(USE_VERTEX_ANIMATION) +uniform float u_VertexLerp; +//#endif + +varying vec3 var_Position; + +vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st) +{ + if (u_DeformGen == 0) + { + return pos; + } + + float base = u_DeformParams[0]; + float amplitude = u_DeformParams[1]; + float phase = u_DeformParams[2]; + float frequency = u_DeformParams[3]; + float spread = u_DeformParams[4]; + + if (u_DeformGen == DGEN_BULGE) + { + phase *= st.x; + } + else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH) + { + phase += dot(pos.xyz, vec3(spread)); + } + + float value = phase + (u_Time * frequency); + float func; + + if (u_DeformGen == DGEN_WAVE_SIN) + { + func = sin(value * 2.0 * M_PI); + } + else if (u_DeformGen == DGEN_WAVE_SQUARE) + { + func = sign(0.5 - fract(value)); + } + else if (u_DeformGen == DGEN_WAVE_TRIANGLE) + { + func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0; + } + else if (u_DeformGen == DGEN_WAVE_SAWTOOTH) + { + func = fract(value); + } + else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH) + { + func = (1.0 - fract(value)); + } + else // if (u_DeformGen == DGEN_BULGE) + { + func = sin(value); + } + + return pos + normal * (base + func * amplitude); +} + +void main() +{ + vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp); + vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp); + + position = DeformPosition(position, normal, attr_TexCoord0.st); + + gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0); + + var_Position = (u_ModelMatrix * vec4(position, 1.0)).xyz; +} diff --git a/src/renderergl2/glsl/shadowmask_fp.glsl b/src/renderergl2/glsl/shadowmask_fp.glsl new file mode 100644 index 0000000..2b57e3b --- /dev/null +++ b/src/renderergl2/glsl/shadowmask_fp.glsl @@ -0,0 +1,143 @@ +uniform sampler2D u_ScreenDepthMap; + +uniform sampler2DShadow u_ShadowMap; +#if defined(USE_SHADOW_CASCADE) +uniform sampler2DShadow u_ShadowMap2; +uniform sampler2DShadow u_ShadowMap3; +uniform sampler2DShadow u_ShadowMap4; +#endif + +uniform mat4 u_ShadowMvp; +#if defined(USE_SHADOW_CASCADE) +uniform mat4 u_ShadowMvp2; +uniform mat4 u_ShadowMvp3; +uniform mat4 u_ShadowMvp4; +#endif + +uniform vec3 u_ViewOrigin; +uniform vec4 u_ViewInfo; // zfar / znear, zfar + +varying vec2 var_DepthTex; +varying vec3 var_ViewDir; + +// depth is GL_DEPTH_COMPONENT24 +// so the maximum error is 1.0 / 2^24 +#define DEPTH_MAX_ERROR 0.000000059604644775390625 + +// Input: It uses texture coords as the random number seed. +// Output: Random number: [0,1), that is between 0.0 and 0.999999... inclusive. +// Author: Michael Pohoreski +// Copyright: Copyleft 2012 :-) +// Source: http://stackoverflow.com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader + +float random( const vec2 p ) +{ + // We need irrationals for pseudo randomness. + // Most (all?) known transcendental numbers will (generally) work. + const vec2 r = vec2( + 23.1406926327792690, // e^pi (Gelfond's constant) + 2.6651441426902251); // 2^sqrt(2) (Gelfond-Schneider constant) + //return fract( cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) ); + return mod( 123456789., 1e-7 + 256. * dot(p,r) ); +} + +float PCF(const sampler2DShadow shadowmap, const vec2 st, const float dist) +{ + float mult; + float scale = 2.0 / r_shadowMapSize; + +#if 0 + // from http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html + vec2 offset = vec2(greaterThan(fract(var_DepthTex.xy * r_FBufScale * 0.5), vec2(0.25))); + offset.y += offset.x; + if (offset.y > 1.1) offset.y = 0.0; + + mult = shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, 0.5)) * scale, dist)) + + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, 0.5)) * scale, dist)) + + shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, -1.5)) * scale, dist)) + + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, -1.5)) * scale, dist)); + + mult *= 0.25; +#endif + +#if defined(USE_SHADOW_FILTER) + float r = random(var_DepthTex.xy); + float sinr = sin(r) * scale; + float cosr = cos(r) * scale; + mat2 rmat = mat2(cosr, sinr, -sinr, cosr); + + mult = shadow2D(shadowmap, vec3(st + rmat * vec2(-0.7055767, 0.196515), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.3524343, -0.7791386), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.2391056, 0.9189604), dist)); + #if defined(USE_SHADOW_FILTER2) + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.07580382, -0.09224417), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.5784913, -0.002528916), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.192888, 0.4064181), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.6335801, -0.5247476), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.5579782, 0.7491854), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.7320465, 0.6317794), dist)); + + mult *= 0.11111; + #else + mult *= 0.33333; + #endif +#else + mult = shadow2D(shadowmap, vec3(st, dist)); +#endif + + return mult; +} + +float getLinearDepth(sampler2D depthMap, vec2 tex, float zFarDivZNear) +{ + float sampleZDivW = texture2D(depthMap, tex).r - DEPTH_MAX_ERROR; + return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW); +} + +void main() +{ + float result; + + float depth = getLinearDepth(u_ScreenDepthMap, var_DepthTex, u_ViewInfo.x); + vec4 biasPos = vec4(u_ViewOrigin + var_ViewDir * (depth - 0.5 / u_ViewInfo.x), 1.0); + + vec4 shadowpos = u_ShadowMvp * biasPos; + +#if defined(USE_SHADOW_CASCADE) + if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w))))) + { +#endif + shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5); + result = PCF(u_ShadowMap, shadowpos.xy, shadowpos.z); +#if defined(USE_SHADOW_CASCADE) + } + else + { + shadowpos = u_ShadowMvp2 * biasPos; + + if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w))))) + { + shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5); + result = PCF(u_ShadowMap2, shadowpos.xy, shadowpos.z); + } + else + { + shadowpos = u_ShadowMvp3 * biasPos; + + if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w))))) + { + shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5); + result = PCF(u_ShadowMap3, shadowpos.xy, shadowpos.z); + } + else + { + shadowpos = u_ShadowMvp4 * biasPos; + shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5); + result = PCF(u_ShadowMap4, shadowpos.xy, shadowpos.z); + } + } + } +#endif + + gl_FragColor = vec4(vec3(result), 1.0); +} diff --git a/src/renderergl2/glsl/shadowmask_vp.glsl b/src/renderergl2/glsl/shadowmask_vp.glsl new file mode 100644 index 0000000..13166a2 --- /dev/null +++ b/src/renderergl2/glsl/shadowmask_vp.glsl @@ -0,0 +1,18 @@ +attribute vec4 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform vec3 u_ViewForward; +uniform vec3 u_ViewLeft; +uniform vec3 u_ViewUp; +uniform vec4 u_ViewInfo; // zfar / znear + +varying vec2 var_DepthTex; +varying vec3 var_ViewDir; + +void main() +{ + gl_Position = attr_Position; + vec2 screenCoords = gl_Position.xy / gl_Position.w; + var_DepthTex = attr_TexCoord0.xy; + var_ViewDir = u_ViewForward + u_ViewLeft * -screenCoords.x + u_ViewUp * screenCoords.y; +} diff --git a/src/renderergl2/glsl/ssao_fp.glsl b/src/renderergl2/glsl/ssao_fp.glsl new file mode 100644 index 0000000..93f6185 --- /dev/null +++ b/src/renderergl2/glsl/ssao_fp.glsl @@ -0,0 +1,86 @@ +uniform sampler2D u_ScreenDepthMap; + +uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height + +varying vec2 var_ScreenTex; + +vec2 poissonDisc[9] = vec2[9]( +vec2(-0.7055767, 0.196515), vec2(0.3524343, -0.7791386), +vec2(0.2391056, 0.9189604), vec2(-0.07580382, -0.09224417), +vec2(0.5784913, -0.002528916), vec2(0.192888, 0.4064181), +vec2(-0.6335801, -0.5247476), vec2(-0.5579782, 0.7491854), +vec2(0.7320465, 0.6317794) +); +#define NUM_SAMPLES 3 + +// Input: It uses texture coords as the random number seed. +// Output: Random number: [0,1), that is between 0.0 and 0.999999... inclusive. +// Author: Michael Pohoreski +// Copyright: Copyleft 2012 :-) +// Source: http://stackoverflow.com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader + +float random( const vec2 p ) +{ + // We need irrationals for pseudo randomness. + // Most (all?) known transcendental numbers will (generally) work. + const vec2 r = vec2( + 23.1406926327792690, // e^pi (Gelfond's constant) + 2.6651441426902251); // 2^sqrt(2) (Gelfond-Schneider constant) + //return fract( cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) ); + return mod( 123456789., 1e-7 + 256. * dot(p,r) ); +} + +mat2 randomRotation( const vec2 p ) +{ + float r = random(p); + float sinr = sin(r); + float cosr = cos(r); + return mat2(cosr, sinr, -sinr, cosr); +} + +float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNear) +{ + float sampleZDivW = texture2D(depthMap, tex).r; + return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW); +} + +float ambientOcclusion(sampler2D depthMap, const vec2 tex, const float zFarDivZNear, const float zFar, const vec2 scale) +{ + float result = 0; + + float sampleZ = getLinearDepth(depthMap, tex, zFarDivZNear); + float scaleZ = zFarDivZNear * sampleZ; + + vec2 slope = vec2(dFdx(sampleZ), dFdy(sampleZ)) / vec2(dFdx(tex.x), dFdy(tex.y)); + + if (length(slope) * zFar > 5000.0) + return 1.0; + + vec2 offsetScale = vec2(scale * 1024.0 / scaleZ); + + mat2 rmat = randomRotation(tex); + + float invZFar = 1.0 / zFar; + float zLimit = 20.0 * invZFar; + int i; + for (i = 0; i < NUM_SAMPLES; i++) + { + vec2 offset = rmat * poissonDisc[i] * offsetScale; + float sampleDiff = getLinearDepth(depthMap, tex + offset, zFarDivZNear) - sampleZ; + + bool s1 = abs(sampleDiff) > zLimit; + bool s2 = sampleDiff + invZFar > dot(slope, offset); + result += float(s1 || s2); + } + + result *= 1.0 / float(NUM_SAMPLES); + + return result; +} + +void main() +{ + float result = ambientOcclusion(u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x, u_ViewInfo.y, u_ViewInfo.wz); + + gl_FragColor = vec4(vec3(result), 1.0); +} diff --git a/src/renderergl2/glsl/ssao_vp.glsl b/src/renderergl2/glsl/ssao_vp.glsl new file mode 100644 index 0000000..9c46a79 --- /dev/null +++ b/src/renderergl2/glsl/ssao_vp.glsl @@ -0,0 +1,12 @@ +attribute vec4 attr_Position; +attribute vec4 attr_TexCoord0; + +varying vec2 var_ScreenTex; + +void main() +{ + gl_Position = attr_Position; + var_ScreenTex = attr_TexCoord0.xy; + //vec2 screenCoords = gl_Position.xy / gl_Position.w; + //var_ScreenTex = screenCoords * 0.5 + 0.5; +} diff --git a/src/renderergl2/glsl/texturecolor_fp.glsl b/src/renderergl2/glsl/texturecolor_fp.glsl new file mode 100644 index 0000000..7c9046e --- /dev/null +++ b/src/renderergl2/glsl/texturecolor_fp.glsl @@ -0,0 +1,10 @@ +uniform sampler2D u_DiffuseMap; +uniform vec4 u_Color; + +varying vec2 var_Tex1; + + +void main() +{ + gl_FragColor = texture2D(u_DiffuseMap, var_Tex1) * u_Color; +} diff --git a/src/renderergl2/glsl/texturecolor_vp.glsl b/src/renderergl2/glsl/texturecolor_vp.glsl new file mode 100644 index 0000000..552cd93 --- /dev/null +++ b/src/renderergl2/glsl/texturecolor_vp.glsl @@ -0,0 +1,13 @@ +attribute vec3 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform mat4 u_ModelViewProjectionMatrix; + +varying vec2 var_Tex1; + + +void main() +{ + gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0); + var_Tex1 = attr_TexCoord0.st; +} diff --git a/src/renderergl2/glsl/tonemap_fp.glsl b/src/renderergl2/glsl/tonemap_fp.glsl new file mode 100644 index 0000000..9e24e24 --- /dev/null +++ b/src/renderergl2/glsl/tonemap_fp.glsl @@ -0,0 +1,57 @@ +uniform sampler2D u_TextureMap; +uniform sampler2D u_LevelsMap; + +uniform vec4 u_Color; + + +uniform vec2 u_AutoExposureMinMax; +uniform vec3 u_ToneMinAvgMaxLinear; + +varying vec2 var_TexCoords; +varying float var_InvWhite; + +const vec3 LUMINANCE_VECTOR = vec3(0.2125, 0.7154, 0.0721); //vec3(0.299, 0.587, 0.114); + +float FilmicTonemap(float x) +{ + const float SS = 0.22; // Shoulder Strength + const float LS = 0.30; // Linear Strength + const float LA = 0.10; // Linear Angle + const float TS = 0.20; // Toe Strength + const float TAN = 0.01; // Toe Angle Numerator + const float TAD = 0.30; // Toe Angle Denominator + + return ((x*(SS*x+LA*LS)+TS*TAN)/(x*(SS*x+LS)+TS*TAD)) - TAN/TAD; +} + +void main() +{ + vec4 color = texture2D(u_TextureMap, var_TexCoords) * u_Color; + +#if defined(USE_PBR) + color.rgb *= color.rgb; +#endif + + vec3 minAvgMax = texture2D(u_LevelsMap, var_TexCoords).rgb; + vec3 logMinAvgMaxLum = clamp(minAvgMax * 20.0 - 10.0, -u_AutoExposureMinMax.y, -u_AutoExposureMinMax.x); + + float invAvgLum = u_ToneMinAvgMaxLinear.y * exp2(-logMinAvgMaxLum.y); + + color.rgb = color.rgb * invAvgLum - u_ToneMinAvgMaxLinear.xxx; + color.rgb = max(vec3(0.0), color.rgb); + + color.r = FilmicTonemap(color.r); + color.g = FilmicTonemap(color.g); + color.b = FilmicTonemap(color.b); + + color.rgb = clamp(color.rgb * var_InvWhite, 0.0, 1.0); + +#if defined(USE_PBR) + color.rgb = sqrt(color.rgb); +#endif + + // add a bit of dither to reduce banding + color.rgb += vec3(1.0/510.0 * mod(gl_FragCoord.x + gl_FragCoord.y, 2.0) - 1.0/1020.0); + + gl_FragColor = color; +} diff --git a/src/renderergl2/glsl/tonemap_vp.glsl b/src/renderergl2/glsl/tonemap_vp.glsl new file mode 100644 index 0000000..577c0a1 --- /dev/null +++ b/src/renderergl2/glsl/tonemap_vp.glsl @@ -0,0 +1,27 @@ +attribute vec3 attr_Position; +attribute vec4 attr_TexCoord0; + +uniform mat4 u_ModelViewProjectionMatrix; +uniform vec3 u_ToneMinAvgMaxLinear; + +varying vec2 var_TexCoords; +varying float var_InvWhite; + +float FilmicTonemap(float x) +{ + const float SS = 0.22; // Shoulder Strength + const float LS = 0.30; // Linear Strength + const float LA = 0.10; // Linear Angle + const float TS = 0.20; // Toe Strength + const float TAN = 0.01; // Toe Angle Numerator + const float TAD = 0.30; // Toe Angle Denominator + + return ((x*(SS*x+LA*LS)+TS*TAN)/(x*(SS*x+LS)+TS*TAD)) - TAN/TAD; +} + +void main() +{ + gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0); + var_TexCoords = attr_TexCoord0.st; + var_InvWhite = 1.0 / FilmicTonemap(u_ToneMinAvgMaxLinear.z - u_ToneMinAvgMaxLinear.x); +} diff --git a/src/renderergl2/tr_animation.cpp b/src/renderergl2/tr_animation.cpp new file mode 100644 index 0000000..55dddd7 --- /dev/null +++ b/src/renderergl2/tr_animation.cpp @@ -0,0 +1,525 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + + +// copied and adapted from tr_mesh.c + +/* +============= +R_MDRCullModel +============= +*/ + +static int R_MDRCullModel( mdrHeader_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdrFrame_t *oldFrame, *newFrame; + int i, frameSize; + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // compute frame pointers + newFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + oldFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.oldframe); + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + // Ummm... yeah yeah I know we don't really have an md3 here.. but we pretend + // we do. After all, the purpose of mdrs are not that different, are they? + + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_MDRComputeFogNum + +================= +*/ + +int R_MDRComputeFogNum( mdrHeader_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdrFrame_t *mdrFrame; + vec3_t localOrigin; + int frameSize; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // FIXME: non-normalized axis issues + mdrFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + VectorAdd( ent->e.origin, mdrFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdrFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdrFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + + +/* +============== +R_MDRAddAnimSurfaces +============== +*/ + +// much stuff in there is just copied from R_AddMd3Surfaces in tr_mesh.c + +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) { + mdrHeader_t *header; + mdrSurface_t *surface; + mdrLOD_t *lod; + shader_t *shader; + skin_t *skin; + int i, j; + int lodnum = 0; + int fogNum = 0; + int cull; + int cubemapIndex; + bool personalModel; + + header = (mdrHeader_t *) tr.currentModel->modelData; + + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal + || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))); + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) + { + ent->e.frame %= header->numFrames; + ent->e.oldframe %= header->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ((ent->e.frame >= header->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= header->numFrames) + || (ent->e.oldframe < 0) ) + { + ri.Printf( PRINT_DEVELOPER, "R_MDRAddAnimSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_MDRCullModel (header, ent); + if ( cull == CULL_OUT ) { + return; + } + + // figure out the current LOD of the model we're rendering, and set the lod pointer respectively. + lodnum = R_ComputeLOD(ent); + // check whether this model has as that many LODs at all. If not, try the closest thing we got. + if(header->numLODs <= 0) + return; + if(header->numLODs <= lodnum) + lodnum = header->numLODs - 1; + + lod = (mdrLOD_t *)( (byte *)header + header->ofsLODs); + for(i = 0; i < lodnum; i++) + { + lod = (mdrLOD_t *) ((byte *) lod + lod->ofsEnd); + } + + // set up lighting + if ( !personalModel || r_shadows->integer > 1 ) + { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // fogNum? + fogNum = R_MDRComputeFogNum( header, ent ); + + cubemapIndex = R_CubemapForPoint(ent->e.origin); + + surface = (mdrSurface_t *)( (byte *)lod + lod->ofsSurfaces ); + + for ( i = 0 ; i < lod->numSurfaces ; i++ ) + { + + if(ent->e.customShader) + shader = R_GetShaderByHandle(ent->e.customShader); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j].name, surface->name)) + { + shader = skin->surfaces[j].shader; + break; + } + } + } + else if(surface->shaderIndex > 0) + shader = R_GetShaderByHandle( surface->shaderIndex ); + else + shader = tr.defaultShader; + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (surfaceType_t*)surface, tr.shadowShader, 0, false, false, 0 ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (surfaceType_t*)surface, tr.projectionShadowShader, 0, false, false, 0 ); + } + + if (!personalModel) + R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, false, false, cubemapIndex ); + + surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + +/* +============== +RB_MDRSurfaceAnim +============== +*/ +void RB_MDRSurfaceAnim( mdrSurface_t *surface ) +{ + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + mdrVertex_t *v; + mdrHeader_t *header; + mdrFrame_t *frame; + mdrFrame_t *oldFrame; + mdrBone_t bones[MDR_MAX_BONES], *bonePtr, *bone; + + int frameSize; + + // don't lerp if lerping off, or this is the only frame, or the last frame... + // + if (backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame) + { + backlerp = 0; // if backlerp is 0, lerping is off and frontlerp is never used + frontlerp = 1; + } + else + { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0f - backlerp; + } + + header = (mdrHeader_t *)((byte *)surface + surface->ofsHeader); + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + frame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles * 3 ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + + // Set up all triangles. + for (j = 0 ; j < indexes ; j++) + { + tess.indexes[baseIndex + j] = baseVertex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp ) + { + // no lerping needed + bonePtr = frame->bones; + } + else + { + bonePtr = bones; + + for ( i = 0 ; i < header->numBones*12 ; i++ ) + { + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + v = (mdrVertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) + { + vec3_t tempVert, tempNormal; + mdrWeight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) + { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + R_VaoPackNormal(tess.normal[baseVertex + j], tempNormal); + + tess.texCoords[baseVertex + j][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][1] = v->texCoords[1]; + + v = (mdrVertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} + + +#define MC_MASK_X ((1<<(MC_BITS_X))-1) +#define MC_MASK_Y ((1<<(MC_BITS_Y))-1) +#define MC_MASK_Z ((1<<(MC_BITS_Z))-1) +#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1) + +#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2)) + +#define MC_POS_X (0) +#define MC_SHIFT_X (0) + +#define MC_POS_Y ((((MC_BITS_X))/8)) +#define MC_SHIFT_Y ((((MC_BITS_X)%8))) + +#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8)) +#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8))) + +#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8)) +#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8))) + +#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8)) +#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8))) + +#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8)) +#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8))) + +#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8)) +#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8))) + +#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8)) +#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8))) + +#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8)) +#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8))) + +#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8)) +#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8))) + +#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8)) +#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8))) + +#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8)) +#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8))) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp) +{ + int val; + + val=(int)((unsigned short *)(comp))[0]; + val-=1<<(MC_BITS_X-1); + mat[0][3]=((float)(val))*MC_SCALE_X; + + val=(int)((unsigned short *)(comp))[1]; + val-=1<<(MC_BITS_Y-1); + mat[1][3]=((float)(val))*MC_SCALE_Y; + + val=(int)((unsigned short *)(comp))[2]; + val-=1<<(MC_BITS_Z-1); + mat[2][3]=((float)(val))*MC_SCALE_Z; + + val=(int)((unsigned short *)(comp))[3]; + val-=1<<(MC_BITS_VECT-1); + mat[0][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[4]; + val-=1<<(MC_BITS_VECT-1); + mat[0][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[5]; + val-=1<<(MC_BITS_VECT-1); + mat[0][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[6]; + val-=1<<(MC_BITS_VECT-1); + mat[1][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[7]; + val-=1<<(MC_BITS_VECT-1); + mat[1][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[8]; + val-=1<<(MC_BITS_VECT-1); + mat[1][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[9]; + val-=1<<(MC_BITS_VECT-1); + mat[2][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[10]; + val-=1<<(MC_BITS_VECT-1); + mat[2][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[11]; + val-=1<<(MC_BITS_VECT-1); + mat[2][2]=((float)(val))*MC_SCALE_VECT; +} diff --git a/src/renderergl2/tr_backend.cpp b/src/renderergl2/tr_backend.cpp new file mode 100644 index 0000000..3459f58 --- /dev/null +++ b/src/renderergl2/tr_backend.cpp @@ -0,0 +1,1817 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" +#include "tr_fbo.h" +#include "tr_dsa.h" + +backEndData_t *backEndData; +backEndState_t backEnd; + + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +/* +** GL_BindToTMU +*/ +void GL_BindToTMU( image_t *image, int tmu ) +{ + GLuint texture = (tmu == TB_COLORMAP) ? tr.defaultImage->texnum : 0; + GLenum target = GL_TEXTURE_2D; + + if (image) + { + if (image->flags & IMGFLAG_CUBEMAP) + target = GL_TEXTURE_CUBE_MAP; + + image->frameUsed = tr.frameCount; + texture = image->texnum; + } + else + { + ri.Printf(PRINT_WARNING, "GL_BindToTMU: NULL image\n"); + } + + GL_BindMultiTexture(GL_TEXTURE0_ARB + tmu, target, texture); +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { + if ( glState.faceCulling == cullType ) { + return; + } + + if ( cullType == CT_TWO_SIDED ) + { + qglDisable( GL_CULL_FACE ); + } + else + { + bool cullFront = (cullType == CT_FRONT_SIDED); + + if ( glState.faceCulling == CT_TWO_SIDED ) + qglEnable( GL_CULL_FACE ); + + if ( glState.faceCullFront != cullFront ) + qglCullFace( cullFront ? GL_FRONT : GL_BACK ); + + glState.faceCullFront = cullFront; + } + + glState.faceCulling = cullType; +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) +{ + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) + { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_BITS ) + { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) + { + qglDepthFunc( GL_EQUAL ); + } + else if ( stateBits & GLS_DEPTHFUNC_GREATER) + { + qglDepthFunc( GL_GREATER ); + } + else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + uint32_t oldState = glState.glStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ); + uint32_t newState = stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ); + uint32_t storedState = glState.storedGlState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ); + + if (oldState == 0) + { + qglEnable( GL_BLEND ); + } + else if (newState == 0) + { + qglDisable( GL_BLEND ); + } + + if (newState != 0 && storedState != newState) + { + GLenum srcFactor = GL_ONE, dstFactor = GL_ONE; + + glState.storedGlState &= ~( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ); + glState.storedGlState |= newState; + + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + ri.Error( ERR_DROP, "GL_State: invalid src blend state bits" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits" ); + break; + } + + qglBlendFunc( srcFactor, dstFactor ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) + { + if ( stateBits & GLS_DEPTHMASK_TRUE ) + { + qglDepthMask( GL_TRUE ); + } + else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) + { + if ( stateBits & GLS_POLYMODE_LINE ) + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) + { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) + { + qglDisable( GL_DEPTH_TEST ); + } + else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + glState.glStateBits = stateBits; +} + + +void GL_SetProjectionMatrix(mat4_t matrix) +{ + Mat4Copy(matrix, glState.projection); + Mat4Multiply(glState.projection, glState.modelview, glState.modelviewProjection); +} + + +void GL_SetModelviewMatrix(mat4_t matrix) +{ + Mat4Copy(matrix, glState.modelview); + Mat4Multiply(glState.projection, glState.modelview, glState.modelviewProjection); +} + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + qglClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + backEnd.isHyperspace = true; +} + + +static void SetViewportAndScissor( void ) { + GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + + +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + int clearBits = 0; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish (); + glState.finishCalled = true; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = true; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = false; + + if (glRefConfig.framebufferObject) + { + FBO_t *fbo = backEnd.viewParms.targetFbo; + + // FIXME: HUGE HACK: render to the screen fbo if we've already postprocessed the frame and aren't drawing more world + // drawing more world check is in case of double renders, such as skyportals + if (fbo == NULL && !(backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL))) + fbo = tr.renderFbo; + + if (tr.renderCubeFbo && fbo == tr.renderCubeFbo) + { + cubemap_t *cubemap = &tr.cubemaps[backEnd.viewParms.targetFboCubemapIndex]; + FBO_AttachImage(fbo, cubemap->image, GL_COLOR_ATTACHMENT0_EXT, backEnd.viewParms.targetFboLayer); + } + + FBO_Bind(fbo); + } + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + // clear relevant buffers + clearBits = GL_DEPTH_BUFFER_BIT; + + if ( r_measureOverdraw->integer || r_shadows->integer == 2 ) + { + clearBits |= GL_STENCIL_BUFFER_BIT; + } + if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) ) + { + clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used + } + + // clear to black for cube maps + if (tr.renderCubeFbo && backEnd.viewParms.targetFbo == tr.renderCubeFbo) + { + clearBits |= GL_COLOR_BUFFER_BIT; + } + + qglClear( clearBits ); + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) + { + RB_Hyperspace(); + return; + } + else + { + backEnd.isHyperspace = false; + } + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = false; + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { +#if 0 + float plane[4]; + GLdouble plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct (backEnd.viewParms.orientation.axis[0], plane); + plane2[1] = DotProduct (backEnd.viewParms.orientation.axis[1], plane); + plane2[2] = DotProduct (backEnd.viewParms.orientation.axis[2], plane); + plane2[3] = DotProduct (plane, backEnd.viewParms.orientation.origin) - plane[3]; +#endif + GL_SetModelviewMatrix( s_flipMatrix ); + } +} + + +/* +================== +RB_RenderDrawSurfList +================== +*/ +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + int pshadowed, oldPshadowed; + int cubemapIndex, oldCubemapIndex; + bool depthRange, oldDepthRange, isCrosshair, wasCrosshair; + int i; + drawSurf_t *drawSurf; + int oldSort; + FBO_t* fbo = NULL; + bool inQuery = false; + + float depth[2]; + + + // save original time for entity shader offsets + double originalTime = backEnd.refdef.floatTime; + + fbo = glState.currentFBO; + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = false; + wasCrosshair = false; + oldDlighted = false; + oldPshadowed = false; + oldCubemapIndex = -1; + oldSort = -1; + + depth[0] = 0.f; + depth[1] = 1.f; + + backEnd.pc.c_surfaces += numDrawSurfs; + + for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) { + if ( drawSurf->sort == oldSort && drawSurf->cubemapIndex == oldCubemapIndex) { + if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE) + continue; + + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + oldSort = drawSurf->sort; + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed ); + cubemapIndex = drawSurf->cubemapIndex; + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if ( shader != NULL && ( shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted || pshadowed != oldPshadowed || cubemapIndex != oldCubemapIndex + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) ) { + if (oldShader != NULL) { + RB_EndSurface(); + } + RB_BeginSurface( shader, fogNum, cubemapIndex ); + backEnd.pc.c_surfBatches++; + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + oldPshadowed = pshadowed; + oldCubemapIndex = cubemapIndex; + } + + if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE) + continue; + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + bool sunflare = false; + depthRange = isCrosshair = false; + + if ( entityNum != REFENTITYNUM_WORLD ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + + // FIXME: e.shaderTime must be passed as int to avoid fp-precision loss issues + backEnd.refdef.floatTime = originalTime - (double)backEnd.currentEntity->e.shaderTime; + + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.orientation ); + } + + if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) + { + // hack the depth range to prevent view model from poking into walls + depthRange = true; + + if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR) + isCrosshair = true; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.orientation = backEnd.viewParms.world; + // we have to reset the shaderTime as well otherwise image animations on + // the world (like water) continue with the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.orientation ); + } + + GL_SetModelviewMatrix( backEnd.orientation.modelMatrix ); + + // + // change depthrange. Also change projection matrix so first person weapon does not look like coming + // out of the screen. + // + if (oldDepthRange != depthRange || wasCrosshair != isCrosshair) + { + if (depthRange) + { + if(backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + if(isCrosshair) + { + if(oldDepthRange) + { + // was not a crosshair but now is, change back proj matrix + GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); + } + } + else + { + viewParms_t temp = backEnd.viewParms; + + R_SetupProjection(&temp, r_znear->value, 0, false); + + GL_SetProjectionMatrix( temp.projectionMatrix ); + } + } + + if(!oldDepthRange) + { + depth[0] = 0; + depth[1] = 0.3f; + qglDepthRange (depth[0], depth[1]); + } + } + else + { + if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); + } + + if (!sunflare) + qglDepthRange (0, 1); + + depth[0] = 0; + depth[1] = 1; + } + + oldDepthRange = depthRange; + wasCrosshair = isCrosshair; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + backEnd.refdef.floatTime = originalTime; + + // draw the contents of the last shader batch + if (oldShader != NULL) { + RB_EndSurface(); + } + + if (inQuery) { + qglEndQuery(GL_SAMPLES_PASSED); + } + + if (glRefConfig.framebufferObject) + FBO_Bind(fbo); + + // go back to the world modelview matrix + + GL_SetModelviewMatrix( backEnd.viewParms.world.modelMatrix ); + + qglDepthRange (0, 1); +} + + +/* +============================================================================ + +RENDER BACK END FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +void RB_SetGL2D (void) { + mat4_t matrix; + int width, height; + + if (backEnd.projection2D && backEnd.last2DFBO == glState.currentFBO) + return; + + backEnd.projection2D = true; + backEnd.last2DFBO = glState.currentFBO; + + if (glState.currentFBO) + { + width = glState.currentFBO->width; + height = glState.currentFBO->height; + } + else + { + width = glConfig.vidWidth; + height = glConfig.vidHeight; + } + + // set 2D virtual screen size + qglViewport( 0, 0, width, height ); + qglScissor( 0, 0, width, height ); + + Mat4Ortho(0, width, height, 0, 0, 1, matrix); + GL_SetProjectionMatrix(matrix); + Mat4Identity(matrix); + GL_SetModelviewMatrix(matrix); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = ri.Milliseconds(); + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001; +} + + +/* +============= +RE_StretchRaw + +FIXME: not exactly backend +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, bool dirty) { + int i, j; + int start, end; + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + + if ( !tr.registered ) { + return; + } + R_IssuePendingRenderCommands(); + + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // we definately want to sync every frame for the cinematics + qglFinish(); + + start = 0; + if ( r_speeds->integer ) { + start = ri.Milliseconds(); + } + + // make sure rows and cols are powers of 2 + for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { + } + for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { + } + if ( ( 1 << i ) != cols || ( 1 << j ) != rows) { + ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); + } + + RE_UploadCinematic (w, h, cols, rows, data, client, dirty); + GL_BindToTMU(tr.scratchImage[client], TB_COLORMAP); + + if ( r_speeds->integer ) { + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + + // FIXME: HUGE hack + if (glRefConfig.framebufferObject) + { + FBO_Bind(backEnd.framePostProcessed ? NULL : tr.renderFbo); + } + + RB_SetGL2D(); + + VectorSet4(quadVerts[0], x, y, 0.0f, 1.0f); + VectorSet4(quadVerts[1], x + w, y, 0.0f, 1.0f); + VectorSet4(quadVerts[2], x + w, y + h, 0.0f, 1.0f); + VectorSet4(quadVerts[3], x, y + h, 0.0f, 1.0f); + + VectorSet2(texCoords[0], 0.5f / cols, 0.5f / rows); + VectorSet2(texCoords[1], (cols - 0.5f) / cols, 0.5f / rows); + VectorSet2(texCoords[2], (cols - 0.5f) / cols, (rows - 0.5f) / rows); + VectorSet2(texCoords[3], 0.5f / cols, (rows - 0.5f) / rows); + + GLSL_BindProgram(&tr.textureColorShader); + + GLSL_SetUniformMat4(&tr.textureColorShader, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec4(&tr.textureColorShader, UNIFORM_COLOR, colorWhite); + + RB_InstantQuad2(quadVerts, texCoords); +} + +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, bool dirty) { + GLuint texture; + + if (!tr.scratchImage[client]) + { + ri.Printf(PRINT_WARNING, "RE_UploadCinematic: scratch images not initialized\n"); + return; + } + + texture = tr.scratchImage[client]->texnum; + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + } +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic ( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + // FIXME: HUGE hack + if (glRefConfig.framebufferObject) + FBO_Bind(backEnd.framePostProcessed ? NULL : tr.renderFbo); + + RB_SetGL2D(); + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + { + uint16_t color[4]; + + VectorScale4(backEnd.color2D, 257, color); + + VectorCopy4(color, tess.color[ numVerts ]); + VectorCopy4(color, tess.color[ numVerts + 1]); + VectorCopy4(color, tess.color[ numVerts + 2]); + VectorCopy4(color, tess.color[ numVerts + 3 ]); + } + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0] = cmd->s1; + tess.texCoords[ numVerts ][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][1] = cmd->t2; + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + bool isShadowView; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + isShadowView = !!(backEnd.viewParms.flags & VPF_DEPTHSHADOW); + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView (); + + if (glRefConfig.framebufferObject && (backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp) + { + qglEnable(GL_DEPTH_CLAMP); + } + + if (glRefConfig.framebufferObject && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || isShadowView)) + { + FBO_t *oldFbo = glState.currentFBO; + vec4_t viewInfo; + + VectorSet4(viewInfo, backEnd.viewParms.zFar / r_znear->value, backEnd.viewParms.zFar, 0.0, 0.0); + + backEnd.depthFill = true; + qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]); + backEnd.depthFill = false; + + if (!isShadowView) + { + if (tr.msaaResolveFbo) + { + // If we're using multisampling, resolve the depth first + FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST); + } + else if (tr.renderFbo == NULL && tr.renderDepthImage) + { + // If we're rendering directly to the screen, copy the depth to a texture + // This is incredibly slow on Intel Graphics, so just skip it on there + if (!glRefConfig.intelGraphics) + qglCopyTextureSubImage2DEXT(tr.renderDepthImage->texnum, GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight); + } + + if (tr.hdrDepthFbo) + { + // need the depth in a texture we can do GL_LINEAR sampling on, so copy it to an HDR image + vec4_t srcTexCoords; + + VectorSet4(srcTexCoords, 0.0f, 0.0f, 1.0f, 1.0f); + + FBO_BlitFromTexture(tr.renderDepthImage, srcTexCoords, NULL, tr.hdrDepthFbo, NULL, NULL, NULL, 0); + } + + if (r_sunlightMode->integer && backEnd.viewParms.flags & VPF_USESUNLIGHT) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + vec4_t box; + + FBO_Bind(tr.screenShadowFbo); + + box[0] = backEnd.viewParms.viewportX * tr.screenShadowFbo->width / (float)glConfig.vidWidth; + box[1] = backEnd.viewParms.viewportY * tr.screenShadowFbo->height / (float)glConfig.vidHeight; + box[2] = backEnd.viewParms.viewportWidth * tr.screenShadowFbo->width / (float)glConfig.vidWidth; + box[3] = backEnd.viewParms.viewportHeight * tr.screenShadowFbo->height / (float)glConfig.vidHeight; + + qglViewport(box[0], box[1], box[2], box[3]); + qglScissor(box[0], box[1], box[2], box[3]); + + box[0] = backEnd.viewParms.viewportX / (float)glConfig.vidWidth; + box[1] = backEnd.viewParms.viewportY / (float)glConfig.vidHeight; + box[2] = box[0] + backEnd.viewParms.viewportWidth / (float)glConfig.vidWidth; + box[3] = box[1] + backEnd.viewParms.viewportHeight / (float)glConfig.vidHeight; + + texCoords[0][0] = box[0]; texCoords[0][1] = box[3]; + texCoords[1][0] = box[2]; texCoords[1][1] = box[3]; + texCoords[2][0] = box[2]; texCoords[2][1] = box[1]; + texCoords[3][0] = box[0]; texCoords[3][1] = box[1]; + + box[0] = -1.0f; + box[1] = -1.0f; + box[2] = 1.0f; + box[3] = 1.0f; + + VectorSet4(quadVerts[0], box[0], box[3], 0, 1); + VectorSet4(quadVerts[1], box[2], box[3], 0, 1); + VectorSet4(quadVerts[2], box[2], box[1], 0, 1); + VectorSet4(quadVerts[3], box[0], box[1], 0, 1); + + GL_State(GLS_DEPTHTEST_DISABLE); + + GLSL_BindProgram(&tr.shadowmaskShader); + + GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP); + + if (r_shadowCascadeZFar->integer != 0) + { + GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP); + GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2); + GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3); + GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP4); + + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP4, backEnd.refdef.sunShadowMvp[3]); + } + else + { + GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[3]); + } + + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWORIGIN, backEnd.refdef.vieworg); + { + vec3_t viewVector; + + float zmax = backEnd.viewParms.zFar; + float ymax = zmax * tan(backEnd.viewParms.fovY * M_PI / 360.0f); + float xmax = zmax * tan(backEnd.viewParms.fovX * M_PI / 360.0f); + + VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWFORWARD, viewVector); + VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWLEFT, viewVector); + VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWUP, viewVector); + + GLSL_SetUniformVec4(&tr.shadowmaskShader, UNIFORM_VIEWINFO, viewInfo); + } + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + + if (r_shadowBlur->integer) + { + viewInfo[2] = 1.0f / (float)(tr.screenScratchFbo->width); + viewInfo[3] = 1.0f / (float)(tr.screenScratchFbo->height); + + FBO_Bind(tr.screenScratchFbo); + + GLSL_BindProgram(&tr.depthBlurShader[0]); + + GL_BindToTMU(tr.screenShadowImage, TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo); + + RB_InstantQuad2(quadVerts, texCoords); + + FBO_Bind(tr.screenShadowFbo); + + GLSL_BindProgram(&tr.depthBlurShader[1]); + + GL_BindToTMU(tr.screenScratchImage, TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo); + + RB_InstantQuad2(quadVerts, texCoords); + } + } + + if (r_ssao->integer) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + + viewInfo[2] = 1.0f / ((float)(tr.quarterImage[0]->width) * tan(backEnd.viewParms.fovX * M_PI / 360.0f) * 2.0f); + viewInfo[3] = 1.0f / ((float)(tr.quarterImage[0]->height) * tan(backEnd.viewParms.fovY * M_PI / 360.0f) * 2.0f); + viewInfo[3] *= (float)backEnd.viewParms.viewportHeight / (float)backEnd.viewParms.viewportWidth; + + FBO_Bind(tr.quarterFbo[0]); + + qglViewport(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + qglScissor(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + + VectorSet4(quadVerts[0], -1, 1, 0, 1); + VectorSet4(quadVerts[1], 1, 1, 0, 1); + VectorSet4(quadVerts[2], 1, -1, 0, 1); + VectorSet4(quadVerts[3], -1, -1, 0, 1); + + texCoords[0][0] = 0; texCoords[0][1] = 1; + texCoords[1][0] = 1; texCoords[1][1] = 1; + texCoords[2][0] = 1; texCoords[2][1] = 0; + texCoords[3][0] = 0; texCoords[3][1] = 0; + + GL_State( GLS_DEPTHTEST_DISABLE ); + + GLSL_BindProgram(&tr.ssaoShader); + + GL_BindToTMU(tr.hdrDepthImage, TB_COLORMAP); + + GLSL_SetUniformVec4(&tr.ssaoShader, UNIFORM_VIEWINFO, viewInfo); + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + + + viewInfo[2] = 1.0f / (float)(tr.quarterImage[0]->width); + viewInfo[3] = 1.0f / (float)(tr.quarterImage[0]->height); + + FBO_Bind(tr.quarterFbo[1]); + + qglViewport(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); + qglScissor(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); + + GLSL_BindProgram(&tr.depthBlurShader[0]); + + GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo); + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + + + FBO_Bind(tr.screenSsaoFbo); + + qglViewport(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); + qglScissor(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); + + GLSL_BindProgram(&tr.depthBlurShader[1]); + + GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo); + + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + } + } + + // reset viewport and scissor + FBO_Bind(oldFbo); + SetViewportAndScissor(); + } + + if (glRefConfig.framebufferObject && (backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp) + { + qglDisable(GL_DEPTH_CLAMP); + } + + if (!isShadowView) + { + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + + if (r_drawSun->integer) + { + RB_DrawSun(0.1, tr.sunShader); + } + + if (glRefConfig.framebufferObject && r_drawSunRays->integer) + { + FBO_t *oldFbo = glState.currentFBO; + FBO_Bind(tr.sunRaysFbo); + + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + qglClear( GL_COLOR_BUFFER_BIT ); + + if (glRefConfig.occlusionQuery) + { + tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = true; + qglBeginQuery(GL_SAMPLES_PASSED, tr.sunFlareQuery[tr.sunFlareQueryIndex]); + } + + RB_DrawSun(0.3, tr.sunFlareShader); + + if (glRefConfig.occlusionQuery) + { + qglEndQuery(GL_SAMPLES_PASSED); + } + + FBO_Bind(oldFbo); + } + + // darken down any stencil shadows + RB_ShadowFinish(); + + // add light flares on lights that aren't obscured + RB_RenderFlares(); + } + + if (glRefConfig.framebufferObject && tr.renderCubeFbo && backEnd.viewParms.targetFbo == tr.renderCubeFbo) + { + cubemap_t *cubemap = &tr.cubemaps[backEnd.viewParms.targetFboCubemapIndex]; + + FBO_Bind(NULL); + if (cubemap && cubemap->image) + qglGenerateTextureMipmapEXT(cubemap->image->texnum, GL_TEXTURE_CUBE_MAP); + } + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + if (glRefConfig.framebufferObject) + FBO_Bind(NULL); + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + if ( r_clear->integer ) { + qglClearColor( 1, 0, 0.5, 1 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + return (const void *)(cmd + 1); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + int i; + image_t *image; + float x, y, w, h; + int start, end; + + RB_SetGL2D(); + + qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + + start = ri.Milliseconds(); + + for ( i=0 ; iinteger == 2 ) { + w *= image->uploadWidth / 512.0f; + h *= image->uploadHeight / 512.0f; + } + + { + vec4_t quadVerts[4]; + + GL_BindToTMU(image, TB_COLORMAP); + + VectorSet4(quadVerts[0], x, y, 0, 1); + VectorSet4(quadVerts[1], x + w, y, 0, 1); + VectorSet4(quadVerts[2], x + w, y + h, 0, 1); + VectorSet4(quadVerts[3], x, y + h, 0, 1); + + RB_InstantQuad(quadVerts); + } + } + + qglFinish(); + + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); + +} + +/* +============= +RB_ColorMask + +============= +*/ +const void *RB_ColorMask(const void *data) +{ + const colorMaskCommand_t *cmd = (colorMaskCommand_t*)data; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + if (glRefConfig.framebufferObject) + { + // reverse color mask, so 0 0 0 0 is the default + backEnd.colorMask[0] = !cmd->rgba[0]; + backEnd.colorMask[1] = !cmd->rgba[1]; + backEnd.colorMask[2] = !cmd->rgba[2]; + backEnd.colorMask[3] = !cmd->rgba[3]; + } + + qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]); + + return (const void *)(cmd + 1); +} + +/* +============= +RB_ClearDepth + +============= +*/ +const void *RB_ClearDepth(const void *data) +{ + const clearDepthCommand_t *cmd = (clearDepthCommand_t*)data; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + // texture swapping test + if (r_showImages->integer) + RB_ShowImages(); + + if (glRefConfig.framebufferObject) + { + if (!tr.renderFbo || backEnd.framePostProcessed) + { + FBO_Bind(NULL); + } + else + { + FBO_Bind(tr.renderFbo); + } + } + + qglClear(GL_DEPTH_BUFFER_BIT); + + // if we're doing MSAA, clear the depth texture for the resolve buffer + if (tr.msaaResolveFbo) + { + FBO_Bind(tr.msaaResolveFbo); + qglClear(GL_DEPTH_BUFFER_BIT); + } + + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_SwapBuffers + +============= +*/ +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = (unsigned char*)ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + ri.Hunk_FreeTempMemory( stencilReadback ); + } + + if (glRefConfig.framebufferObject) + { + if (!backEnd.framePostProcessed) + { + if (tr.msaaResolveFbo && r_hdr->integer) + { + // Resolving an RGB16F MSAA FBO to the screen messes with the brightness, so resolve to an RGB16F FBO first + FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST); + FBO_FastBlit(tr.msaaResolveFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else if (tr.renderFbo) + { + FBO_FastBlit(tr.renderFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + } + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.framePostProcessed = false; + backEnd.projection2D = false; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_CapShadowMap + +============= +*/ +const void *RB_CapShadowMap(const void *data) +{ + const capShadowmapCommand_t *cmd = (capShadowmapCommand_t*)data; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + if (cmd->map != -1) + { + if (cmd->cubeSide != -1) + { + if (tr.shadowCubemaps[cmd->map]) + { + qglCopyTextureSubImage2DEXT(tr.shadowCubemaps[cmd->map]->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + cmd->cubeSide, 0, 0, 0, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE); + } + } + else + { + if (tr.pshadowMaps[cmd->map]) + { + qglCopyTextureSubImage2DEXT(tr.pshadowMaps[cmd->map]->texnum, GL_TEXTURE_2D, 0, 0, 0, backEnd.refdef.x, glConfig.vidHeight - (backEnd.refdef.y + PSHADOW_MAP_SIZE), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE); + } + } + } + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_PostProcess + +============= +*/ +const void *RB_PostProcess(const void *data) +{ + const postProcessCommand_t *cmd = (const postProcessCommand_t*)data; + FBO_t *srcFbo; + ivec4_t srcBox, dstBox; + bool autoExposure; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + if (!glRefConfig.framebufferObject || !r_postProcess->integer) + { + // do nothing + return (const void *)(cmd + 1); + } + + if (cmd) + { + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + } + + srcFbo = tr.renderFbo; + if (tr.msaaResolveFbo) + { + // Resolve the MSAA before anything else + // Can't resolve just part of the MSAA FBO, so multiple views will suffer a performance hit here + FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST); + srcFbo = tr.msaaResolveFbo; + } + + dstBox[0] = backEnd.viewParms.viewportX; + dstBox[1] = backEnd.viewParms.viewportY; + dstBox[2] = backEnd.viewParms.viewportWidth; + dstBox[3] = backEnd.viewParms.viewportHeight; + + if (r_ssao->integer) + { + srcBox[0] = backEnd.viewParms.viewportX * tr.screenSsaoImage->width / (float)glConfig.vidWidth; + srcBox[1] = backEnd.viewParms.viewportY * tr.screenSsaoImage->height / (float)glConfig.vidHeight; + srcBox[2] = backEnd.viewParms.viewportWidth * tr.screenSsaoImage->width / (float)glConfig.vidWidth; + srcBox[3] = backEnd.viewParms.viewportHeight * tr.screenSsaoImage->height / (float)glConfig.vidHeight; + + FBO_Blit(tr.screenSsaoFbo, srcBox, NULL, srcFbo, dstBox, NULL, NULL, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO); + } + + srcBox[0] = backEnd.viewParms.viewportX; + srcBox[1] = backEnd.viewParms.viewportY; + srcBox[2] = backEnd.viewParms.viewportWidth; + srcBox[3] = backEnd.viewParms.viewportHeight; + + if (srcFbo) + { + if (r_hdr->integer && (r_toneMap->integer || r_forceToneMap->integer)) + { + autoExposure = r_autoExposure->integer || r_forceAutoExposure->integer; + RB_ToneMap(srcFbo, srcBox, NULL, dstBox, autoExposure); + } + else if (r_cameraExposure->value == 0.0f) + { + FBO_FastBlit(srcFbo, srcBox, NULL, dstBox, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else + { + vec4_t color; + + color[0] = + color[1] = + color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value); + color[3] = 1.0f; + + FBO_Blit(srcFbo, srcBox, NULL, NULL, dstBox, NULL, color, 0); + } + } + + if (r_drawSunRays->integer) + RB_SunRays(NULL, srcBox, NULL, dstBox); + + if (1) + RB_BokehBlur(NULL, srcBox, NULL, dstBox, backEnd.refdef.blurFactor); + else + RB_GaussianBlur(backEnd.refdef.blurFactor); + +#if 0 + if (0) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + ivec4_t iQtrBox; + vec4_t box; + vec4_t viewInfo; + static float scale = 5.0f; + + scale -= 0.005f; + if (scale < 0.01f) + scale = 5.0f; + + FBO_FastBlit(NULL, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + iQtrBox[0] = backEnd.viewParms.viewportX * tr.quarterImage[0]->width / (float)glConfig.vidWidth; + iQtrBox[1] = backEnd.viewParms.viewportY * tr.quarterImage[0]->height / (float)glConfig.vidHeight; + iQtrBox[2] = backEnd.viewParms.viewportWidth * tr.quarterImage[0]->width / (float)glConfig.vidWidth; + iQtrBox[3] = backEnd.viewParms.viewportHeight * tr.quarterImage[0]->height / (float)glConfig.vidHeight; + + qglViewport(iQtrBox[0], iQtrBox[1], iQtrBox[2], iQtrBox[3]); + qglScissor(iQtrBox[0], iQtrBox[1], iQtrBox[2], iQtrBox[3]); + + VectorSet4(box, 0.0f, 0.0f, 1.0f, 1.0f); + + texCoords[0][0] = box[0]; texCoords[0][1] = box[3]; + texCoords[1][0] = box[2]; texCoords[1][1] = box[3]; + texCoords[2][0] = box[2]; texCoords[2][1] = box[1]; + texCoords[3][0] = box[0]; texCoords[3][1] = box[1]; + + VectorSet4(box, -1.0f, -1.0f, 1.0f, 1.0f); + + VectorSet4(quadVerts[0], box[0], box[3], 0, 1); + VectorSet4(quadVerts[1], box[2], box[3], 0, 1); + VectorSet4(quadVerts[2], box[2], box[1], 0, 1); + VectorSet4(quadVerts[3], box[0], box[1], 0, 1); + + GL_State(GLS_DEPTHTEST_DISABLE); + + + VectorSet4(viewInfo, backEnd.viewParms.zFar / r_znear->value, backEnd.viewParms.zFar, 0.0, 0.0); + + viewInfo[2] = scale / (float)(tr.quarterImage[0]->width); + viewInfo[3] = scale / (float)(tr.quarterImage[0]->height); + + FBO_Bind(tr.quarterFbo[1]); + GLSL_BindProgram(&tr.depthBlurShader[2]); + GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP); + GLSL_SetUniformVec4(&tr.depthBlurShader[2], UNIFORM_VIEWINFO, viewInfo); + RB_InstantQuad2(quadVerts, texCoords); + + FBO_Bind(tr.quarterFbo[0]); + GLSL_BindProgram(&tr.depthBlurShader[3]); + GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP); + GLSL_SetUniformVec4(&tr.depthBlurShader[3], UNIFORM_VIEWINFO, viewInfo); + RB_InstantQuad2(quadVerts, texCoords); + + SetViewportAndScissor(); + + FBO_FastBlit(tr.quarterFbo[1], NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR); + FBO_Bind(NULL); + } +#endif + + if (0 && r_sunlightMode->integer) + { + ivec4_t dstBox; + VectorSet4(dstBox, 0, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 128, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 256, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 384, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + } + + if (0 && r_shadows->integer == 4) + { + ivec4_t dstBox; + VectorSet4(dstBox, 512 + 0, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512 + 128, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512 + 256, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512 + 384, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + } + + if (0) + { + ivec4_t dstBox; + VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256); + FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512, glConfig.vidHeight - 256, 256, 256); + FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0); + } + + if (0) + { + ivec4_t dstBox; + VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256); + FBO_BlitFromTexture(tr.sunRaysImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0); + } + +#if 0 + if (r_cubeMapping->integer && tr.numCubemaps) + { + ivec4_t dstBox; + int cubemapIndex = R_CubemapForPoint( backEnd.viewParms.orientation.origin ); + + if (cubemapIndex) + { + VectorSet4(dstBox, 0, glConfig.vidHeight - 256, 256, 256); + //FBO_BlitFromTexture(tr.renderCubeImage, NULL, NULL, NULL, dstBox, &tr.testcubeShader, NULL, 0); + FBO_BlitFromTexture(tr.cubemaps[cubemapIndex - 1].image, NULL, NULL, NULL, dstBox, &tr.testcubeShader, NULL, 0); + } + } +#endif + + backEnd.framePostProcessed = true; + + return (const void *)(cmd + 1); +} + +// FIXME: put this function declaration elsewhere +void R_SaveDDS(const char *filename, byte *pic, int width, int height, int depth); + +/* +============= +RB_ExportCubemaps + +============= +*/ +const void *RB_ExportCubemaps(const void *data) +{ + const exportCubemapsCommand_t *cmd = (const exportCubemapsCommand_t*)data; + + // finish any 2D drawing if needed + if (tess.numIndexes) + RB_EndSurface(); + + if (!glRefConfig.framebufferObject || !tr.world || tr.numCubemaps == 0) + { + // do nothing + ri.Printf(PRINT_ALL, "Nothing to export!\n"); + return (const void *)(cmd + 1); + } + + if (cmd) + { + FBO_t *oldFbo = glState.currentFBO; + int sideSize = r_cubemapSize->integer * r_cubemapSize->integer * 4; + byte *cubemapPixels = (byte*)ri.Malloc(sideSize * 6); + int i, j; + + FBO_Bind(tr.renderCubeFbo); + + for (i = 0; i < tr.numCubemaps; i++) + { + char filename[MAX_QPATH]; + cubemap_t *cubemap = &tr.cubemaps[i]; + byte *p = cubemapPixels; + + for (j = 0; j < 6; j++) + { + FBO_AttachImage(tr.renderCubeFbo, cubemap->image, GL_COLOR_ATTACHMENT0_EXT, j); + qglReadPixels(0, 0, r_cubemapSize->integer, r_cubemapSize->integer, GL_RGBA, GL_UNSIGNED_BYTE, p); + p += sideSize; + } + + if (cubemap->name[0]) + { + COM_StripExtension(cubemap->name, filename, MAX_QPATH); + Q_strcat(filename, MAX_QPATH, ".dds"); + } + else + { + Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/%03d.dds", tr.world->baseName, i); + } + + R_SaveDDS(filename, cubemapPixels, r_cubemapSize->integer, r_cubemapSize->integer, 6); + ri.Printf(PRINT_ALL, "Saved cubemap %d as %s\n", i, filename); + } + + FBO_Bind(oldFbo); + + ri.Free(cubemapPixels); + } + + return (const void *)(cmd + 1); +} + + +/* +==================== +RB_ExecuteRenderCommands +==================== +*/ +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = ri.Milliseconds (); + + while ( 1 ) { + data = PADP(data, sizeof(void *)); + + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + case RC_SCREENSHOT: + data = RB_TakeScreenshotCmd( data ); + break; + case RC_VIDEOFRAME: + data = RB_TakeVideoFrameCmd( data ); + break; + case RC_COLORMASK: + data = RB_ColorMask(data); + break; + case RC_CLEARDEPTH: + data = RB_ClearDepth(data); + break; + case RC_CAPSHADOWMAP: + data = RB_CapShadowMap(data); + break; + case RC_POSTPROCESS: + data = RB_PostProcess(data); + break; + case RC_EXPORT_CUBEMAPS: + data = RB_ExportCubemaps(data); + break; + case RC_END_OF_LIST: + default: + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + // stop rendering + t2 = ri.Milliseconds (); + backEnd.pc.msec = t2 - t1; + return; + } + } + +} diff --git a/src/renderergl2/tr_bsp.cpp b/src/renderergl2/tr_bsp.cpp new file mode 100644 index 0000000..3bf74b5 --- /dev/null +++ b/src/renderergl2/tr_bsp.cpp @@ -0,0 +1,3046 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_map.c + +#include "tr_local.h" + +#define JSON_IMPLEMENTATION +#include "qcommon/json.h" +#undef JSON_IMPLEMENTATION + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift, r, g, b; + + // shift the color data based on overbright range + shift = r_mapOverBrightBits->integer - tr.overbrightBits; + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // Minimum values + if(r < r_mapLightmapMin->integer){ + r = r_mapLightmapMin->integer; + } + if(g < r_mapLightmapMin->integer){ + g = r_mapLightmapMin->integer; + } + if(b < r_mapLightmapMin->integer){ + b = r_mapLightmapMin->integer; + } + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + + +/* +=============== +R_ColorShiftLightingFloats + +=============== +*/ +static void R_ColorShiftLightingFloats(float in[4], float out[4]) +{ + float r, g, b; + float scale = (1 << (r_mapOverBrightBits->integer - tr.overbrightBits)) / 255.0f; + + r = in[0] * scale; + g = in[1] * scale; + b = in[2] * scale; + + // Minimum values + if(r < r_mapLightmapMin->value / 255.0f){ + r = r_mapLightmapMin->value / 255.0f; + } + if(g < r_mapLightmapMin->value / 255.0f){ + g = r_mapLightmapMin->value / 255.0f; + } + if(b < r_mapLightmapMin->value / 255.0f){ + b = r_mapLightmapMin->value / 255.0f; + } + + // normalize by color instead of saturating to white + if ( r > 1 || g > 1 || b > 1 ) { + float max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r / max; + g = g / max; + b = b / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +// Modified from http://graphicrants.blogspot.jp/2009/04/rgbm-color-encoding.html +void ColorToRGBM(const vec3_t color, unsigned char rgbm[4]) +{ + vec3_t sample; + float maxComponent; + + VectorCopy(color, sample); + + maxComponent = MAX(sample[0], sample[1]); + maxComponent = MAX(maxComponent, sample[2]); + maxComponent = CLAMP(maxComponent, 1.0f/255.0f, 1.0f); + + rgbm[3] = (unsigned char) ceil(maxComponent * 255.0f); + maxComponent = 255.0f / rgbm[3]; + + VectorScale(sample, maxComponent, sample); + + rgbm[0] = (unsigned char) (sample[0] * 255); + rgbm[1] = (unsigned char) (sample[1] * 255); + rgbm[2] = (unsigned char) (sample[2] * 255); +} + +void ColorToRGB16(const vec3_t color, uint16_t rgb16[3]) +{ + rgb16[0] = color[0] * 65535.0f + 0.5f; + rgb16[1] = color[1] * 65535.0f + 0.5f; + rgb16[2] = color[2] * 65535.0f + 0.5f; +} + + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define DEFAULT_LIGHTMAP_SIZE 128 +static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) { + int/*imgFlags_t*/ imgFlags = IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE; + byte *buf, *buf_p; + dsurface_t *surf; + int len; + byte *image; + int i, j, numLightmaps, textureInternalFormat = 0; + int numLightmapsPerPage = 16; + float maxIntensity = 0; + double sumIntensity = 0; + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + R_IssuePendingRenderCommands(); + + tr.lightmapSize = DEFAULT_LIGHTMAP_SIZE; + numLightmaps = len / (tr.lightmapSize * tr.lightmapSize * 3); + + // check for deluxe mapping + if (numLightmaps <= 1) + { + tr.worldDeluxeMapping = false; + } + else + { + tr.worldDeluxeMapping = true; + for( i = 0, surf = (dsurface_t *)(fileBase + surfs->fileofs); + i < surfs->filelen / sizeof(dsurface_t); i++, surf++ ) { + int lightmapNum = LittleLong( surf->lightmapNum ); + + if ( lightmapNum >= 0 && (lightmapNum & 1) != 0 ) { + tr.worldDeluxeMapping = false; + break; + } + } + } + + image = (byte*)ri.Malloc(tr.lightmapSize * tr.lightmapSize * 4 * 2); + + if (tr.worldDeluxeMapping) + numLightmaps >>= 1; + + // Use fat lightmaps of an appropriate size. + if (r_mergeLightmaps->integer) + { + int maxLightmapsPerAxis = glConfig.maxTextureSize / tr.lightmapSize; + int lightmapCols = 4, lightmapRows = 4; + + // Increase width at first, then height. + while (lightmapCols * lightmapRows < numLightmaps && lightmapCols != maxLightmapsPerAxis) + lightmapCols <<= 1; + + while (lightmapCols * lightmapRows < numLightmaps && lightmapRows != maxLightmapsPerAxis) + lightmapRows <<= 1; + + tr.fatLightmapCols = lightmapCols; + tr.fatLightmapRows = lightmapRows; + numLightmapsPerPage = lightmapCols * lightmapRows; + + tr.numLightmaps = (numLightmaps + (numLightmapsPerPage - 1)) / numLightmapsPerPage; + } + else + { + tr.numLightmaps = numLightmaps; + } + + tr.lightmaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); + + if (tr.worldDeluxeMapping) + tr.deluxemaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); + + textureInternalFormat = GL_RGBA8; + if (r_hdr->integer) + { + // Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures. + char filename[MAX_QPATH]; + + Com_sprintf(filename, sizeof(filename), "maps/%s/lm_0000.hdr", s_worldData.baseName); + if (ri.FS_FileExists(filename)) + textureInternalFormat = GL_RGBA16; + } + + if (r_mergeLightmaps->integer) + { + int width = tr.fatLightmapCols * tr.lightmapSize; + int height = tr.fatLightmapRows * tr.lightmapSize; + + for (i = 0; i < tr.numLightmaps; i++) + { + tr.lightmaps[i] = R_CreateImage(va("_fatlightmap%d", i), NULL, width, height, IMGTYPE_COLORALPHA, imgFlags, textureInternalFormat); + + if (tr.worldDeluxeMapping) + tr.deluxemaps[i] = R_CreateImage(va("_fatdeluxemap%d", i), NULL, width, height, IMGTYPE_DELUXE, imgFlags, 0); + } + } + + for(i = 0; i < numLightmaps; i++) + { + int xoff = 0, yoff = 0; + int lightmapnum = i; + // expand the 24 bit on-disk to 32 bit + + if (r_mergeLightmaps->integer) + { + int lightmaponpage = i % numLightmapsPerPage; + xoff = (lightmaponpage % tr.fatLightmapCols) * tr.lightmapSize; + yoff = (lightmaponpage / tr.fatLightmapCols) * tr.lightmapSize; + + lightmapnum /= numLightmapsPerPage; + } + + // if (tr.worldLightmapping) + { + char filename[MAX_QPATH]; + byte *hdrLightmap = NULL; + int size = 0; + + // look for hdr lightmaps + if (textureInternalFormat == GL_RGBA16) + { + Com_sprintf( filename, sizeof( filename ), "maps/%s/lm_%04d.hdr", s_worldData.baseName, i * (tr.worldDeluxeMapping ? 2 : 1) ); + //ri.Printf(PRINT_ALL, "looking for %s\n", filename); + + size = ri.FS_ReadFile(filename, (void **)&hdrLightmap); + } + + if (hdrLightmap) + { + byte *p = hdrLightmap, *end = hdrLightmap + size; + //ri.Printf(PRINT_ALL, "found!\n"); + + /* FIXME: don't just skip over this header and actually parse it */ + while (p < end && !(*p == '\n' && *(p+1) == '\n')) + p++; + + p += 2; + + while (p < end && !(*p == '\n')) + p++; + + p++; + + if (p >= end) + ri.Error(ERR_DROP, "Bad header for %s!", filename); + + buf_p = p; + +#if 0 // HDRFILE_RGBE + if ((int)(end - hdrLightmap) != tr.lightmapSize * tr.lightmapSize * 4) + ri.Error(ERR_DROP, "Bad size for %s (%i)!", filename, size); +#else // HDRFILE_FLOAT + if ((int)(end - hdrLightmap) != tr.lightmapSize * tr.lightmapSize * 12) + ri.Error(ERR_DROP, "Bad size for %s (%i)!", filename, size); +#endif + } + else + { + int imgOffset = tr.worldDeluxeMapping ? i * 2 : i; + buf_p = buf + imgOffset * tr.lightmapSize * tr.lightmapSize * 3; + } + + for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) + { + if (hdrLightmap) + { + vec4_t color; + +#if 0 // HDRFILE_RGBE + float exponent = exp2(buf_p[j*4+3] - 128); + + color[0] = buf_p[j*4+0] * exponent; + color[1] = buf_p[j*4+1] * exponent; + color[2] = buf_p[j*4+2] * exponent; +#else // HDRFILE_FLOAT + memcpy(color, &buf_p[j*12], 12); + + color[0] = LittleFloat(color[0]); + color[1] = LittleFloat(color[1]); + color[2] = LittleFloat(color[2]); +#endif + color[3] = 1.0f; + + R_ColorShiftLightingFloats(color, color); + + ColorToRGB16(color, (uint16_t *)(&image[j * 8])); + ((uint16_t *)(&image[j * 8]))[3] = 65535; + } + else if (textureInternalFormat == GL_RGBA16) + { + vec4_t color; + + //hack: convert LDR lightmap to HDR one + color[0] = MAX(buf_p[j*3+0], 0.499f); + color[1] = MAX(buf_p[j*3+1], 0.499f); + color[2] = MAX(buf_p[j*3+2], 0.499f); + + // if under an arbitrary value (say 12) grey it out + // this prevents weird splotches in dimly lit areas + if (color[0] + color[1] + color[2] < 12.0f) + { + float avg = (color[0] + color[1] + color[2]) * 0.3333f; + color[0] = avg; + color[1] = avg; + color[2] = avg; + } + color[3] = 1.0f; + + R_ColorShiftLightingFloats(color, color); + + ColorToRGB16(color, (uint16_t *)(&image[j * 8])); + ((uint16_t *)(&image[j * 8]))[3] = 65535; + } + else + { + if ( r_lightmap->integer == 2 ) + { // color code by intensity as development tool (FIXME: check range) + float r = buf_p[j*3+0]; + float g = buf_p[j*3+1]; + float b = buf_p[j*3+2]; + float intensity; + float out[3] = {0.0, 0.0, 0.0}; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) + intensity = 1.0f; + else + intensity /= 255.0f; + + if ( intensity > maxIntensity ) + maxIntensity = intensity; + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j*4+0] = out[0] * 255; + image[j*4+1] = out[1] * 255; + image[j*4+2] = out[2] * 255; + image[j*4+3] = 255; + + sumIntensity += intensity; + } + else + { + R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] ); + image[j*4+3] = 255; + } + } + } + + if (r_mergeLightmaps->integer) + R_UpdateSubImage(tr.lightmaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize, textureInternalFormat); + else + tr.lightmaps[i] = R_CreateImage(va("*lightmap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_COLORALPHA, imgFlags, textureInternalFormat ); + + if (hdrLightmap) + ri.FS_FreeFile(hdrLightmap); + } + + if (tr.worldDeluxeMapping) + { + buf_p = buf + (i * 2 + 1) * tr.lightmapSize * tr.lightmapSize * 3; + + for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) { + image[j*4+0] = buf_p[j*3+0]; + image[j*4+1] = buf_p[j*3+1]; + image[j*4+2] = buf_p[j*3+2]; + + // make 0,0,0 into 127,127,127 + if ((image[j*4+0] == 0) && (image[j*4+1] == 0) && (image[j*4+2] == 0)) + { + image[j*4+0] = + image[j*4+1] = + image[j*4+2] = 127; + } + + image[j*4+3] = 255; + } + + if (r_mergeLightmaps->integer) + R_UpdateSubImage(tr.deluxemaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize, GL_RGBA8 ); + else + tr.deluxemaps[i] = R_CreateImage(va("*deluxemap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_DELUXE, imgFlags, 0 ); + } + } + + if ( r_lightmap->integer == 2 ) { + ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } + + ri.Free(image); +} + + +static float FatPackU(float input, int lightmapnum) +{ + if (lightmapnum < 0) + return input; + + if (tr.worldDeluxeMapping) + lightmapnum >>= 1; + + if (tr.fatLightmapCols > 0) + { + lightmapnum %= (tr.fatLightmapCols * tr.fatLightmapRows); + return (input + (lightmapnum % tr.fatLightmapCols)) / (float)(tr.fatLightmapCols); + } + + return input; +} + +static float FatPackV(float input, int lightmapnum) +{ + if (lightmapnum < 0) + return input; + + if (tr.worldDeluxeMapping) + lightmapnum >>= 1; + + if (tr.fatLightmapCols > 0) + { + lightmapnum %= (tr.fatLightmapCols * tr.fatLightmapRows); + return (input + (lightmapnum / tr.fatLightmapCols)) / (float)(tr.fatLightmapRows); + } + + return input; +} + + +static int FatLightmap(int lightmapnum) +{ + if (lightmapnum < 0) + return lightmapnum; + + if (tr.worldDeluxeMapping) + lightmapnum >>= 1; + + if (tr.fatLightmapCols > 0) + return lightmapnum / (tr.fatLightmapCols * tr.fatLightmapRows); + + return lightmapnum; +} + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + s_worldData.numClusters = LittleLong( ((int *)buf)[0] ); + s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + } else { + byte *dest = (byte*)ri.Hunk_Alloc( len - 8, h_low ); + Com_Memcpy( dest, buf + 8, len - 8 ); + s_worldData.vis = dest; + } +} + +//=============================================================================== + + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) { + shader_t *shader; + dshader_t *dsh; + + int _shaderNum = LittleLong( shaderNum ); + if ( _shaderNum < 0 || _shaderNum >= s_worldData.numShaders ) { + ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", _shaderNum ); + } + dsh = &s_worldData.shaders[ _shaderNum ]; + + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + lightmapNum = LIGHTMAP_BY_VERTEX; + } + + if ( r_fullbright->integer ) { + lightmapNum = LIGHTMAP_WHITEIMAGE; + } + + shader = R_FindShader( dsh->shader, lightmapNum, true ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +void LoadDrawVertToSrfVert(srfVert_t *s, drawVert_t *d, int realLightmapNum, float hdrVertColors[3], vec3_t *bounds) +{ + vec4_t v; + + s->xyz[0] = LittleFloat(d->xyz[0]); + s->xyz[1] = LittleFloat(d->xyz[1]); + s->xyz[2] = LittleFloat(d->xyz[2]); + + if (bounds) + AddPointToBounds(s->xyz, bounds[0], bounds[1]); + + s->st[0] = LittleFloat(d->st[0]); + s->st[1] = LittleFloat(d->st[1]); + + if (realLightmapNum >= 0) + { + s->lightmap[0] = FatPackU(LittleFloat(d->lightmap[0]), realLightmapNum); + s->lightmap[1] = FatPackV(LittleFloat(d->lightmap[1]), realLightmapNum); + } + else + { + s->lightmap[0] = LittleFloat(d->lightmap[0]); + s->lightmap[1] = LittleFloat(d->lightmap[1]); + } + + v[0] = LittleFloat(d->normal[0]); + v[1] = LittleFloat(d->normal[1]); + v[2] = LittleFloat(d->normal[2]); + + R_VaoPackNormal(s->normal, v); + + if (hdrVertColors) + { + v[0] = hdrVertColors[0]; + v[1] = hdrVertColors[1]; + v[2] = hdrVertColors[2]; + } + else + { + //hack: convert LDR vertex colors to HDR + if (r_hdr->integer) + { + v[0] = MAX(d->color[0], 0.499f); + v[1] = MAX(d->color[1], 0.499f); + v[2] = MAX(d->color[2], 0.499f); + } + else + { + v[0] = d->color[0]; + v[1] = d->color[1]; + v[2] = d->color[2]; + } + + } + v[3] = d->color[3] / 255.0f; + + R_ColorShiftLightingFloats(v, v); + R_VaoPackColor(s->color, v); +} + + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) { + int i, j; + srfBspSurface_t *cv; + glIndex_t *tri; + int numVerts, numIndexes, badTriangles; + int realLightmapNum; + + realLightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong(ds->numVerts); + if (numVerts > MAX_FACE_POINTS) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numVerts); + numVerts = MAX_FACE_POINTS; + surf->shader = tr.defaultShader; + } + + numIndexes = LittleLong(ds->numIndexes); + + //cv = ri.Hunk_Alloc(sizeof(*cv), h_low); + cv = (srfBspSurface_t*)surf->data; + cv->surfaceType = SF_FACE; + + cv->numIndexes = numIndexes; + cv->indexes = (glIndex_t*)ri.Hunk_Alloc(numIndexes * sizeof(cv->indexes[0]), h_low); + + cv->numVerts = numVerts; + cv->verts = (srfVert_t*)ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low); + + // copy vertexes + surf->cullinfo.type = CULLINFO_PLANE | CULLINFO_BOX; + ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]); + verts += LittleLong(ds->firstVert); + for(i = 0; i < numVerts; i++) + LoadDrawVertToSrfVert(&cv->verts[i], &verts[i], realLightmapNum, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, surf->cullinfo.bounds); + + // copy triangles + badTriangles = 0; + indexes += LittleLong(ds->firstIndex); + for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3) + { + for(j = 0; j < 3; j++) + { + tri[j] = LittleLong(indexes[i + j]); + + if(tri[j] >= numVerts) + { + ri.Error(ERR_DROP, "Bad index in face surface"); + } + } + + if ((tri[0] == tri[1]) || (tri[1] == tri[2]) || (tri[0] == tri[2])) + { + tri -= 3; + badTriangles++; + } + } + + if (badTriangles) + { + ri.Printf(PRINT_WARNING, "Face has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numIndexes / 3, numVerts, numIndexes / 3 - badTriangles); + cv->numIndexes -= badTriangles * 3; + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->cullPlane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->cullPlane.dist = DotProduct( cv->verts[0].xyz, cv->cullPlane.normal ); + SetPlaneSignbits( &cv->cullPlane ); + cv->cullPlane.type = PlaneTypeForNormal( cv->cullPlane.normal ); + surf->cullinfo.plane = cv->cullPlane; + + surf->data = (surfaceType_t *)cv; + + // Calculate tangent spaces + { + srfVert_t *dv[3]; + + for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3) + { + dv[0] = &cv->verts[tri[0]]; + dv[1] = &cv->verts[tri[1]]; + dv[2] = &cv->verts[tri[2]]; + + R_CalcTangentVectors(dv); + } + } +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf ) { + srfBspSurface_t *grid = (srfBspSurface_t *)surf->data; + int i; + int width, height, numPoints; + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE]; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + int realLightmapNum; + + realLightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + if(width < 0 || width > MAX_PATCH_SIZE || height < 0 || height > MAX_PATCH_SIZE) + ri.Error(ERR_DROP, "ParseMesh: bad size"); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for(i = 0; i < numPoints; i++) + LoadDrawVertToSrfVert(&points[i], &verts[i], realLightmapNum, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, NULL); + + // pre-tesseleate + R_SubdividePatchToGrid( grid, width, height, points ); + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); + + surf->cullinfo.type = CULLINFO_BOX | CULLINFO_SPHERE; + VectorCopy(grid->cullBounds[0], surf->cullinfo.bounds[0]); + VectorCopy(grid->cullBounds[1], surf->cullinfo.bounds[1]); + VectorCopy(grid->cullOrigin, surf->cullinfo.localOrigin); + surf->cullinfo.radius = grid->cullRadius; +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) { + srfBspSurface_t *cv; + glIndex_t *tri; + int i, j; + int numVerts, numIndexes, badTriangles; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong(ds->numVerts); + numIndexes = LittleLong(ds->numIndexes); + + //cv = ri.Hunk_Alloc(sizeof(*cv), h_low); + cv = (srfBspSurface_t*)surf->data; + cv->surfaceType = SF_TRIANGLES; + + cv->numIndexes = numIndexes; + cv->indexes = (glIndex_t*)ri.Hunk_Alloc(numIndexes * sizeof(cv->indexes[0]), h_low); + + cv->numVerts = numVerts; + cv->verts = (srfVert_t*)ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low); + + surf->data = (surfaceType_t *) cv; + + // copy vertexes + surf->cullinfo.type = CULLINFO_BOX; + ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]); + verts += LittleLong(ds->firstVert); + for(i = 0; i < numVerts; i++) + LoadDrawVertToSrfVert(&cv->verts[i], &verts[i], -1, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, surf->cullinfo.bounds); + + // copy triangles + badTriangles = 0; + indexes += LittleLong(ds->firstIndex); + for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3) + { + for(j = 0; j < 3; j++) + { + tri[j] = LittleLong(indexes[i + j]); + + if(tri[j] >= numVerts) + { + ri.Error(ERR_DROP, "Bad index in face surface"); + } + } + + if ((tri[0] == tri[1]) || (tri[1] == tri[2]) || (tri[0] == tri[2])) + { + tri -= 3; + badTriangles++; + } + } + + if (badTriangles) + { + ri.Printf(PRINT_WARNING, "Trisurf has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numIndexes / 3, numVerts, numIndexes / 3 - badTriangles); + cv->numIndexes -= badTriangles * 3; + } + + // Calculate tangent spaces + { + srfVert_t *dv[3]; + + for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3) + { + dv[0] = &cv->verts[tri[0]]; + dv[1] = &cv->verts[tri[1]]; + dv[2] = &cv->verts[tri[2]]; + + R_CalcTangentVectors(dv); + } + } +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfFlare_t *flare; + int i; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + //flare = ri.Hunk_Alloc( sizeof( *flare ), h_low ); + flare = (srfFlare_t*)surf->data; + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + + surf->cullinfo.type = CULLINFO_NONE; +} + + +/* +================= +R_MergedWidthPoints + +returns true if there are grid points merged on a width edge +================= +*/ +int R_MergedWidthPoints(srfBspSurface_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->width-1; i++) { + for (j = i + 1; j < grid->width-1; j++) { + if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue; + return true; + } + } + return false; +} + +/* +================= +R_MergedHeightPoints + +returns true if there are grid points merged on a height edge +================= +*/ +int R_MergedHeightPoints(srfBspSurface_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->height-1; i++) { + for (j = i + 1; j < grid->height-1; j++) { + if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue; + return true; + } + } + return false; +} + +/* +================= +R_FixSharedVertexLodError_r + +NOTE: never sync LoD through grid edges with merged points! + +FIXME: write generalized version that also avoids cracks between a patch and one that meets half way? +================= +*/ +void R_FixSharedVertexLodError_r( int start, srfBspSurface_t *grid1 ) { + int j, k, l, m, n, offset1, offset2, touch; + srfBspSurface_t *grid2; + + for ( j = start; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfBspSurface_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // if the LOD errors are already fixed for this patch + if ( grid2->lodFixed == 2 ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + touch = false; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->width-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->widthLodError[k]; + touch = true; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->widthLodError[k]; + touch = true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->height-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->heightLodError[k]; + touch = true; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->heightLodError[k]; + touch = true; + } + } + } + } + if (touch) { + grid2->lodFixed = 2; + R_FixSharedVertexLodError_r ( start, grid2 ); + //NOTE: this would be correct but makes things really slow + //grid2->lodFixed = 1; + } + } +} + +/* +================= +R_FixSharedVertexLodError + +This function assumes that all patches in one group are nicely stitched together for the highest LoD. +If this is not the case this function will still do its job but won't fix the highest LoD cracks. +================= +*/ +void R_FixSharedVertexLodError( void ) { + int i; + srfBspSurface_t *grid1; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfBspSurface_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodFixed ) + continue; + // + grid1->lodFixed = 2; + // recursively fix other patches in the same LOD group + R_FixSharedVertexLodError_r( i + 1, grid1); + } +} + + +/* +=============== +R_StitchPatches +=============== +*/ +int R_StitchPatches( int grid1num, int grid2num ) { + float *v1, *v2; + srfBspSurface_t *grid1, *grid2; + int k, l, m, n, offset1, offset2, row, column; + + grid1 = (srfBspSurface_t *) s_worldData.surfaces[grid1num].data; + grid2 = (srfBspSurface_t *) s_worldData.surfaces[grid2num].data; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->width-2; k += 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + R_GridInsertRow( grid2, l+1, column, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->height-2; k += 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = grid1->width-1; k > 1; k -= 2) { + + for (m = 0; m < 2; m++) { + + if ( !grid2 || grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (!grid2 || grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + R_GridInsertRow( grid2, l+1, column, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + if (!grid2) + break; + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = grid1->height-1; k > 1; k -= 2) { + for (m = 0; m < 2; m++) { + + if (!grid2 || grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + for (m = 0; m < 2; m++) { + + if (!grid2 || grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = false; + s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2; + return true; + } + } + } + } + return false; +} + +/* +=============== +R_TryStitchPatch + +This function will try to stitch patches in the same LoD group together for the highest LoD. + +Only single missing vertice cracks will be fixed. + +Vertices will be joined at the patch side a crack is first found, at the other side +of the patch (on the same row or column) the vertices will not be joined and cracks +might still appear at that side. +=============== +*/ +int R_TryStitchingPatch( int grid1num ) { + int j, numstitches; + srfBspSurface_t *grid1, *grid2; + + numstitches = 0; + grid1 = (srfBspSurface_t *) s_worldData.surfaces[grid1num].data; + for ( j = 0; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfBspSurface_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + while (R_StitchPatches(grid1num, j)) + { + numstitches++; + } + } + return numstitches; +} + +/* +=============== +R_StitchAllPatches +=============== +*/ +void R_StitchAllPatches( void ) { + int i, stitched, numstitches; + srfBspSurface_t *grid1; + + numstitches = 0; + do + { + stitched = false; + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfBspSurface_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodStitched ) + continue; + // + grid1->lodStitched = true; + stitched = true; + // + numstitches += R_TryStitchingPatch( i ); + } + } + while (stitched); + ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches ); +} + +/* +=============== +R_MovePatchSurfacesToHunk +=============== +*/ +void R_MovePatchSurfacesToHunk(void) { + int i; + srfBspSurface_t *grid; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + void *copyFrom; + // + grid = (srfBspSurface_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid->surfaceType != SF_GRID ) + continue; + // + + copyFrom = grid->widthLodError; + grid->widthLodError = (float*)ri.Hunk_Alloc( grid->width * 4, h_low ); + Com_Memcpy(grid->widthLodError, copyFrom, grid->width * 4); + ri.Free(copyFrom); + + copyFrom = grid->heightLodError; + grid->heightLodError = (float*)ri.Hunk_Alloc(grid->height * 4, h_low); + Com_Memcpy(grid->heightLodError, copyFrom, grid->height * 4); + ri.Free(copyFrom); + + copyFrom = grid->indexes; + grid->indexes = (glIndex_t*)ri.Hunk_Alloc(grid->numIndexes * sizeof(glIndex_t), h_low); + Com_Memcpy(grid->indexes, copyFrom, grid->numIndexes * sizeof(glIndex_t)); + ri.Free(copyFrom); + + copyFrom = grid->verts; + grid->verts = (srfVert_t*)ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low); + Com_Memcpy(grid->verts, copyFrom, grid->numVerts * sizeof(srfVert_t)); + ri.Free(copyFrom); + } +} + + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) { + dsurface_t *in; + msurface_t *out; + drawVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + float *hdrVertColors = NULL; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + if (surfs->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = surfs->filelen / sizeof(*in); + + dv = (drawVert_t*)(fileBase + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (int*)(fileBase + indexLump->fileofs); + if ( indexLump->filelen % sizeof(*indexes)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + out = (msurface_t*)ri.Hunk_Alloc ( count * sizeof(*out), h_low ); + + s_worldData.surfaces = out; + s_worldData.numsurfaces = count; + s_worldData.surfacesViewCount = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesViewCount), h_low ); + s_worldData.surfacesDlightBits = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesDlightBits), h_low ); + s_worldData.surfacesPshadowBits = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesPshadowBits), h_low ); + + // load hdr vertex colors + if (r_hdr->integer) + { + char filename[MAX_QPATH]; + int size; + + Com_sprintf( filename, sizeof( filename ), "maps/%s/vertlight.raw", s_worldData.baseName); + //ri.Printf(PRINT_ALL, "looking for %s\n", filename); + + size = ri.FS_ReadFile(filename, (void **)&hdrVertColors); + + if (hdrVertColors) + { + //ri.Printf(PRINT_ALL, "Found!\n"); + if (size != sizeof(float) * 3 * (verts->filelen / sizeof(*dv))) + ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!", filename, size, (int)((sizeof(float)) * 3 * (verts->filelen / sizeof(*dv)))); + } + } + + + // Two passes, allocate surfaces first, then load them full of data + // This ensures surfaces are close together to reduce L2 cache misses when using VAOs, + // which don't actually use the verts and indexes + in = (dsurface_t*)(fileBase + surfs->fileofs); + out = s_worldData.surfaces; + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low); + break; + case MST_TRIANGLE_SOUP: + out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low); + break; + case MST_PLANAR: + out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low); + break; + case MST_FLARE: + out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfFlare_t), h_low); + break; + default: + break; + } + } + + in = (dsurface_t*)(fileBase + surfs->fileofs); + out = s_worldData.surfaces; + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh ( in, dv, hdrVertColors, out ); + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, hdrVertColors, out, indexes ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, hdrVertColors, out, indexes ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes ); + numFlares++; + break; + default: + ri.Error( ERR_DROP, "Bad surfaceType" ); + } + } + + if (hdrVertColors) + { + ri.FS_FreeFile(hdrVertColors); + } + +#ifdef PATCH_STITCHING + R_StitchAllPatches(); +#endif + + R_FixSharedVertexLodError(); + +#ifdef PATCH_STITCHING + R_MovePatchSurfacesToHunk(); +#endif + + ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", + numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (dmodel_t*)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + + s_worldData.numBModels = count; + s_worldData.bmodels = out = (bmodel_t*)ri.Hunk_Alloc( count * sizeof(*out), h_low ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = LittleFloat (in->mins[j]); + out->bounds[1][j] = LittleFloat (in->maxs[j]); + } + + out->firstSurface = LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + + if(i == 0) + { + // Add this for limiting VAO surface creation + s_worldData.numWorldSurfaces = out->numSurfaces; + } + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = (dnode_t*)(fileBase + nodeLump->fileofs); + if (nodeLump->filelen % sizeof(dnode_t) || + leafLump->filelen % sizeof(dleaf_t) ) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + numNodes = nodeLump->filelen / sizeof(dnode_t); + numLeafs = leafLump->filelen / sizeof(dleaf_t); + + out = (mnode_t*)ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low); + + s_worldData.nodes = out; + s_worldData.numnodes = numNodes + numLeafs; + s_worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i=0 ; imins[j] = LittleLong (in->mins[j]); + out->maxs[j] = LittleLong (in->maxs[j]); + } + + p = LittleLong(in->planeNum); + out->plane = s_worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = LittleLong (in->children[j]); + if (p >= 0) + out->children[j] = s_worldData.nodes + p; + else + out->children[j] = s_worldData.nodes + numNodes + (-1 - p); + } + } + + // load leafs + inLeaf = (dleaf_t*)(fileBase + leafLump->fileofs); + for ( i=0 ; imins[j] = LittleLong (inLeaf->mins[j]); + out->maxs[j] = LittleLong (inLeaf->maxs[j]); + } + + out->cluster = LittleLong(inLeaf->cluster); + out->area = LittleLong(inLeaf->area); + + if ( out->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = LittleLong(inLeaf->firstLeafSurface); + out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces); + } + + // chain decendants + R_SetParent (s_worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l ) { + int i, count; + dshader_t *in, *out; + + in = (dshader_t*)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = (dshader_t*)ri.Hunk_Alloc ( count*sizeof(*out), h_low ); + + s_worldData.shaders = out; + s_worldData.numShaders = count; + + Com_Memcpy( out, in, count*sizeof(*out) ); + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = (int*)ri.Hunk_Alloc ( count*sizeof(*out), h_low); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = (cplane_t*)ri.Hunk_Alloc ( count*2*sizeof(*out), h_low); + + s_worldData.planes = out; + s_worldData.numplanes = count; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) { + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide; + + fogs = (dfog_t*)(fileBase + l->fileofs); + if (l->filelen % sizeof(*fogs)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + count = l->filelen / sizeof(*fogs); + + // create fog strucutres for them + s_worldData.numfogs = count + 1; + s_worldData.fogs = (fog_t*)ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low); + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = (dbrush_t*)(fileBase + brushesLump->fileofs); + if (brushesLump->filelen % sizeof(*brushes)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + brushesCount = brushesLump->filelen / sizeof(*brushes); + + sides = (dbrushside_t*)(fileBase + sidesLump->fileofs); + if (sidesLump->filelen % sizeof(*sides)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + sidesCount = sidesLump->filelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = LittleLong( fogs->brushNum ); + + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + ri.Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + ri.Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, true ); + + out->parms = shader->fogParms; + + out->colorInt = ColorBytes4 ( shader->fogParms.color[0], + shader->fogParms.color[1], + shader->fogParms.color[2], 1.0 ); + + d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + out->hasSurface = false; + } else { + out->hasSurface = true; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + +} + + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l ) { + int i; + vec3_t maxs; + int numGridPoints; + world_t *w; + float *wMins, *wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != numGridPoints * 8 ) { + ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridData = (byte*)ri.Hunk_Alloc( l->filelen, h_low ); + Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridPoints ; i++ ) { + R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] ); + R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] ); + } + + // load hdr lightgrid + if (r_hdr->integer) + { + char filename[MAX_QPATH]; + float *hdrLightGrid; + int size; + + Com_sprintf( filename, sizeof( filename ), "maps/%s/lightgrid.raw", s_worldData.baseName); + //ri.Printf(PRINT_ALL, "looking for %s\n", filename); + + size = ri.FS_ReadFile(filename, (void **)&hdrLightGrid); + + if (hdrLightGrid) + { + //ri.Printf(PRINT_ALL, "found!\n"); + + if (size != sizeof(float) * 6 * numGridPoints) + ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!", filename, size, (int)(sizeof(float)) * 6 * numGridPoints); + + w->lightGrid16 = (uint16_t*)ri.Hunk_Alloc(sizeof(w->lightGrid16) * 6 * numGridPoints, h_low); + + for (i = 0; i < numGridPoints ; i++) + { + vec4_t c; + + c[0] = hdrLightGrid[i * 6]; + c[1] = hdrLightGrid[i * 6 + 1]; + c[2] = hdrLightGrid[i * 6 + 2]; + c[3] = 1.0f; + + R_ColorShiftLightingFloats(c, c); + ColorToRGB16(c, &w->lightGrid16[i * 6]); + + c[0] = hdrLightGrid[i * 6 + 3]; + c[1] = hdrLightGrid[i * 6 + 4]; + c[2] = hdrLightGrid[i * 6 + 5]; + c[3] = 1.0f; + + R_ColorShiftLightingFloats(c, c); + ColorToRGB16(c, &w->lightGrid16[i * 6 + 3]); + } + } + else if (0) + { + // promote 8-bit lightgrid to 16-bit + w->lightGrid16 = (uint16_t*)ri.Hunk_Alloc(sizeof(w->lightGrid16) * 6 * numGridPoints, h_low); + + for (i = 0; i < numGridPoints; i++) + { + w->lightGrid16[i * 6] = w->lightGridData[i * 8] * 257; + w->lightGrid16[i * 6 + 1] = w->lightGridData[i * 8 + 1] * 257; + w->lightGrid16[i * 6 + 2] = w->lightGridData[i * 8 + 2] * 257; + w->lightGrid16[i * 6 + 3] = w->lightGridData[i * 8 + 3] * 257; + w->lightGrid16[i * 6 + 4] = w->lightGridData[i * 8 + 4] * 257; + w->lightGrid16[i * 6 + 5] = w->lightGridData[i * 8 + 5] * 257; + } + } + + if (hdrLightGrid) + ri.FS_FreeFile(hdrLightGrid); + } +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l ) { + char *p, *token; + char *s; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + p = (char *)(fileBase + l->fileofs); + + // store for reference by the cgame + w->entityString = (char*)ri.Hunk_Alloc( l->filelen + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + // check for remapping of shaders for vertex lighting + if (!Q_strncmp(keyname, "vertexremapshader", strlen("vertexremapshader")) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + if (r_vertexLight->integer) { + R_RemapShader(value, s, "0"); + } + continue; + } + // check for remapping of shaders + if (!Q_strncmp(keyname, "remapshader", strlen("remapshader")) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader(value, s, "0"); + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + + // check for auto exposure + if (!Q_stricmp(keyname, "autoExposureMinMax")) { + sscanf(value, "%f %f", &tr.autoExposureMinMax[0], &tr.autoExposureMinMax[1]); + continue; + } + } +} + +/* +================= +R_GetEntityToken +================= +*/ +bool R_GetEntityToken( char *buffer, int size ) { + const char *s; + + s = COM_Parse( &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint && !s[0] ) { + s_worldData.entityParsePoint = s_worldData.entityString; + return false; + } else { + return true; + } +} + +#ifndef MAX_SPAWN_VARS +#define MAX_SPAWN_VARS 64 +#endif + +// derived from G_ParseSpawnVars() in g_spawn.c +bool R_ParseSpawnVars( char *spawnVarChars, int maxSpawnVarChars, int *numSpawnVars, char *spawnVars[MAX_SPAWN_VARS][2] ) +{ + char keyname[MAX_TOKEN_CHARS]; + char com_token[MAX_TOKEN_CHARS]; + int numSpawnVarChars = 0; + + *numSpawnVars = 0; + + // parse the opening brace + if ( !R_GetEntityToken( com_token, sizeof( com_token ) ) ) { + // end of spawn string + return false; + } + if ( com_token[0] != '{' ) { + ri.Printf( PRINT_ALL, "R_ParseSpawnVars: found %s when expecting {\n",com_token ); + return false; + } + + // go through all the key / value pairs + while ( 1 ) { + int keyLength, tokenLength; + + // parse key + if ( !R_GetEntityToken( keyname, sizeof( keyname ) ) ) { + ri.Printf( PRINT_ALL, "R_ParseSpawnVars: EOF without closing brace\n" ); + return false; + } + + if ( keyname[0] == '}' ) { + break; + } + + // parse value + if ( !R_GetEntityToken( com_token, sizeof( com_token ) ) ) { + ri.Printf( PRINT_ALL, "R_ParseSpawnVars: EOF without closing brace\n" ); + return false; + } + + if ( com_token[0] == '}' ) { + ri.Printf( PRINT_ALL, "R_ParseSpawnVars: closing brace without data\n" ); + return false; + } + + if ( *numSpawnVars == MAX_SPAWN_VARS ) { + ri.Printf( PRINT_ALL, "R_ParseSpawnVars: MAX_SPAWN_VARS\n" ); + return false; + } + + keyLength = strlen(keyname) + 1; + tokenLength = strlen(com_token) + 1; + + if (numSpawnVarChars + keyLength + tokenLength > maxSpawnVarChars) + { + ri.Printf( PRINT_ALL, "R_ParseSpawnVars: MAX_SPAWN_VAR_CHARS\n" ); + return false; + } + + strcpy(spawnVarChars + numSpawnVarChars, keyname); + spawnVars[ *numSpawnVars ][0] = spawnVarChars + numSpawnVarChars; + numSpawnVarChars += keyLength; + + strcpy(spawnVarChars + numSpawnVarChars, com_token); + spawnVars[ *numSpawnVars ][1] = spawnVarChars + numSpawnVarChars; + numSpawnVarChars += tokenLength; + + (*numSpawnVars)++; + } + + return true; +} + +void R_LoadEnvironmentJson(const char *baseName) +{ + char filename[MAX_QPATH]; + + union { + char *c; + void *v; + } buffer; + char *bufferEnd; + + const char *cubemapArrayJson; + int filelen, i; + + Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/env.json", baseName); + + filelen = ri.FS_ReadFile(filename, &buffer.v); + if (!buffer.c) + return; + bufferEnd = buffer.c + filelen; + + if (JSON_ValueGetType(buffer.c, bufferEnd) != JSONTYPE_OBJECT) + { + ri.Printf(PRINT_ALL, "Bad %s: does not start with a object\n", filename); + ri.FS_FreeFile(buffer.v); + return; + } + + cubemapArrayJson = JSON_ObjectGetNamedValue(buffer.c, bufferEnd, "Cubemaps"); + if (!cubemapArrayJson) + { + ri.Printf(PRINT_ALL, "Bad %s: no Cubemaps\n", filename); + ri.FS_FreeFile(buffer.v); + return; + } + + if (JSON_ValueGetType(cubemapArrayJson, bufferEnd) != JSONTYPE_ARRAY) + { + ri.Printf(PRINT_ALL, "Bad %s: Cubemaps not an array\n", filename); + ri.FS_FreeFile(buffer.v); + return; + } + + tr.numCubemaps = JSON_ArrayGetIndex(cubemapArrayJson, bufferEnd, NULL, 0); + tr.cubemaps = (cubemap_t*)ri.Hunk_Alloc(tr.numCubemaps * sizeof(*tr.cubemaps), h_low); + memset(tr.cubemaps, 0, tr.numCubemaps * sizeof(*tr.cubemaps)); + + for (i = 0; i < tr.numCubemaps; i++) + { + cubemap_t *cubemap = &tr.cubemaps[i]; + const char *cubemapJson, *keyValueJson, *indexes[3]; + int j; + + cubemapJson = JSON_ArrayGetValue(cubemapArrayJson, bufferEnd, i); + + keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Name"); + if (!JSON_ValueGetString(keyValueJson, bufferEnd, cubemap->name, MAX_QPATH)) + cubemap->name[0] = '\0'; + + keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Position"); + JSON_ArrayGetIndex(keyValueJson, bufferEnd, indexes, 3); + for (j = 0; j < 3; j++) + cubemap->origin[j] = JSON_ValueGetFloat(indexes[j], bufferEnd); + + cubemap->parallaxRadius = 1000.0f; + keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Radius"); + if (keyValueJson) + cubemap->parallaxRadius = JSON_ValueGetFloat(keyValueJson, bufferEnd); + } + + ri.FS_FreeFile(buffer.v); +} + +void R_LoadCubemapEntities(const char *cubemapEntityName) +{ + char spawnVarChars[2048]; + int numSpawnVars; + char *spawnVars[MAX_SPAWN_VARS][2]; + int numCubemaps = 0; + + // count cubemaps + numCubemaps = 0; + while(R_ParseSpawnVars(spawnVarChars, sizeof(spawnVarChars), &numSpawnVars, spawnVars)) + { + int i; + + for (i = 0; i < numSpawnVars; i++) + { + if (!Q_stricmp(spawnVars[i][0], "classname") && !Q_stricmp(spawnVars[i][1], cubemapEntityName)) + numCubemaps++; + } + } + + if (!numCubemaps) + return; + + tr.numCubemaps = numCubemaps; + tr.cubemaps = (cubemap_t*)ri.Hunk_Alloc(tr.numCubemaps * sizeof(*tr.cubemaps), h_low); + memset(tr.cubemaps, 0, tr.numCubemaps * sizeof(*tr.cubemaps)); + + numCubemaps = 0; + while(R_ParseSpawnVars(spawnVarChars, sizeof(spawnVarChars), &numSpawnVars, spawnVars)) + { + int i; + char name[MAX_QPATH]; + bool isCubemap = false; + bool originSet = false; + vec3_t origin; + float parallaxRadius = 1000.0f; + + name[0] = '\0'; + for (i = 0; i < numSpawnVars; i++) + { + if (!Q_stricmp(spawnVars[i][0], "classname") && !Q_stricmp(spawnVars[i][1], cubemapEntityName)) + isCubemap = true; + + if (!Q_stricmp(spawnVars[i][0], "name")) + Q_strncpyz(name, spawnVars[i][1], MAX_QPATH); + + if (!Q_stricmp(spawnVars[i][0], "origin")) + { + sscanf(spawnVars[i][1], "%f %f %f", &origin[0], &origin[1], &origin[2]); + originSet = true; + } + else if (!Q_stricmp(spawnVars[i][0], "radius")) + { + sscanf(spawnVars[i][1], "%f", ¶llaxRadius); + } + } + + if (isCubemap && originSet) + { + cubemap_t *cubemap = &tr.cubemaps[numCubemaps]; + Q_strncpyz(cubemap->name, name, MAX_QPATH); + VectorCopy(origin, cubemap->origin); + cubemap->parallaxRadius = parallaxRadius; + numCubemaps++; + } + } +} + +void R_AssignCubemapsToWorldSurfaces(void) +{ + world_t *w; + int i; + + w = &s_worldData; + + for (i = 0; i < w->numsurfaces; i++) + { + msurface_t *surf = &w->surfaces[i]; + vec3_t surfOrigin; + + if (surf->cullinfo.type & CULLINFO_SPHERE) + { + VectorCopy(surf->cullinfo.localOrigin, surfOrigin); + } + else if (surf->cullinfo.type & CULLINFO_BOX) + { + surfOrigin[0] = (surf->cullinfo.bounds[0][0] + surf->cullinfo.bounds[1][0]) * 0.5f; + surfOrigin[1] = (surf->cullinfo.bounds[0][1] + surf->cullinfo.bounds[1][1]) * 0.5f; + surfOrigin[2] = (surf->cullinfo.bounds[0][2] + surf->cullinfo.bounds[1][2]) * 0.5f; + } + else + { + //ri.Printf(PRINT_ALL, "surface %d has no cubemap\n", i); + continue; + } + + surf->cubemapIndex = R_CubemapForPoint(surfOrigin); + //ri.Printf(PRINT_ALL, "surface %d has cubemap %d\n", i, surf->cubemapIndex); + } +} + + +void R_LoadCubemaps(void) +{ + int i; + int/*imgFlags_t*/ flags = IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_NOLIGHTSCALE | IMGFLAG_CUBEMAP; + + for (i = 0; i < tr.numCubemaps; i++) + { + char filename[MAX_QPATH]; + cubemap_t *cubemap = &tr.cubemaps[i]; + + Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/%03d.dds", tr.world->baseName, i); + + cubemap->image = R_FindImageFile(filename, IMGTYPE_COLORALPHA, flags); + } +} + + +void R_RenderMissingCubemaps(void) +{ + int i, j; + int/*imgFlags_t*/ flags = IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_NOLIGHTSCALE | IMGFLAG_CUBEMAP; + + for (i = 0; i < tr.numCubemaps; i++) + { + if (!tr.cubemaps[i].image) + { + tr.cubemaps[i].image = R_CreateImage(va("*cubeMap%d", i), NULL, + r_cubemapSize->integer, r_cubemapSize->integer, + IMGTYPE_COLORALPHA, flags, GL_RGBA8); + + for (j = 0; j < 6; j++) + { + RE_ClearScene(); + R_RenderCubemapSide(i, j, false); + R_IssuePendingRenderCommands(); + R_InitNextFrame(); + } + } + } +} + + +void R_CalcVertexLightDirs( void ) +{ + int i, k; + msurface_t *surface; + + for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces /* s_worldData.numWorldSurfaces */; k++, surface++) + { + srfBspSurface_t *bspSurf = (srfBspSurface_t *) surface->data; + + switch(bspSurf->surfaceType) + { + case SF_FACE: + case SF_GRID: + case SF_TRIANGLES: + for(i = 0; i < bspSurf->numVerts; i++) + { + vec3_t lightDir; + vec3_t normal; + + R_VaoUnpackNormal(normal, bspSurf->verts[i].normal); + R_LightDirForPoint( bspSurf->verts[i].xyz, lightDir, normal, &s_worldData ); + R_VaoPackNormal(bspSurf->verts[i].lightdir, lightDir); + } + + break; + + default: + break; + } + } +} + + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap( const char *name ) { + int i; + dheader_t *header; + union { + byte *b; + void *v; + } buffer; + byte *startMarker; + + if ( tr.worldMapLoaded ) { + ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map" ); + } + + // set default map light scale + tr.sunShadowScale = 0.5f; + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + // set default autoexposure settings + tr.autoExposureMinMax[0] = -2.0f; + tr.autoExposureMinMax[1] = 2.0f; + + // set default tone mapping settings + tr.toneMinAvgMaxLevel[0] = -8.0f; + tr.toneMinAvgMaxLevel[1] = -2.0f; + tr.toneMinAvgMaxLevel[2] = 0.0f; + + // reset last cascade sun direction so last shadow cascade is rerendered + VectorClear(tr.lastCascadeSunDirection); + + tr.worldMapLoaded = true; + + // load it + ri.FS_ReadFile( name, &buffer.v ); + if ( !buffer.b ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name); + } + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + Com_Memset( &s_worldData, 0, sizeof( s_worldData ) ); + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName)); + + startMarker = (byte*)ri.Hunk_Alloc(0, h_low); + c_gridVerts = 0; + + header = (dheader_t *)buffer.b; + fileBase = (byte *)header; + + i = LittleLong (header->version); + if ( i != BSP_VERSION ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", + name, i, BSP_VERSION); + } + + // swap all the lumps + for (i=0 ; ilumps[LUMP_ENTITIES] ); + R_LoadShaders( &header->lumps[LUMP_SHADERS] ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS], &header->lumps[LUMP_SURFACES] ); + R_LoadPlanes (&header->lumps[LUMP_PLANES]); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] ); + R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]); + R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]); + R_LoadSubmodels (&header->lumps[LUMP_MODELS]); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] ); + + // determine vertex light directions + R_CalcVertexLightDirs(); + + // determine which parts of the map are in sunlight + if (0) + { + world_t *w; + uint8_t *primaryLightGrid, *data; + int lightGridSize; + int i; + + w = &s_worldData; + + lightGridSize = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + primaryLightGrid = (uint8_t*)ri.Malloc(lightGridSize * sizeof(*primaryLightGrid)); + + memset(primaryLightGrid, 0, lightGridSize * sizeof(*primaryLightGrid)); + + data = w->lightGridData; + for (i = 0; i < lightGridSize; i++, data += 8) + { + int lat, lng; + vec3_t gridLightDir, gridLightCol; + + // skip samples in wall + if (!(data[0]+data[1]+data[2]+data[3]+data[4]+data[5]) ) + continue; + + gridLightCol[0] = ByteToFloat(data[3]); + gridLightCol[1] = ByteToFloat(data[4]); + gridLightCol[2] = ByteToFloat(data[5]); + (void)gridLightCol; // Suppress unused-but-set-variable warning + + lat = data[7]; + lng = data[6]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + gridLightDir[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + gridLightDir[1] = tr.sinTable[lat] * tr.sinTable[lng]; + gridLightDir[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + // FIXME: magic number for determining if light direction is close enough to sunlight + if (DotProduct(gridLightDir, tr.sunDirection) > 0.75f) + { + primaryLightGrid[i] = 1; + } + else + { + primaryLightGrid[i] = 255; + } + } + + if (0) + { + int i; + byte *buffer = (byte*)ri.Malloc(w->lightGridBounds[0] * w->lightGridBounds[1] * 3 + 18); + byte *out; + uint8_t *in; + char fileName[MAX_QPATH]; + + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = w->lightGridBounds[0] & 255; + buffer[13] = w->lightGridBounds[0] >> 8; + buffer[14] = w->lightGridBounds[1] & 255; + buffer[15] = w->lightGridBounds[1] >> 8; + buffer[16] = 24; // pixel size + + in = primaryLightGrid; + for (i = 0; i < w->lightGridBounds[2]; i++) + { + int j; + + sprintf(fileName, "primarylg%d.tga", i); + + out = buffer + 18; + for (j = 0; j < w->lightGridBounds[0] * w->lightGridBounds[1]; j++) + { + if (*in == 1) + { + *out++ = 255; + *out++ = 255; + *out++ = 255; + } + else if (*in == 255) + { + *out++ = 64; + *out++ = 64; + *out++ = 64; + } + else + { + *out++ = 0; + *out++ = 0; + *out++ = 0; + } + in++; + } + + ri.FS_WriteFile(fileName, buffer, w->lightGridBounds[0] * w->lightGridBounds[1] * 3 + 18); + } + + ri.Free(buffer); + } + + for (i = 0; i < w->numWorldSurfaces; i++) + { + msurface_t *surf = w->surfaces + i; + cullinfo_t *ci = &surf->cullinfo; + + if(ci->type & CULLINFO_PLANE) + { + if (DotProduct(ci->plane.normal, tr.sunDirection) <= 0.0f) + { + //ri.Printf(PRINT_ALL, "surface %d is not oriented towards sunlight\n", i); + continue; + } + } + + if(ci->type & CULLINFO_BOX) + { + int ibounds[2][3], x, y, z, goodSamples, numSamples; + vec3_t lightOrigin; + + VectorSubtract( ci->bounds[0], w->lightGridOrigin, lightOrigin ); + + ibounds[0][0] = floor(lightOrigin[0] * w->lightGridInverseSize[0]); + ibounds[0][1] = floor(lightOrigin[1] * w->lightGridInverseSize[1]); + ibounds[0][2] = floor(lightOrigin[2] * w->lightGridInverseSize[2]); + + VectorSubtract( ci->bounds[1], w->lightGridOrigin, lightOrigin ); + + ibounds[1][0] = ceil(lightOrigin[0] * w->lightGridInverseSize[0]); + ibounds[1][1] = ceil(lightOrigin[1] * w->lightGridInverseSize[1]); + ibounds[1][2] = ceil(lightOrigin[2] * w->lightGridInverseSize[2]); + + ibounds[0][0] = CLAMP(ibounds[0][0], 0, w->lightGridSize[0]); + ibounds[0][1] = CLAMP(ibounds[0][1], 0, w->lightGridSize[1]); + ibounds[0][2] = CLAMP(ibounds[0][2], 0, w->lightGridSize[2]); + + ibounds[1][0] = CLAMP(ibounds[1][0], 0, w->lightGridSize[0]); + ibounds[1][1] = CLAMP(ibounds[1][1], 0, w->lightGridSize[1]); + ibounds[1][2] = CLAMP(ibounds[1][2], 0, w->lightGridSize[2]); + + /* + ri.Printf(PRINT_ALL, "surf %d bounds (%f %f %f)-(%f %f %f) ibounds (%d %d %d)-(%d %d %d)\n", i, + ci->bounds[0][0], ci->bounds[0][1], ci->bounds[0][2], + ci->bounds[1][0], ci->bounds[1][1], ci->bounds[1][2], + ibounds[0][0], ibounds[0][1], ibounds[0][2], + ibounds[1][0], ibounds[1][1], ibounds[1][2]); + */ + + goodSamples = 0; + numSamples = 0; + for (x = ibounds[0][0]; x <= ibounds[1][0]; x++) + { + for (y = ibounds[0][1]; y <= ibounds[1][1]; y++) + { + for (z = ibounds[0][2]; z <= ibounds[1][2]; z++) + { + uint8_t primaryLight = primaryLightGrid[x * 8 + y * 8 * w->lightGridBounds[0] + z * 8 * w->lightGridBounds[0] * w->lightGridBounds[2]]; + + if (primaryLight == 0) + continue; + + numSamples++; + + if (primaryLight == 1) + goodSamples++; + } + } + } + + // FIXME: magic number for determining whether object is mostly in sunlight + if (goodSamples > numSamples * 0.75f) + { + //ri.Printf(PRINT_ALL, "surface %d is in sunlight\n", i); + //surf->primaryLight = 1; + } + } + } + + ri.Free(primaryLightGrid); + } + + // load cubemaps + if (r_cubeMapping->integer) + { + // Try loading an env.json file first + R_LoadEnvironmentJson(s_worldData.baseName); + + if (!tr.numCubemaps) + { + R_LoadCubemapEntities("misc_cubemap"); + } + + if (!tr.numCubemaps) + { + // location names are an assured way to get an even distribution + R_LoadCubemapEntities("target_location"); + } + + if (!tr.numCubemaps) + { + // try misc_models + R_LoadCubemapEntities("misc_model"); + } + + if (tr.numCubemaps) + { + R_AssignCubemapsToWorldSurfaces(); + } + } + + s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker; + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; + + // make sure the VAO glState entry is safe + R_BindNullVao(); + + // Render or load all cubemaps + if (r_cubeMapping->integer && tr.numCubemaps && glRefConfig.framebufferObject) + { + R_LoadCubemaps(); + R_RenderMissingCubemaps(); + } + + ri.FS_FreeFile( buffer.v ); +} diff --git a/src/renderergl2/tr_cmds.cpp b/src/renderergl2/tr_cmds.cpp new file mode 100644 index 0000000..d7eecd2 --- /dev/null +++ b/src/renderergl2/tr_cmds.cpp @@ -0,0 +1,672 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if (r_speeds->integer == 1) { + ri.Printf (PRINT_ALL, "%i/%i/%i shaders/batches/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfBatches, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, + R_SumOfUsedImages()/(1000000.0f), backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); + } else if (r_speeds->integer == 2) { + ri.Printf (PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + ri.Printf (PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if (r_speeds->integer == 3) { + ri.Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster ); + } else if (r_speeds->integer == 4) { + if ( backEnd.pc.c_dlightVertexes ) { + ri.Printf (PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } + else if (r_speeds->integer == 5 ) + { + ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar ); + } + else if (r_speeds->integer == 6 ) + { + ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + else if (r_speeds->integer == 7 ) + { + ri.Printf( PRINT_ALL, "VAO draws: static %i dynamic %i\n", + backEnd.pc.c_staticVaoDraws, backEnd.pc.c_dynamicVaoDraws); + ri.Printf( PRINT_ALL, "GLSL binds: %i draws: gen %i light %i fog %i dlight %i\n", + backEnd.pc.c_glslShaderBinds, backEnd.pc.c_genericDraws, backEnd.pc.c_lightallDraws, backEnd.pc.c_fogDraws, backEnd.pc.c_dlightDraws); + } + + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_IssueRenderCommands +==================== +*/ +void R_IssueRenderCommands( bool runPerformanceCounters ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + assert(cmdList); + // add an end-of-list command + *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + RB_ExecuteRenderCommands( cmdList->cmds ); + } +} + + +/* +==================== +R_IssuePendingRenderCommands + +Issue any pending commands and wait for them to complete. +==================== +*/ +void R_IssuePendingRenderCommands( void ) { + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( false ); +} + +/* +============ +R_GetCommandBufferReserved + +make sure there is enough command space +============ +*/ +void *R_GetCommandBufferReserved( int bytes, int reservedBytes ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + bytes = PAD(bytes, sizeof(void *)); + + // always leave room for the end of list command + if ( cmdList->used + bytes + sizeof( int ) + reservedBytes > MAX_RENDER_COMMANDS ) { + if ( bytes > MAX_RENDER_COMMANDS - sizeof( int ) ) { + ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + +/* +============= +R_GetCommandBuffer + +returns NULL if there is not enough space for important commands +============= +*/ +void *R_GetCommandBuffer( int bytes ) { + return R_GetCommandBufferReserved( bytes, PAD( sizeof( swapBuffersCommand_t ), sizeof(void *) ) ); +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = (drawSurfsCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + + +/* +============= +R_AddCapShadowmapCmd + +============= +*/ +void R_AddCapShadowmapCmd( int map, int cubeSide ) { + capShadowmapCommand_t *cmd; + + cmd = (capShadowmapCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_CAPSHADOWMAP; + + cmd->map = map; + cmd->cubeSide = cubeSide; +} + + +/* +============= +R_PostProcessingCmd + +============= +*/ +void R_AddPostProcessCmd( ) { + postProcessCommand_t *cmd; + + cmd = (postProcessCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_POSTPROCESS; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = (setColorCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( !rgba ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + rgba = colorWhite; + } + + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; +} + +/* +============= +R_ClipRegion +============= +*/ +static bool R_ClipRegion ( float *x, float *y, float *w, float *h, + float *s1, float *t1, float *s2, float *t2 ) { + float left, top, right, bottom; + float _s1, _t1, _s2, _t2; + float clipLeft, clipTop, clipRight, clipBottom; + + if (tr.clipRegion[2] <= tr.clipRegion[0] || + tr.clipRegion[3] <= tr.clipRegion[1] ) { + return false; + } + + left = *x; + top = *y; + right = *x + *w; + bottom = *y + *h; + + _s1 = *s1; + _t1 = *t1; + _s2 = *s2; + _t2 = *t2; + + clipLeft = tr.clipRegion[0]; + clipTop = tr.clipRegion[1]; + clipRight = tr.clipRegion[2]; + clipBottom = tr.clipRegion[3]; + + // Completely clipped away + if ( right <= clipLeft || left >= clipRight || + bottom <= clipTop || top >= clipBottom ) { + return true; + } + + // Clip left edge + if ( left < clipLeft ) { + float f = ( clipLeft - left ) / ( right - left ); + *s1 = ( f * ( _s2 - _s1 ) ) + _s1; + *x = clipLeft; + *w -= ( clipLeft - left ); + } + + // Clip right edge + if ( right > clipRight ) { + float f = ( clipRight - right ) / ( left - right ); + *s2 = ( f * ( _s1 - _s2 ) ) + _s2; + *w = clipRight - *x; + } + + // Clip top edge + if ( top < clipTop ) { + float f = ( clipTop - top ) / ( bottom - top ); + *t1 = ( f * ( _t2 - _t1 ) ) + _t1; + *y = clipTop; + *h -= ( clipTop - top ); + } + + // Clip bottom edge + if ( bottom > clipBottom ) { + float f = ( clipBottom - bottom ) / ( top - bottom ); + *t2 = ( f * ( _t1 - _t2 ) ) + _t2; + *h = clipBottom - *y; + } + + return false; +} + +/* +============= +RE_SetClipRegion +============= +*/ +void RE_SetClipRegion( const float *region ) { + if ( region == NULL ) { + Com_Memset( tr.clipRegion, 0, sizeof( vec4_t ) ); + } else { + Vector4Copy( region, tr.clipRegion ); + } +} + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + if (!tr.registered) { + return; + } + if (R_ClipRegion(&x, &y, &w, &h, &s1, &t1, &s2, &t2)) { + return; + } + cmd = (stretchPicCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +#define MODE_RED_CYAN 1 +#define MODE_RED_BLUE 2 +#define MODE_RED_GREEN 3 +#define MODE_GREEN_MAGENTA 4 +#define MODE_MAX MODE_GREEN_MAGENTA + +void R_SetColorMode(GLboolean *rgba, stereoFrame_t stereoFrame, int colormode) +{ + rgba[0] = rgba[1] = rgba[2] = rgba[3] = GL_TRUE; + + if(colormode > MODE_MAX) + { + if(stereoFrame == STEREO_LEFT) + stereoFrame = STEREO_RIGHT; + else if(stereoFrame == STEREO_RIGHT) + stereoFrame = STEREO_LEFT; + + colormode -= MODE_MAX; + } + + if(colormode == MODE_GREEN_MAGENTA) + { + if(stereoFrame == STEREO_LEFT) + rgba[0] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + rgba[1] = GL_FALSE; + } + else + { + if(stereoFrame == STEREO_LEFT) + rgba[1] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + { + rgba[0] = GL_FALSE; + + if(colormode == MODE_RED_BLUE) + rgba[1] = GL_FALSE; + else if(colormode == MODE_RED_GREEN) + rgba[2] = GL_FALSE; + } + } +} + + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd = NULL; + colorMaskCommand_t *colcmd = NULL; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = false; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // + if ( r_measureOverdraw->integer ) + { + if ( glConfig.stencilBits < 4 ) + { + ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = false; + } + else if ( r_shadows->integer == 2 ) + { + ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = false; + } + else + { + R_IssuePendingRenderCommands(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = false; + } + else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_IssuePendingRenderCommands(); + qglDisable( GL_STENCIL_TEST ); + } + r_measureOverdraw->modified = false; + } + + // + // texturemode stuff + // + if ( r_textureMode->modified ) { + R_IssuePendingRenderCommands(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = false; + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = false; + + R_IssuePendingRenderCommands(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) + { + int err; + + R_IssuePendingRenderCommands(); + if ((err = qglGetError()) != GL_NO_ERROR) + ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!", err); + } + + if (glConfig.stereoEnabled) { + if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + cmd->commandId = RC_DRAW_BUFFER; + + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } + else + { + if(r_anaglyphMode->integer) + { + if(r_anaglyphMode->modified) + { + // clear both, front and backbuffer. + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + backEnd.colorMask[0] = GL_FALSE; + backEnd.colorMask[1] = GL_FALSE; + backEnd.colorMask[2] = GL_FALSE; + backEnd.colorMask[3] = GL_FALSE; + + if (glRefConfig.framebufferObject) + { + // clear all framebuffers + if (tr.msaaResolveFbo) + { + FBO_Bind(tr.msaaResolveFbo); + qglClear(GL_COLOR_BUFFER_BIT); + } + + if (tr.renderFbo) + { + FBO_Bind(tr.renderFbo); + qglClear(GL_COLOR_BUFFER_BIT); + } + + FBO_Bind(NULL); + } + + qglDrawBuffer(GL_FRONT); + qglClear(GL_COLOR_BUFFER_BIT); + qglDrawBuffer(GL_BACK); + qglClear(GL_COLOR_BUFFER_BIT); + + r_anaglyphMode->modified = false; + } + + if(stereoFrame == STEREO_LEFT) + { + if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + if( !(colcmd = (colorMaskCommand_t*)R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else if(stereoFrame == STEREO_RIGHT) + { + clearDepthCommand_t *cldcmd; + + if( !(cldcmd = (clearDepthCommand_t*)R_GetCommandBuffer(sizeof(*cldcmd))) ) + return; + + cldcmd->commandId = RC_CLEARDEPTH; + + if( !(colcmd = (colorMaskCommand_t*)R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + + R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer); + colcmd->commandId = RC_COLORMASK; + } + else + { + if(stereoFrame != STEREO_CENTER) + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + + if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) ) + return; + } + + if(cmd) + { + cmd->commandId = RC_DRAW_BUFFER; + + if(r_anaglyphMode->modified) + { + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + backEnd.colorMask[0] = 0; + backEnd.colorMask[1] = 0; + backEnd.colorMask[2] = 0; + backEnd.colorMask[3] = 0; + r_anaglyphMode->modified = false; + } + + if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT")) + cmd->buffer = (int)GL_FRONT; + else + cmd->buffer = (int)GL_BACK; + } + } + + tr.refdef.stereoFrame = stereoFrame; +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = (swapBuffersCommand_t*)R_GetCommandBufferReserved( sizeof( *cmd ), 0 ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + + R_IssueRenderCommands( true ); + + R_InitNextFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; +} + +/* +============= +RE_TakeVideoFrame +============= +*/ +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, bool motionJpeg ) +{ + videoFrameCommand_t *cmd; + + if( !tr.registered ) { + return; + } + + cmd = (videoFrameCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if( !cmd ) { + return; + } + + cmd->commandId = RC_VIDEOFRAME; + + cmd->width = width; + cmd->height = height; + cmd->captureBuffer = captureBuffer; + cmd->encodeBuffer = encodeBuffer; + cmd->motionJpeg = motionJpeg; +} diff --git a/src/renderergl2/tr_curve.cpp b/src/renderergl2/tr_curve.cpp new file mode 100644 index 0000000..20aef7f --- /dev/null +++ b/src/renderergl2/tr_curve.cpp @@ -0,0 +1,741 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfBspSurface_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfBspSurface_t *R_SubdividePatchToGrid( int width, int height, + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( srfVert_t *a, srfVert_t *b, srfVert_t *out ) { + out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]); + + out->st[0] = 0.5f * (a->st[0] + b->st[0]); + out->st[1] = 0.5f * (a->st[1] + b->st[1]); + + out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]); + out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]); + + out->color[0] = ((int)a->color[0] + (int)b->color[0]) >> 1; + out->color[1] = ((int)a->color[1] + (int)b->color[1]) >> 1; + out->color[2] = ((int)a->color[2] + (int)b->color[2]) >> 1; + out->color[3] = ((int)a->color[3] + (int)b->color[3]) >> 1; +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + srfVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} + + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count = 0; + vec3_t base; + vec3_t delta; + int x, y; + srfVert_t *dv; + vec3_t around[8], temp; + bool good[8]; + bool wrapWidth, wrapHeight; + float len; +static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = false; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = true; + } + + wrapHeight = false; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = true; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = false; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = true; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + //if ( count == 0 ) { + // printf("bad normal\n"); + //} + { + vec3_t fNormal; + VectorNormalize2(sum, fNormal); + R_VaoPackNormal(dv->normal, fNormal); + } + } + } +} + +static void MakeMeshTangentVectors(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], int numIndexes, + glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]) +{ + int i, j; + srfVert_t *dv[3]; + static srfVert_t ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE]; + glIndex_t *tri; + + // FIXME: use more elegant way + for(i = 0; i < width; i++) + { + for(j = 0; j < height; j++) + { + dv[0] = &ctrl2[j * width + i]; + *dv[0] = ctrl[j][i]; + } + } + + for(i = 0, tri = indexes; i < numIndexes; i += 3, tri += 3) + { + dv[0] = &ctrl2[tri[0]]; + dv[1] = &ctrl2[tri[1]]; + dv[2] = &ctrl2[tri[2]]; + + R_CalcTangentVectors(dv); + } + + for(i = 0; i < width; i++) + { + for(j = 0; j < height; j++) + { + dv[0] = &ctrl2[j * width + i]; + dv[1] = &ctrl[j][i]; + + VectorCopy4(dv[0]->tangent, dv[1]->tangent); + } + } +} + + +static int MakeMeshIndexes(int width, int height, glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]) +{ + int i, j; + int numIndexes; + int w, h; + + h = height - 1; + w = width - 1; + numIndexes = 0; + for(i = 0; i < h; i++) + { + for(j = 0; j < w; j++) + { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = i * width + j + 1; + v2 = v1 - 1; + v3 = v2 + width; + v4 = v3 + 1; + + indexes[numIndexes++] = v2; + indexes[numIndexes++] = v3; + indexes[numIndexes++] = v1; + + indexes[numIndexes++] = v1; + indexes[numIndexes++] = v3; + indexes[numIndexes++] = v4; + } + } + + return numIndexes; +} + + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + srfVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width-1-j]; + ctrl[i][width-1-j] = temp; + } + } +} + + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + Com_Memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height-1-i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + srfVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +void R_CreateSurfaceGridMesh(srfBspSurface_t *grid, int width, int height, + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE], + int numIndexes, glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]) { + int i, j; + srfVert_t *vert; + vec3_t tmpVec; + + // copy the results out to a grid + Com_Memset(grid, 0, sizeof(*grid)); + +#ifdef PATCH_STITCHING + grid->widthLodError = (float*)/*ri.Hunk_Alloc*/ ri.Malloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = (float*)/*ri.Hunk_Alloc*/ ri.Malloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); + + grid->numIndexes = numIndexes; + grid->indexes = (glIndex_t*)ri.Malloc(grid->numIndexes * sizeof(glIndex_t)); + Com_Memcpy(grid->indexes, indexes, numIndexes * sizeof(glIndex_t)); + + grid->numVerts = (width * height); + grid->verts = (srfVert_t*)ri.Malloc(grid->numVerts * sizeof(srfVert_t)); +#else + grid->widthLodError = ri.Hunk_Alloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = ri.Hunk_Alloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); + + grid->numIndexes = numIndexes; + grid->indexes = ri.Hunk_Alloc(grid->numIndexes * sizeof(glIndex_t), h_low); + Com_Memcpy(grid->indexes, indexes, numIndexes * sizeof(glIndex_t)); + + grid->numVerts = (width * height); + grid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->cullBounds[0], grid->cullBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->cullBounds[0], grid->cullBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->cullBounds[0], grid->cullBounds[1], grid->cullOrigin ); + VectorScale( grid->cullOrigin, 0.5f, grid->cullOrigin ); + VectorSubtract( grid->cullBounds[0], grid->cullOrigin, tmpVec ); + grid->cullRadius = VectorLength( tmpVec ); + + VectorCopy( grid->cullOrigin, grid->lodOrigin ); + grid->lodRadius = grid->cullRadius; + // +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +static void R_FreeSurfaceGridMeshData( srfBspSurface_t *grid ) { + ri.Free(grid->widthLodError); + ri.Free(grid->heightLodError); + ri.Free(grid->indexes); + ri.Free(grid->verts); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +void R_SubdividePatchToGrid( srfBspSurface_t *grid, int width, int height, + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + srfVert_t_cleared( prev ); + srfVert_t_cleared( next ); + srfVert_t_cleared( mid ); + float len, maxLen; + int dir; + int t; + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + int numIndexes; + static glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]; + int consecutiveComplete; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + consecutiveComplete = 0; + + // horizontal subdivisions + for ( j = 0 ; ; j = (j + 2) % (width - 1) ) { + // check subdivided midpoints against control points + + // FIXME: also check midpoints of adjacent patches against the control points + // this would basically stitch all patches in the same LOD group together. + + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t midxyz2; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + + ctrl[i][j+2].xyz[l] ) * 0.25f; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz2); + len = VectorLengthSquared( midxyz2 ); // we will do the sqrt later + if ( len > maxLen ) { + maxLen = len; + } + } + + maxLen = sqrt(maxLen); + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1f ) { + errorTable[dir][j+1] = 999; + // if we go over the whole grid twice without adding any columns, stop + if (++consecutiveComplete >= width) + break; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j+1] = 1.0f/maxLen; + break; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j+1] = 1.0f/maxLen; + // if we go over the whole grid twice without adding any columns, stop + if (++consecutiveComplete >= width) + break; + continue; // didn't need subdivision + } + + errorTable[dir][j+2] = 1.0f/maxLen; + + consecutiveComplete = 0; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); + LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k-2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // skip the new one, we'll get it on the next pass + j += 2; + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j-1] = ctrl[k][j]; + } + errorTable[0][j-1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j-1][k] = ctrl[j][k]; + } + errorTable[1][j-1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate indexes + numIndexes = MakeMeshIndexes(width, height, indexes); + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + MakeMeshTangentVectors(width, height, ctrl, numIndexes, indexes); + + R_CreateSurfaceGridMesh(grid, width, height, ctrl, errorTable, numIndexes, indexes); +} + +/* +=============== +R_GridInsertColumn +=============== +*/ +void R_GridInsertColumn( srfBspSurface_t *grid, int column, int row, vec3_t point, float loderror ) { + int i, j; + int width, height, oldwidth; + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + int numIndexes; + static glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]; + + oldwidth = 0; + width = grid->width + 1; + if (width > MAX_GRID_SIZE) + return; + height = grid->height; + for (i = 0; i < width; i++) { + if (i == column) { + //insert new column + for (j = 0; j < grid->height; j++) { + LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); + if (j == row) + VectorCopy(point, ctrl[j][i].xyz); + } + errorTable[0][i] = loderror; + continue; + } + errorTable[0][i] = grid->widthLodError[oldwidth]; + for (j = 0; j < grid->height; j++) { + ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; + } + oldwidth++; + } + for (j = 0; j < grid->height; j++) { + errorTable[1][j] = grid->heightLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + + // calculate indexes + numIndexes = MakeMeshIndexes(width, height, indexes); + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + MakeMeshTangentVectors(width, height, ctrl, numIndexes, indexes); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMeshData(grid); + // create a new grid + R_CreateSurfaceGridMesh(grid, width, height, ctrl, errorTable, numIndexes, indexes); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); +} + +/* +=============== +R_GridInsertRow +=============== +*/ +void R_GridInsertRow( srfBspSurface_t *grid, int row, int column, vec3_t point, float loderror ) { + int i, j; + int width, height, oldheight; + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + int numIndexes; + static glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]; + + oldheight = 0; + width = grid->width; + height = grid->height + 1; + if (height > MAX_GRID_SIZE) + return; + for (i = 0; i < height; i++) { + if (i == row) { + //insert new row + for (j = 0; j < grid->width; j++) { + LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); + if (j == column) + VectorCopy(point, ctrl[i][j].xyz); + } + errorTable[1][i] = loderror; + continue; + } + errorTable[1][i] = grid->heightLodError[oldheight]; + for (j = 0; j < grid->width; j++) { + ctrl[i][j] = grid->verts[oldheight * grid->width + j]; + } + oldheight++; + } + for (j = 0; j < grid->width; j++) { + errorTable[0][j] = grid->widthLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + + // calculate indexes + numIndexes = MakeMeshIndexes(width, height, indexes); + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + MakeMeshTangentVectors(width, height, ctrl, numIndexes, indexes); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMeshData(grid); + // create a new grid + R_CreateSurfaceGridMesh(grid, width, height, ctrl, errorTable, numIndexes, indexes); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); +} diff --git a/src/renderergl2/tr_dsa.cpp b/src/renderergl2/tr_dsa.cpp new file mode 100644 index 0000000..8ea42bc --- /dev/null +++ b/src/renderergl2/tr_dsa.cpp @@ -0,0 +1,287 @@ +/* +=========================================================================== +Copyright (C) 2016 James Canete +Copyright (C) 2015-2019 GrangerHub + +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 3 +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, see . +=========================================================================== +*/ + +#include "tr_local.h" + +#include "tr_dsa.h" + +static struct +{ + GLuint textures[NUM_TEXTURE_BUNDLES]; + GLenum texunit; + + GLuint program; + + GLuint drawFramebuffer; + GLuint readFramebuffer; + GLuint renderbuffer; +} +glDsaState; + +void GL_BindNullTextures() +{ + int i; + + if (glRefConfig.directStateAccess) + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + qglBindMultiTextureEXT(GL_TEXTURE0 + i, GL_TEXTURE_2D, 0); + glDsaState.textures[i] = 0; + } + } + else + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + qglActiveTexture(GL_TEXTURE0 + i); + qglBindTexture(GL_TEXTURE_2D, 0); + glDsaState.textures[i] = 0; + } + + qglActiveTexture(GL_TEXTURE0); + glDsaState.texunit = GL_TEXTURE0; + } +} + +int GL_BindMultiTexture(GLenum texunit, GLenum target, GLuint texture) +{ + GLuint tmu = texunit - GL_TEXTURE0; + + if (glDsaState.textures[tmu] == texture) + return 0; + + if (target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z) + target = GL_TEXTURE_CUBE_MAP; + + qglBindMultiTextureEXT(texunit, target, texture); + glDsaState.textures[tmu] = texture; + return 1; +} + +GLvoid APIENTRY GLDSA_BindMultiTextureEXT(GLenum texunit, GLenum target, GLuint texture) +{ + if (glDsaState.texunit != texunit) + { + qglActiveTexture(texunit); + glDsaState.texunit = texunit; + } + + qglBindTexture(target, texture); +} + +GLvoid APIENTRY GLDSA_TextureParameterfEXT(GLuint texture, GLenum target, GLenum pname, GLfloat param) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglTexParameterf(target, pname, param); +} + +GLvoid APIENTRY GLDSA_TextureParameteriEXT(GLuint texture, GLenum target, GLenum pname, GLint param) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglTexParameteri(target, pname, param); +} + +GLvoid APIENTRY GLDSA_TextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat, + GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglTexImage2D(target, level, internalformat, width, height, border, format, type, pixels); +} + +GLvoid APIENTRY GLDSA_TextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); +} + +GLvoid APIENTRY GLDSA_CopyTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint x, GLint y, GLsizei width, GLsizei height) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); +} + +GLvoid APIENTRY GLDSA_CompressedTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, + GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data); +} + +GLvoid APIENTRY GLDSA_CompressedTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, + GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, + GLsizei imageSize, const GLvoid *data) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data); +} + +GLvoid APIENTRY GLDSA_GenerateTextureMipmapEXT(GLuint texture, GLenum target) +{ + GL_BindMultiTexture(glDsaState.texunit, target, texture); + qglGenerateMipmap(target); +} + +void GL_BindNullProgram() +{ + qglUseProgram(0); + glDsaState.program = 0; +} + +int GL_UseProgram(GLuint program) +{ + if (glDsaState.program == program) + return 0; + + qglUseProgram(program); + glDsaState.program = program; + return 1; +} + +GLvoid APIENTRY GLDSA_ProgramUniform1iEXT(GLuint program, GLint location, GLint v0) +{ + GL_UseProgram(program); + qglUniform1i(location, v0); +} + +GLvoid APIENTRY GLDSA_ProgramUniform1fEXT(GLuint program, GLint location, GLfloat v0) +{ + GL_UseProgram(program); + qglUniform1f(location, v0); +} + +GLvoid APIENTRY GLDSA_ProgramUniform2fEXT(GLuint program, GLint location, + GLfloat v0, GLfloat v1) +{ + GL_UseProgram(program); + qglUniform2f(location, v0, v1); +} + +GLvoid APIENTRY GLDSA_ProgramUniform3fEXT(GLuint program, GLint location, + GLfloat v0, GLfloat v1, GLfloat v2) +{ + GL_UseProgram(program); + qglUniform3f(location, v0, v1, v2); +} + +GLvoid APIENTRY GLDSA_ProgramUniform4fEXT(GLuint program, GLint location, + GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) +{ + GL_UseProgram(program); + qglUniform4f(location, v0, v1, v2, v3); +} + +GLvoid APIENTRY GLDSA_ProgramUniform1fvEXT(GLuint program, GLint location, + GLsizei count, const GLfloat *value) +{ + GL_UseProgram(program); + qglUniform1fv(location, count, value); +} + +GLvoid APIENTRY GLDSA_ProgramUniformMatrix4fvEXT(GLuint program, GLint location, + GLsizei count, GLboolean transpose, + const GLfloat *value) +{ + GL_UseProgram(program); + qglUniformMatrix4fv(location, count, transpose, value); +} + +void GL_BindNullFramebuffers() +{ + qglBindFramebuffer(GL_FRAMEBUFFER, 0); + glDsaState.drawFramebuffer = glDsaState.readFramebuffer = 0; + qglBindRenderbuffer(GL_RENDERBUFFER, 0); + glDsaState.renderbuffer = 0; +} + +void GL_BindFramebuffer(GLenum target, GLuint framebuffer) +{ + switch (target) + { + case GL_FRAMEBUFFER: + if (framebuffer != glDsaState.drawFramebuffer || framebuffer != glDsaState.readFramebuffer) + { + qglBindFramebuffer(target, framebuffer); + glDsaState.drawFramebuffer = glDsaState.readFramebuffer = framebuffer; + } + break; + + case GL_DRAW_FRAMEBUFFER: + if (framebuffer != glDsaState.drawFramebuffer) + { + qglBindFramebuffer(target, framebuffer); + glDsaState.drawFramebuffer = framebuffer; + } + break; + + case GL_READ_FRAMEBUFFER: + if (framebuffer != glDsaState.readFramebuffer) + { + qglBindFramebuffer(target, framebuffer); + glDsaState.readFramebuffer = framebuffer; + } + break; + } +} + +void GL_BindRenderbuffer(GLuint renderbuffer) +{ + if (renderbuffer != glDsaState.renderbuffer) + { + qglBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glDsaState.renderbuffer = renderbuffer; + } +} + +GLvoid APIENTRY GLDSA_NamedRenderbufferStorageEXT(GLuint renderbuffer, + GLenum internalformat, GLsizei width, GLsizei height) +{ + GL_BindRenderbuffer(renderbuffer); + qglRenderbufferStorage(GL_RENDERBUFFER, internalformat, width, height); +} + +GLvoid APIENTRY GLDSA_NamedRenderbufferStorageMultisampleEXT(GLuint renderbuffer, + GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) +{ + GL_BindRenderbuffer(renderbuffer); + qglRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat, width, height); +} + +GLenum APIENTRY GLDSA_CheckNamedFramebufferStatusEXT(GLuint framebuffer, GLenum target) +{ + GL_BindFramebuffer(target, framebuffer); + return qglCheckFramebufferStatus(target); +} + +GLvoid APIENTRY GLDSA_NamedFramebufferTexture2DEXT(GLuint framebuffer, + GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + GL_BindFramebuffer(GL_FRAMEBUFFER, framebuffer); + qglFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, texture, level); +} + +GLvoid APIENTRY GLDSA_NamedFramebufferRenderbufferEXT(GLuint framebuffer, + GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) +{ + GL_BindFramebuffer(GL_FRAMEBUFFER, framebuffer); + qglFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, renderbuffertarget, renderbuffer); +} diff --git a/src/renderergl2/tr_dsa.h b/src/renderergl2/tr_dsa.h new file mode 100644 index 0000000..92c6563 --- /dev/null +++ b/src/renderergl2/tr_dsa.h @@ -0,0 +1,80 @@ +/* +=========================================================================== +Copyright (C) 2016 James Canete +Copyright (C) 2015-2019 GrangerHub + +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 3 +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, see . +=========================================================================== +*/ + +#ifndef __TR_DSA_H__ +#define __TR_DSA_H__ + +#include "renderercommon/qgl.h" + +void GL_BindNullTextures(void); +int GL_BindMultiTexture(GLenum texunit, GLenum target, GLuint texture); + +GLvoid APIENTRY GLDSA_BindMultiTextureEXT(GLenum texunit, GLenum target, GLuint texture); +GLvoid APIENTRY GLDSA_TextureParameterfEXT(GLuint texture, GLenum target, GLenum pname, GLfloat param); +GLvoid APIENTRY GLDSA_TextureParameteriEXT(GLuint texture, GLenum target, GLenum pname, GLint param); +GLvoid APIENTRY GLDSA_TextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat, + GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLvoid APIENTRY GLDSA_TextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLvoid APIENTRY GLDSA_CopyTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint x, GLint y, GLsizei width, GLsizei height); +GLvoid APIENTRY GLDSA_CompressedTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, + GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +GLvoid APIENTRY GLDSA_CompressedTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, + GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, + GLsizei imageSize, const GLvoid *data); + +GLvoid APIENTRY GLDSA_GenerateTextureMipmapEXT(GLuint texture, GLenum target); + +void GL_BindNullProgram(void); +int GL_UseProgram(GLuint program); + +GLvoid APIENTRY GLDSA_ProgramUniform1iEXT(GLuint program, GLint location, GLint v0); +GLvoid APIENTRY GLDSA_ProgramUniform1fEXT(GLuint program, GLint location, GLfloat v0); +GLvoid APIENTRY GLDSA_ProgramUniform2fEXT(GLuint program, GLint location, + GLfloat v0, GLfloat v1); +GLvoid APIENTRY GLDSA_ProgramUniform3fEXT(GLuint program, GLint location, + GLfloat v0, GLfloat v1, GLfloat v2); +GLvoid APIENTRY GLDSA_ProgramUniform4fEXT(GLuint program, GLint location, + GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLvoid APIENTRY GLDSA_ProgramUniform1fvEXT(GLuint program, GLint location, + GLsizei count, const GLfloat *value); +GLvoid APIENTRY GLDSA_ProgramUniformMatrix4fvEXT(GLuint program, GLint location, + GLsizei count, GLboolean transpose, + const GLfloat *value); + +void GL_BindNullFramebuffers(void); +void GL_BindFramebuffer(GLenum target, GLuint framebuffer); +void GL_BindRenderbuffer(GLuint renderbuffer); + +GLvoid APIENTRY GLDSA_NamedRenderbufferStorageEXT(GLuint renderbuffer, + GLenum internalformat, GLsizei width, GLsizei height); + +GLvoid APIENTRY GLDSA_NamedRenderbufferStorageMultisampleEXT(GLuint renderbuffer, + GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); + +GLenum APIENTRY GLDSA_CheckNamedFramebufferStatusEXT(GLuint framebuffer, GLenum target); +GLvoid APIENTRY GLDSA_NamedFramebufferTexture2DEXT(GLuint framebuffer, + GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLvoid APIENTRY GLDSA_NamedFramebufferRenderbufferEXT(GLuint framebuffer, + GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); + + +#endif diff --git a/src/renderergl2/tr_extensions.cpp b/src/renderergl2/tr_extensions.cpp new file mode 100644 index 0000000..6f73d29 --- /dev/null +++ b/src/renderergl2/tr_extensions.cpp @@ -0,0 +1,279 @@ +/* +=========================================================================== +Copyright (C) 2011 James Canete (use.less01@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous Arena source code. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_extensions.c - extensions needed by the renderer not in sdl_glimp.c + +#ifdef USE_LOCAL_HEADERS +#include "SDL.h" +#else +#include +#endif + +#include "tr_local.h" +#include "tr_dsa.h" + +#define GLE(ret, name, ...) name##proc *qgl##name; +QGL_1_3_PROCS; +QGL_1_5_PROCS; +QGL_2_0_PROCS; +QGL_3_0_PROCS; +QGL_ARB_framebuffer_object_PROCS; +QGL_ARB_vertex_array_object_PROCS; +QGL_EXT_direct_state_access_PROCS; +#undef GLE + +void GLimp_InitExtraExtensions() +{ + const char *extension; + const char *result[3] = {"...ignoring %s\n", "...using %s\n", "...%s not found\n"}; + + // Check OpenGL version + sscanf(glConfig.version_string, "%d.%d", &glRefConfig.openglMajorVersion, &glRefConfig.openglMinorVersion); + if (glRefConfig.openglMajorVersion < 2) ri.Error(ERR_FATAL, "OpenGL 2.0 required!"); + ri.Printf(PRINT_ALL, "...using OpenGL %s\n", glConfig.version_string); + + bool q_gl_version_at_least_3_0 = (glRefConfig.openglMajorVersion >= 3); + bool q_gl_version_at_least_3_2 = (glRefConfig.openglMajorVersion > 3 || + (glRefConfig.openglMajorVersion == 3 && glRefConfig.openglMinorVersion > 2)); + + // Check if we need Intel graphics specific fixes. + glRefConfig.intelGraphics = qfalse; + if (strstr((char *)qglGetString(GL_RENDERER), "Intel")) glRefConfig.intelGraphics = qtrue; + + // set DSA fallbacks +#define GLE(ret, name, ...) qgl##name = GLDSA_##name; + QGL_EXT_direct_state_access_PROCS; +#undef GLE + + // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a +#define GLE(ret, name, ...) qgl##name = (name##proc *)SDL_GL_GetProcAddress("gl" #name); + + // OpenGL 1.3, was GL_ARB_texture_compression + QGL_1_3_PROCS; + + // OpenGL 1.5, was GL_ARB_vertex_buffer_object and GL_ARB_occlusion_query + QGL_1_5_PROCS; + glRefConfig.occlusionQuery = qtrue; + + // OpenGL 2.0, was GL_ARB_shading_language_100, GL_ARB_vertex_program, GL_ARB_shader_objects, and + // GL_ARB_vertex_shader + QGL_2_0_PROCS; + + // OpenGL 3.0 - no matching extension + // QGL_*_PROCS becomes several functions, do not remove {} + if (q_gl_version_at_least_3_0) + { + QGL_3_0_PROCS; + } + + // OpenGL 3.0 - GL_ARB_framebuffer_object + extension = "GL_ARB_framebuffer_object"; + glRefConfig.framebufferObject = qfalse; + glRefConfig.framebufferBlit = qfalse; + glRefConfig.framebufferMultisample = qfalse; + if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.framebufferObject = !!r_ext_framebuffer_object->integer; + glRefConfig.framebufferBlit = qtrue; + glRefConfig.framebufferMultisample = qtrue; + + qglGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glRefConfig.maxRenderbufferSize); + qglGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &glRefConfig.maxColorAttachments); + + QGL_ARB_framebuffer_object_PROCS; + + ri.Printf(PRINT_ALL, result[glRefConfig.framebufferObject], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // OpenGL 3.0 - GL_ARB_vertex_array_object + extension = "GL_ARB_vertex_array_object"; + glRefConfig.vertexArrayObject = qfalse; + if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension)) + { + if (q_gl_version_at_least_3_0) + { + // force VAO, core context requires it + glRefConfig.vertexArrayObject = qtrue; + } + else + { + glRefConfig.vertexArrayObject = !!r_arb_vertex_array_object->integer; + } + + QGL_ARB_vertex_array_object_PROCS; + + ri.Printf(PRINT_ALL, result[glRefConfig.vertexArrayObject], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // OpenGL 3.0 - GL_ARB_texture_float + extension = "GL_ARB_texture_float"; + glRefConfig.textureFloat = qfalse; + if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.textureFloat = !!r_ext_texture_float->integer; + + ri.Printf(PRINT_ALL, result[glRefConfig.textureFloat], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // OpenGL 3.2 - GL_ARB_depth_clamp + extension = "GL_ARB_depth_clamp"; + glRefConfig.depthClamp = qfalse; + if (q_gl_version_at_least_3_2 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.depthClamp = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.depthClamp], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // OpenGL 3.2 - GL_ARB_seamless_cube_map + extension = "GL_ARB_seamless_cube_map"; + glRefConfig.seamlessCubeMap = qfalse; + if (q_gl_version_at_least_3_2 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.seamlessCubeMap = !!r_arb_seamless_cube_map->integer; + + ri.Printf(PRINT_ALL, result[glRefConfig.seamlessCubeMap], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // Determine GLSL version + if (1) + { + char version[256]; + + Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version)); + + sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion); + + ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version); + } + + glRefConfig.memInfo = MI_NONE; + + // GL_NVX_gpu_memory_info + extension = "GL_NVX_gpu_memory_info"; + if (SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.memInfo = MI_NVX; + + ri.Printf(PRINT_ALL, result[1], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ATI_meminfo + extension = "GL_ATI_meminfo"; + if (SDL_GL_ExtensionSupported(extension)) + { + if (glRefConfig.memInfo == MI_NONE) + { + glRefConfig.memInfo = MI_ATI; + + ri.Printf(PRINT_ALL, result[1], extension); + } + else + { + ri.Printf(PRINT_ALL, result[0], extension); + } + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + glRefConfig.textureCompression = TCR_NONE; + + // GL_ARB_texture_compression_rgtc + extension = "GL_ARB_texture_compression_rgtc"; + if (SDL_GL_ExtensionSupported(extension)) + { + bool useRgtc = r_ext_compressed_textures->integer >= 1; + + if (useRgtc) glRefConfig.textureCompression |= TCR_RGTC; + + ri.Printf(PRINT_ALL, result[useRgtc], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + glRefConfig.swizzleNormalmap = r_ext_compressed_textures->integer && !(glRefConfig.textureCompression & TCR_RGTC); + + // GL_ARB_texture_compression_bptc + extension = "GL_ARB_texture_compression_bptc"; + if (SDL_GL_ExtensionSupported(extension)) + { + bool useBptc = r_ext_compressed_textures->integer >= 2; + + if (useBptc) glRefConfig.textureCompression |= TCR_BPTC; + + ri.Printf(PRINT_ALL, result[useBptc], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_direct_state_access + extension = "GL_EXT_direct_state_access"; + glRefConfig.directStateAccess = qfalse; + if (SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.directStateAccess = !!r_ext_direct_state_access->integer; + + // QGL_*_PROCS becomes several functions, do not remove {} + if (glRefConfig.directStateAccess) + { + QGL_EXT_direct_state_access_PROCS; + } + + ri.Printf(PRINT_ALL, result[glRefConfig.directStateAccess], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + +#undef GLE +} diff --git a/src/renderergl2/tr_extramath.cpp b/src/renderergl2/tr_extramath.cpp new file mode 100644 index 0000000..abe75fa --- /dev/null +++ b/src/renderergl2/tr_extramath.cpp @@ -0,0 +1,248 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_extramath.c - extra math needed by the renderer not in qmath.c + +#include "tr_local.h" + +// Some matrix helper functions +// FIXME: do these already exist in ioq3 and I don't know about them? + +void Mat4Zero( mat4_t out ) +{ + out[ 0] = 0.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = 0.0f; + out[ 1] = 0.0f; out[ 5] = 0.0f; out[ 9] = 0.0f; out[13] = 0.0f; + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 0.0f; out[14] = 0.0f; + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 0.0f; +} + +void Mat4Identity( mat4_t out ) +{ + out[ 0] = 1.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = 0.0f; + out[ 1] = 0.0f; out[ 5] = 1.0f; out[ 9] = 0.0f; out[13] = 0.0f; + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 1.0f; out[14] = 0.0f; + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void Mat4Copy( const mat4_t in, mat4_t out ) +{ + out[ 0] = in[ 0]; out[ 4] = in[ 4]; out[ 8] = in[ 8]; out[12] = in[12]; + out[ 1] = in[ 1]; out[ 5] = in[ 5]; out[ 9] = in[ 9]; out[13] = in[13]; + out[ 2] = in[ 2]; out[ 6] = in[ 6]; out[10] = in[10]; out[14] = in[14]; + out[ 3] = in[ 3]; out[ 7] = in[ 7]; out[11] = in[11]; out[15] = in[15]; +} + +void Mat4Multiply( const mat4_t in1, const mat4_t in2, mat4_t out ) +{ + out[ 0] = in1[ 0] * in2[ 0] + in1[ 4] * in2[ 1] + in1[ 8] * in2[ 2] + in1[12] * in2[ 3]; + out[ 1] = in1[ 1] * in2[ 0] + in1[ 5] * in2[ 1] + in1[ 9] * in2[ 2] + in1[13] * in2[ 3]; + out[ 2] = in1[ 2] * in2[ 0] + in1[ 6] * in2[ 1] + in1[10] * in2[ 2] + in1[14] * in2[ 3]; + out[ 3] = in1[ 3] * in2[ 0] + in1[ 7] * in2[ 1] + in1[11] * in2[ 2] + in1[15] * in2[ 3]; + + out[ 4] = in1[ 0] * in2[ 4] + in1[ 4] * in2[ 5] + in1[ 8] * in2[ 6] + in1[12] * in2[ 7]; + out[ 5] = in1[ 1] * in2[ 4] + in1[ 5] * in2[ 5] + in1[ 9] * in2[ 6] + in1[13] * in2[ 7]; + out[ 6] = in1[ 2] * in2[ 4] + in1[ 6] * in2[ 5] + in1[10] * in2[ 6] + in1[14] * in2[ 7]; + out[ 7] = in1[ 3] * in2[ 4] + in1[ 7] * in2[ 5] + in1[11] * in2[ 6] + in1[15] * in2[ 7]; + + out[ 8] = in1[ 0] * in2[ 8] + in1[ 4] * in2[ 9] + in1[ 8] * in2[10] + in1[12] * in2[11]; + out[ 9] = in1[ 1] * in2[ 8] + in1[ 5] * in2[ 9] + in1[ 9] * in2[10] + in1[13] * in2[11]; + out[10] = in1[ 2] * in2[ 8] + in1[ 6] * in2[ 9] + in1[10] * in2[10] + in1[14] * in2[11]; + out[11] = in1[ 3] * in2[ 8] + in1[ 7] * in2[ 9] + in1[11] * in2[10] + in1[15] * in2[11]; + + out[12] = in1[ 0] * in2[12] + in1[ 4] * in2[13] + in1[ 8] * in2[14] + in1[12] * in2[15]; + out[13] = in1[ 1] * in2[12] + in1[ 5] * in2[13] + in1[ 9] * in2[14] + in1[13] * in2[15]; + out[14] = in1[ 2] * in2[12] + in1[ 6] * in2[13] + in1[10] * in2[14] + in1[14] * in2[15]; + out[15] = in1[ 3] * in2[12] + in1[ 7] * in2[13] + in1[11] * in2[14] + in1[15] * in2[15]; +} + +void Mat4Transform( const mat4_t in1, const vec4_t in2, vec4_t out ) +{ + out[ 0] = in1[ 0] * in2[ 0] + in1[ 4] * in2[ 1] + in1[ 8] * in2[ 2] + in1[12] * in2[ 3]; + out[ 1] = in1[ 1] * in2[ 0] + in1[ 5] * in2[ 1] + in1[ 9] * in2[ 2] + in1[13] * in2[ 3]; + out[ 2] = in1[ 2] * in2[ 0] + in1[ 6] * in2[ 1] + in1[10] * in2[ 2] + in1[14] * in2[ 3]; + out[ 3] = in1[ 3] * in2[ 0] + in1[ 7] * in2[ 1] + in1[11] * in2[ 2] + in1[15] * in2[ 3]; +} + +bool Mat4Compare( const mat4_t a, const mat4_t b ) +{ + return !(a[ 0] != b[ 0] || a[ 4] != b[ 4] || a[ 8] != b[ 8] || a[12] != b[12] || + a[ 1] != b[ 1] || a[ 5] != b[ 5] || a[ 9] != b[ 9] || a[13] != b[13] || + a[ 2] != b[ 2] || a[ 6] != b[ 6] || a[10] != b[10] || a[14] != b[14] || + a[ 3] != b[ 3] || a[ 7] != b[ 7] || a[11] != b[11] || a[15] != b[15]); +} + +void Mat4Dump( const mat4_t in ) +{ + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 0], in[ 4], in[ 8], in[12]); + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 1], in[ 5], in[ 9], in[13]); + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 2], in[ 6], in[10], in[14]); + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 3], in[ 7], in[11], in[15]); +} + +void Mat4Translation( vec3_t vec, mat4_t out ) +{ + out[ 0] = 1.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = vec[0]; + out[ 1] = 0.0f; out[ 5] = 1.0f; out[ 9] = 0.0f; out[13] = vec[1]; + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 1.0f; out[14] = vec[2]; + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void Mat4Ortho( float left, float right, float bottom, float top, float znear, float zfar, mat4_t out ) +{ + out[ 0] = 2.0f / (right - left); out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = -(right + left) / (right - left); + out[ 1] = 0.0f; out[ 5] = 2.0f / (top - bottom); out[ 9] = 0.0f; out[13] = -(top + bottom) / (top - bottom); + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 2.0f / (zfar - znear); out[14] = -(zfar + znear) / (zfar - znear); + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void Mat4View(vec3_t axes[3], vec3_t origin, mat4_t out) +{ + out[0] = axes[0][0]; + out[1] = axes[1][0]; + out[2] = axes[2][0]; + out[3] = 0; + + out[4] = axes[0][1]; + out[5] = axes[1][1]; + out[6] = axes[2][1]; + out[7] = 0; + + out[8] = axes[0][2]; + out[9] = axes[1][2]; + out[10] = axes[2][2]; + out[11] = 0; + + out[12] = -DotProduct(origin, axes[0]); + out[13] = -DotProduct(origin, axes[1]); + out[14] = -DotProduct(origin, axes[2]); + out[15] = 1; +} + +void Mat4SimpleInverse( const mat4_t in, mat4_t out) +{ + vec3_t v; + float invSqrLen; + + VectorCopy(in + 0, v); + invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + out[ 0] = v[0]; out[ 4] = v[1]; out[ 8] = v[2]; out[12] = -DotProduct(v, &in[12]); + + VectorCopy(in + 4, v); + invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + out[ 1] = v[0]; out[ 5] = v[1]; out[ 9] = v[2]; out[13] = -DotProduct(v, &in[12]); + + VectorCopy(in + 8, v); + invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + out[ 2] = v[0]; out[ 6] = v[1]; out[10] = v[2]; out[14] = -DotProduct(v, &in[12]); + + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void VectorLerp( vec3_t a, vec3_t b, float lerp, vec3_t c) +{ + c[0] = a[0] * (1.0f - lerp) + b[0] * lerp; + c[1] = a[1] * (1.0f - lerp) + b[1] * lerp; + c[2] = a[2] * (1.0f - lerp) + b[2] * lerp; +} + +bool SpheresIntersect(vec3_t origin1, float radius1, vec3_t origin2, float radius2) +{ + float radiusSum = radius1 + radius2; + vec3_t diff; + + VectorSubtract(origin1, origin2, diff); + + if (DotProduct(diff, diff) <= radiusSum * radiusSum) + { + return true; + } + + return false; +} + +void BoundingSphereOfSpheres(vec3_t origin1, float radius1, vec3_t origin2, float radius2, vec3_t origin3, float *radius3) +{ + vec3_t diff; + + VectorScale(origin1, 0.5f, origin3); + VectorMA(origin3, 0.5f, origin2, origin3); + + VectorSubtract(origin1, origin2, diff); + *radius3 = VectorLength(diff) * 0.5f + MAX(radius1, radius2); +} + +int NextPowerOfTwo(int in) +{ + int out; + + for (out = 1; out < in; out <<= 1) + ; + + return out; +} + +union f32_u { + float f; + uint32_t ui; + struct { + unsigned int fraction:23; + unsigned int exponent:8; + unsigned int sign:1; + } pack; +}; + +union f16_u { + uint16_t ui; + struct { + unsigned int fraction:10; + unsigned int exponent:5; + unsigned int sign:1; + } pack; +}; + +uint16_t FloatToHalf(float in) +{ + union f32_u f32; + union f16_u f16; + + f32.f = in; + + f16.pack.exponent = CLAMP((int)(f32.pack.exponent) - 112, 0, 31); + f16.pack.fraction = f32.pack.fraction >> 13; + f16.pack.sign = f32.pack.sign; + + return f16.ui; +} + +float HalfToFloat(uint16_t in) +{ + union f32_u f32; + union f16_u f16; + + f16.ui = in; + + f32.pack.exponent = (int)(f16.pack.exponent) + 112; + f32.pack.fraction = f16.pack.fraction << 13; + f32.pack.sign = f16.pack.sign; + + return f32.f; +} diff --git a/src/renderergl2/tr_extramath.h b/src/renderergl2/tr_extramath.h new file mode 100644 index 0000000..20c9278 --- /dev/null +++ b/src/renderergl2/tr_extramath.h @@ -0,0 +1,104 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_extramath.h + +#ifndef __TR_EXTRAMATH_H__ +#define __TR_EXTRAMATH_H__ + +typedef vec_t mat4_t[16]; +typedef int ivec2_t[2]; +typedef int ivec3_t[3]; +typedef int ivec4_t[4]; + +void Mat4Zero( mat4_t out ); +void Mat4Identity( mat4_t out ); +void Mat4Copy( const mat4_t in, mat4_t out ); +void Mat4Multiply( const mat4_t in1, const mat4_t in2, mat4_t out ); +void Mat4Transform( const mat4_t in1, const vec4_t in2, vec4_t out ); +bool Mat4Compare(const mat4_t a, const mat4_t b); +void Mat4Dump( const mat4_t in ); +void Mat4Translation( vec3_t vec, mat4_t out ); +void Mat4Ortho( float left, float right, float bottom, float top, float znear, float zfar, mat4_t out ); +void Mat4View(vec3_t axes[3], vec3_t origin, mat4_t out); +void Mat4SimpleInverse( const mat4_t in, mat4_t out); + +#define VectorCopy2(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) +#define VectorSet2(v,x,y) ((v)[0]=(x),(v)[1]=(y)); + +#define VectorCopy4(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorSet4(v,x,y,z,w) ((v)[0]=(x),(v)[1]=(y),(v)[2]=(z),(v)[3]=(w)) +#define DotProduct4(a,b) ((a)[0]*(b)[0] + (a)[1]*(b)[1] + (a)[2]*(b)[2] + (a)[3]*(b)[3]) +#define VectorScale4(a,b,c) ((c)[0]=(a)[0]*(b),(c)[1]=(a)[1]*(b),(c)[2]=(a)[2]*(b),(c)[3]=(a)[3]*(b)) + +#define VectorCopy5(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3],(b)[4]=(a)[4]) + +#define OffsetByteToFloat(a) ((float)(a) * 1.0f/127.5f - 1.0f) +#define FloatToOffsetByte(a) (byte)((a) * 127.5f + 128.0f) +#define ByteToFloat(a) ((float)(a) * 1.0f/255.0f) +#define FloatToByte(a) (byte)((a) * 255.0f) + +static ID_INLINE int VectorCompare4(const vec4_t v1, const vec4_t v2) +{ + if(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3]) + { + return 0; + } + return 1; +} + +static ID_INLINE int VectorCompare5(const vec5_t v1, const vec5_t v2) +{ + if(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3] || v1[4] != v2[4]) + { + return 0; + } + return 1; +} + +void VectorLerp( vec3_t a, vec3_t b, float lerp, vec3_t c); + + +bool SpheresIntersect(vec3_t origin1, float radius1, vec3_t origin2, float radius2); +void BoundingSphereOfSpheres(vec3_t origin1, float radius1, vec3_t origin2, float radius2, vec3_t origin3, float *radius3); + +#ifndef SGN +#define SGN(x) (((x) >= 0) ? !!(x) : -1) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef CLAMP +#define CLAMP(a,b,c) MIN(MAX((a),(b)),(c)) +#endif + +int NextPowerOfTwo(int in); +unsigned short FloatToHalf(float in); +float HalfToFloat(unsigned short in); + +#endif diff --git a/src/renderergl2/tr_extratypes.h b/src/renderergl2/tr_extratypes.h new file mode 100644 index 0000000..c5c99c8 --- /dev/null +++ b/src/renderergl2/tr_extratypes.h @@ -0,0 +1,40 @@ +/* +=========================================================================== +Copyright (C) 2009-2011 Andrei Drexler, Richard Allen, James Canete +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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. + +along with Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __TR_EXTRATYPES_H__ +#define __TR_EXTRATYPES_H__ + +// tr_extratypes.h, for mods that want to extend tr_types.h without losing compatibility with original VMs + +// extra refdef flags start at 0x0008 +#define RDF_NOFOG 0x0008 // don't apply fog to polys added using RE_AddPolyToScene +#define RDF_EXTRA 0x0010 // Makro - refdefex_t to follow after refdef_t +#define RDF_SUNLIGHT 0x0020 // SmileTheory - render sunlight and shadows + +typedef struct { + float blurFactor; + float sunDir[3]; + float sunCol[3]; + float sunAmbCol[3]; +} refdefex_t; + +#endif diff --git a/src/renderergl2/tr_fbo.cpp b/src/renderergl2/tr_fbo.cpp new file mode 100644 index 0000000..5120540 --- /dev/null +++ b/src/renderergl2/tr_fbo.cpp @@ -0,0 +1,659 @@ +/* +=========================================================================== +Copyright (C) 2006 Kirk Barnes +Copyright (C) 2006-2008 Robert Beckebans +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_fbo.c +#include "tr_local.h" + +#include "tr_dsa.h" + +/* +============= +R_CheckFBO +============= +*/ +bool R_CheckFBO(const FBO_t * fbo) +{ + GLenum code = qglCheckNamedFramebufferStatusEXT(fbo->frameBuffer, GL_FRAMEBUFFER); + + if(code == GL_FRAMEBUFFER_COMPLETE) + return true; + + // an error occured + switch (code) + { + case GL_FRAMEBUFFER_UNSUPPORTED: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Unsupported framebuffer format\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete attachment\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing attachment\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing draw buffer\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing read buffer\n", fbo->name); + break; + + default: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) unknown error 0x%X\n", fbo->name, code); + break; + } + + return false; +} + +/* +============ +FBO_Create +============ +*/ +FBO_t *FBO_Create(const char *name, int width, int height) +{ + FBO_t *fbo; + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "FBO_Create: \"%s\" is too long", name); + } + + if(width <= 0 || width > glRefConfig.maxRenderbufferSize) + { + ri.Error(ERR_DROP, "FBO_Create: bad width %i", width); + } + + if(height <= 0 || height > glRefConfig.maxRenderbufferSize) + { + ri.Error(ERR_DROP, "FBO_Create: bad height %i", height); + } + + if(tr.numFBOs == MAX_FBOS) + { + ri.Error(ERR_DROP, "FBO_Create: MAX_FBOS hit"); + } + + fbo = tr.fbos[tr.numFBOs] = (FBO_t*)ri.Hunk_Alloc(sizeof(*fbo), h_low); + Q_strncpyz(fbo->name, name, sizeof(fbo->name)); + fbo->index = tr.numFBOs++; + fbo->width = width; + fbo->height = height; + + qglGenFramebuffers(1, &fbo->frameBuffer); + + return fbo; +} + +/* +================= +FBO_CreateBuffer +================= +*/ +void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample) +{ + uint32_t *pRenderBuffer; + GLenum attachment; + bool absent; + + switch(format) + { + case GL_RGB: + case GL_RGBA: + case GL_RGB8: + case GL_RGBA8: + case GL_RGB16F_ARB: + case GL_RGBA16F_ARB: + case GL_RGB32F_ARB: + case GL_RGBA32F_ARB: + fbo->colorFormat = format; + pRenderBuffer = &fbo->colorBuffers[index]; + attachment = GL_COLOR_ATTACHMENT0 + index; + break; + + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16_ARB: + case GL_DEPTH_COMPONENT24_ARB: + case GL_DEPTH_COMPONENT32_ARB: + fbo->depthFormat = format; + pRenderBuffer = &fbo->depthBuffer; + attachment = GL_DEPTH_ATTACHMENT; + break; + + case GL_STENCIL_INDEX: + case GL_STENCIL_INDEX1: + case GL_STENCIL_INDEX4: + case GL_STENCIL_INDEX8: + case GL_STENCIL_INDEX16: + fbo->stencilFormat = format; + pRenderBuffer = &fbo->stencilBuffer; + attachment = GL_STENCIL_ATTACHMENT; + break; + + case GL_DEPTH_STENCIL: + case GL_DEPTH24_STENCIL8: + fbo->packedDepthStencilFormat = format; + pRenderBuffer = &fbo->packedDepthStencilBuffer; + attachment = 0; // special for stencil and depth + break; + + default: + ri.Printf(PRINT_WARNING, "FBO_CreateBuffer: invalid format %d\n", format); + return; + } + + absent = *pRenderBuffer == 0; + if (absent) + qglGenRenderbuffers(1, pRenderBuffer); + + if (multisample && glRefConfig.framebufferMultisample) + qglNamedRenderbufferStorageMultisampleEXT(*pRenderBuffer, multisample, format, fbo->width, fbo->height); + else + qglNamedRenderbufferStorageEXT(*pRenderBuffer, format, fbo->width, fbo->height); + + if(absent) + { + if (attachment == 0) + { + qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *pRenderBuffer); + qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *pRenderBuffer); + } + else + { + qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, attachment, GL_RENDERBUFFER, *pRenderBuffer); + } + } +} + + +/* +================= +FBO_AttachImage +================= +*/ +void FBO_AttachImage(FBO_t *fbo, image_t *image, GLenum attachment, GLuint cubemapside) +{ + GLenum target = GL_TEXTURE_2D; + int index; + + if (image->flags & IMGFLAG_CUBEMAP) + target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + cubemapside; + + qglNamedFramebufferTexture2DEXT(fbo->frameBuffer, attachment, target, image->texnum, 0); + index = attachment - GL_COLOR_ATTACHMENT0; + if (index >= 0 && index <= 15) + fbo->colorImage[index] = image; +} + + +/* +============ +FBO_Bind +============ +*/ +void FBO_Bind(FBO_t * fbo) +{ + if (!glRefConfig.framebufferObject) + { + ri.Printf(PRINT_WARNING, "FBO_Bind() called without framebuffers enabled!\n"); + return; + } + + if (glState.currentFBO == fbo) + return; + + if (r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment((char*)va("--- FBO_Bind( %s ) ---\n", fbo ? fbo->name : "NULL")); + } + + GL_BindFramebuffer(GL_FRAMEBUFFER, fbo ? fbo->frameBuffer : 0); + glState.currentFBO = fbo; +} + +/* +============ +FBO_Init +============ +*/ +void FBO_Init(void) +{ + int i; + int hdrFormat, multisample = 0; + + ri.Printf(PRINT_ALL, "------- FBO_Init -------\n"); + + if(!glRefConfig.framebufferObject) + return; + + tr.numFBOs = 0; + + GL_CheckErrors(); + + R_IssuePendingRenderCommands(); + + hdrFormat = GL_RGBA8; + if (r_hdr->integer && glRefConfig.textureFloat) + hdrFormat = GL_RGBA16F_ARB; + + if (glRefConfig.framebufferMultisample) + qglGetIntegerv(GL_MAX_SAMPLES, &multisample); + + if (r_ext_framebuffer_multisample->integer < multisample) + multisample = r_ext_framebuffer_multisample->integer; + + if (multisample < 2 || !glRefConfig.framebufferBlit) + multisample = 0; + + if (multisample != r_ext_framebuffer_multisample->integer) + ri.Cvar_SetValue("r_ext_framebuffer_multisample", (float)multisample); + + // only create a render FBO if we need to resolve MSAA or do HDR + // otherwise just render straight to the screen (tr.renderFbo = NULL) + if (multisample && glRefConfig.framebufferMultisample) + { + tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample); + FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24, 0, multisample); + R_CheckFBO(tr.renderFbo); + + tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_AttachImage(tr.msaaResolveFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.msaaResolveFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); + R_CheckFBO(tr.msaaResolveFbo); + } + else if (r_hdr->integer) + { + tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_AttachImage(tr.renderFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.renderFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); + R_CheckFBO(tr.renderFbo); + } + + // clear render buffer + // this fixes the corrupt screen bug with r_hdr 1 on older hardware + if (tr.renderFbo) + { + GL_BindFramebuffer(GL_FRAMEBUFFER, tr.renderFbo->frameBuffer); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + if (tr.screenScratchImage) + { + tr.screenScratchFbo = FBO_Create("screenScratch", tr.screenScratchImage->width, tr.screenScratchImage->height); + FBO_AttachImage(tr.screenScratchFbo, tr.screenScratchImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.screenScratchFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); + R_CheckFBO(tr.screenScratchFbo); + } + + if (tr.sunRaysImage) + { + tr.sunRaysFbo = FBO_Create("_sunRays", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_AttachImage(tr.sunRaysFbo, tr.sunRaysImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.sunRaysFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); + R_CheckFBO(tr.sunRaysFbo); + } + + if (MAX_DRAWN_PSHADOWS && tr.pshadowMaps[0]) + { + for( i = 0; i < MAX_DRAWN_PSHADOWS; i++) + { + tr.pshadowFbos[i] = FBO_Create(va("_shadowmap%d", i), tr.pshadowMaps[i]->width, tr.pshadowMaps[i]->height); + // FIXME: this next line wastes 16mb with 16x512x512 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments + FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0); + FBO_AttachImage(tr.pshadowFbos[i], tr.pshadowMaps[i], GL_DEPTH_ATTACHMENT, 0); + R_CheckFBO(tr.pshadowFbos[i]); + } + } + + if (tr.sunShadowDepthImage[0]) + { + for (i = 0; i < 4; i++) + { + tr.sunShadowFbo[i] = FBO_Create("_sunshadowmap", tr.sunShadowDepthImage[i]->width, tr.sunShadowDepthImage[i]->height); + // FIXME: this next line wastes 16mb with 4x1024x1024 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments + // This at least gets sun shadows working on older GPUs (Intel) + FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0); + FBO_AttachImage(tr.sunShadowFbo[i], tr.sunShadowDepthImage[i], GL_DEPTH_ATTACHMENT, 0); + R_CheckFBO(tr.sunShadowFbo[i]); + } + } + + if (tr.screenShadowImage) + { + tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height); + FBO_AttachImage(tr.screenShadowFbo, tr.screenShadowImage, GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.screenShadowFbo); + } + + if (tr.textureScratchImage[0]) + { + for (i = 0; i < 2; i++) + { + tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height); + FBO_AttachImage(tr.textureScratchFbo[i], tr.textureScratchImage[i], GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.textureScratchFbo[i]); + } + } + + if (tr.calcLevelsImage) + { + tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height); + FBO_AttachImage(tr.calcLevelsFbo, tr.calcLevelsImage, GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.calcLevelsFbo); + } + + if (tr.targetLevelsImage) + { + tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height); + FBO_AttachImage(tr.targetLevelsFbo, tr.targetLevelsImage, GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.targetLevelsFbo); + } + + if (tr.quarterImage[0]) + { + for (i = 0; i < 2; i++) + { + tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height); + FBO_AttachImage(tr.quarterFbo[i], tr.quarterImage[i], GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.quarterFbo[i]); + } + } + + if (tr.hdrDepthImage) + { + tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height); + FBO_AttachImage(tr.hdrDepthFbo, tr.hdrDepthImage, GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.hdrDepthFbo); + } + + if (tr.screenSsaoImage) + { + tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height); + FBO_AttachImage(tr.screenSsaoFbo, tr.screenSsaoImage, GL_COLOR_ATTACHMENT0, 0); + R_CheckFBO(tr.screenSsaoFbo); + } + + if (tr.renderCubeImage) + { + tr.renderCubeFbo = FBO_Create("_renderCubeFbo", tr.renderCubeImage->width, tr.renderCubeImage->height); + FBO_AttachImage(tr.renderCubeFbo, tr.renderCubeImage, GL_COLOR_ATTACHMENT0, 0); + FBO_CreateBuffer(tr.renderCubeFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); + R_CheckFBO(tr.renderCubeFbo); + } + + GL_CheckErrors(); + + GL_BindFramebuffer(GL_FRAMEBUFFER, 0); + glState.currentFBO = NULL; +} + +/* +============ +FBO_Shutdown +============ +*/ +void FBO_Shutdown(void) +{ + int i, j; + FBO_t *fbo; + + ri.Printf(PRINT_ALL, "------- FBO_Shutdown -------\n"); + + if(!glRefConfig.framebufferObject) + return; + + FBO_Bind(NULL); + + for(i = 0; i < tr.numFBOs; i++) + { + fbo = tr.fbos[i]; + + for(j = 0; j < glRefConfig.maxColorAttachments; j++) + { + if(fbo->colorBuffers[j]) + qglDeleteRenderbuffers(1, &fbo->colorBuffers[j]); + } + + if(fbo->depthBuffer) + qglDeleteRenderbuffers(1, &fbo->depthBuffer); + + if(fbo->stencilBuffer) + qglDeleteRenderbuffers(1, &fbo->stencilBuffer); + + if(fbo->frameBuffer) + qglDeleteFramebuffers(1, &fbo->frameBuffer); + } +} + +/* +============ +R_FBOList_f +============ +*/ +void R_FBOList_f(void) +{ + int i; + FBO_t *fbo; + + if(!glRefConfig.framebufferObject) + { + ri.Printf(PRINT_ALL, "GL_EXT_framebuffer_object is not available.\n"); + return; + } + + ri.Printf(PRINT_ALL, " size name\n"); + ri.Printf(PRINT_ALL, "----------------------------------------------------------\n"); + + for(i = 0; i < tr.numFBOs; i++) + { + fbo = tr.fbos[i]; + + ri.Printf(PRINT_ALL, " %4i: %4i %4i %s\n", i, fbo->width, fbo->height, fbo->name); + } + + ri.Printf(PRINT_ALL, " %i FBOs\n", tr.numFBOs); +} + +void FBO_BlitFromTexture(struct image_s *src, vec4_t inSrcTexCorners, vec2_t inSrcTexScale, FBO_t *dst, ivec4_t inDstBox, struct shaderProgram_s *shaderProgram, vec4_t inColor, int blend) +{ + ivec4_t dstBox; + vec4_t color; + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + vec2_t invTexRes; + FBO_t *oldFbo = glState.currentFBO; + mat4_t projection; + int width, height; + + if (!src) + { + ri.Printf(PRINT_WARNING, "Tried to blit from a NULL texture!\n"); + return; + } + + width = dst ? dst->width : glConfig.vidWidth; + height = dst ? dst->height : glConfig.vidHeight; + + if (inSrcTexCorners) + { + VectorSet2(texCoords[0], inSrcTexCorners[0], inSrcTexCorners[1]); + VectorSet2(texCoords[1], inSrcTexCorners[2], inSrcTexCorners[1]); + VectorSet2(texCoords[2], inSrcTexCorners[2], inSrcTexCorners[3]); + VectorSet2(texCoords[3], inSrcTexCorners[0], inSrcTexCorners[3]); + } + else + { + VectorSet2(texCoords[0], 0.0f, 1.0f); + VectorSet2(texCoords[1], 1.0f, 1.0f); + VectorSet2(texCoords[2], 1.0f, 0.0f); + VectorSet2(texCoords[3], 0.0f, 0.0f); + } + + // framebuffers are 0 bottom, Y up. + if (inDstBox) + { + dstBox[0] = inDstBox[0]; + dstBox[1] = height - inDstBox[1] - inDstBox[3]; + dstBox[2] = inDstBox[0] + inDstBox[2]; + dstBox[3] = height - inDstBox[1]; + } + else + { + VectorSet4(dstBox, 0, height, width, 0); + } + + if (inSrcTexScale) + { + VectorCopy2(inSrcTexScale, invTexRes); + } + else + { + VectorSet2(invTexRes, 1.0f, 1.0f); + } + + if (inColor) + { + VectorCopy4(inColor, color); + } + else + { + VectorCopy4(colorWhite, color); + } + + if (!shaderProgram) + { + shaderProgram = &tr.textureColorShader; + } + + FBO_Bind(dst); + + qglViewport( 0, 0, width, height ); + qglScissor( 0, 0, width, height ); + + Mat4Ortho(0, width, height, 0, 0, 1, projection); + + GL_Cull( CT_TWO_SIDED ); + + GL_BindToTMU(src, TB_COLORMAP); + + VectorSet4(quadVerts[0], dstBox[0], dstBox[1], 0.0f, 1.0f); + VectorSet4(quadVerts[1], dstBox[2], dstBox[1], 0.0f, 1.0f); + VectorSet4(quadVerts[2], dstBox[2], dstBox[3], 0.0f, 1.0f); + VectorSet4(quadVerts[3], dstBox[0], dstBox[3], 0.0f, 1.0f); + + invTexRes[0] /= src->width; + invTexRes[1] /= src->height; + + GL_State( blend ); + + GLSL_BindProgram(shaderProgram); + + GLSL_SetUniformMat4(shaderProgram, UNIFORM_MODELVIEWPROJECTIONMATRIX, projection); + GLSL_SetUniformVec4(shaderProgram, UNIFORM_COLOR, color); + GLSL_SetUniformVec2(shaderProgram, UNIFORM_INVTEXRES, invTexRes); + GLSL_SetUniformVec2(shaderProgram, UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax); + GLSL_SetUniformVec3(shaderProgram, UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear); + + RB_InstantQuad2(quadVerts, texCoords); + + FBO_Bind(oldFbo); +} + +void FBO_Blit(FBO_t *src, ivec4_t inSrcBox, vec2_t srcTexScale, FBO_t *dst, ivec4_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend) +{ + vec4_t srcTexCorners; + + if (!src) + { + ri.Printf(PRINT_WARNING, "Tried to blit from a NULL FBO!\n"); + return; + } + + if (inSrcBox) + { + srcTexCorners[0] = inSrcBox[0] / (float)src->width; + srcTexCorners[1] = (inSrcBox[1] + inSrcBox[3]) / (float)src->height; + srcTexCorners[2] = (inSrcBox[0] + inSrcBox[2]) / (float)src->width; + srcTexCorners[3] = inSrcBox[1] / (float)src->height; + } + else + { + VectorSet4(srcTexCorners, 0.0f, 0.0f, 1.0f, 1.0f); + } + + FBO_BlitFromTexture(src->colorImage[0], srcTexCorners, srcTexScale, dst, dstBox, shaderProgram, color, blend | GLS_DEPTHTEST_DISABLE); +} + +void FBO_FastBlit(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, int buffers, int filter) +{ + ivec4_t srcBoxFinal, dstBoxFinal; + GLuint srcFb, dstFb; + + if (!glRefConfig.framebufferBlit) + { + FBO_Blit(src, srcBox, NULL, dst, dstBox, NULL, NULL, 0); + return; + } + + srcFb = src ? src->frameBuffer : 0; + dstFb = dst ? dst->frameBuffer : 0; + + if (!srcBox) + { + int width = src ? src->width : glConfig.vidWidth; + int height = src ? src->height : glConfig.vidHeight; + + VectorSet4(srcBoxFinal, 0, 0, width, height); + } + else + { + VectorSet4(srcBoxFinal, srcBox[0], srcBox[1], srcBox[0] + srcBox[2], srcBox[1] + srcBox[3]); + } + + if (!dstBox) + { + int width = dst ? dst->width : glConfig.vidWidth; + int height = dst ? dst->height : glConfig.vidHeight; + + VectorSet4(dstBoxFinal, 0, 0, width, height); + } + else + { + VectorSet4(dstBoxFinal, dstBox[0], dstBox[1], dstBox[0] + dstBox[2], dstBox[1] + dstBox[3]); + } + + GL_BindFramebuffer(GL_READ_FRAMEBUFFER, srcFb); + GL_BindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFb); + qglBlitFramebuffer(srcBoxFinal[0], srcBoxFinal[1], srcBoxFinal[2], srcBoxFinal[3], + dstBoxFinal[0], dstBoxFinal[1], dstBoxFinal[2], dstBoxFinal[3], + buffers, filter); + + GL_BindFramebuffer(GL_FRAMEBUFFER, 0); + glState.currentFBO = NULL; +} diff --git a/src/renderergl2/tr_fbo.h b/src/renderergl2/tr_fbo.h new file mode 100644 index 0000000..efd317b --- /dev/null +++ b/src/renderergl2/tr_fbo.h @@ -0,0 +1,66 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_fbo.h + +#ifndef __TR_FBO_H__ +#define __TR_FBO_H__ + +struct image_s; +struct shaderProgram_s; + +typedef struct FBO_s +{ + char name[MAX_QPATH]; + + int index; + + uint32_t frameBuffer; + + uint32_t colorBuffers[16]; + int colorFormat; + struct image_s *colorImage[16]; + + uint32_t depthBuffer; + int depthFormat; + + uint32_t stencilBuffer; + int stencilFormat; + + uint32_t packedDepthStencilBuffer; + int packedDepthStencilFormat; + + int width; + int height; +} FBO_t; + +void FBO_AttachImage(FBO_t *fbo, image_t *image, GLenum attachment, GLuint cubemapside); +void FBO_Bind(FBO_t *fbo); +void FBO_Init(void); +void FBO_Shutdown(void); + +void FBO_BlitFromTexture(struct image_s *src, vec4_t inSrcTexCorners, vec2_t inSrcTexScale, FBO_t *dst, ivec4_t inDstBox, struct shaderProgram_s *shaderProgram, vec4_t inColor, int blend); +void FBO_Blit(FBO_t *src, ivec4_t srcBox, vec2_t srcTexScale, FBO_t *dst, ivec4_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend); +void FBO_FastBlit(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, int buffers, int filter); + + +#endif diff --git a/src/renderergl2/tr_flares.cpp b/src/renderergl2/tr_flares.cpp new file mode 100644 index 0000000..e2f38c8 --- /dev/null +++ b/src/renderergl2/tr_flares.cpp @@ -0,0 +1,554 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_flares.c + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare reletive to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that its midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + bool inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + bool visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + + int windowX, windowY; + float eyeZ; + + vec3_t origin; + vec3_t color; +} flare_t; + +#define MAX_FLARES 128 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + +int flareCoeff; + +/* +================== +R_SetFlareCoeff +================== +*/ +static void R_SetFlareCoeff( void ) { + + if(r_flareCoeff->value == 0.0f) + flareCoeff = atof(FLARE_STDCOEFF); + else + flareCoeff = r_flareCoeff->value; +} + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } + + R_SetFlareCoeff(); +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) { + int i; + flare_t *f; + vec3_t local; + float d = 1; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + if(normal && (normal[0] || normal[1] || normal[2])) + { + VectorSubtract( backEnd.viewParms.orientation.origin, point, local ); + VectorNormalizeFast(local); + d = DotProduct(local, normal); + + // If the viewer is behind the flare don't add it. + if(d < 0) + return; + } + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.orientation.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if (!f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + } + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = false; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + + VectorCopy(point, f->origin); + VectorCopy( color, f->color ); + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + VectorScale( f->color, d, f->color ); + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + fog_t *fog = NULL; + + if ( !r_flares->integer ) { + return; + } + + l = backEnd.refdef.dlights; + + if(tr.world) + fog = tr.world->fogs; + + for (i=0 ; inumfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + } + else + j = 0; + + RB_AddFlare( (void *)l, j, l->origin, l->color, NULL ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { + float depth; + bool visible; + float fade; + float screenZ; + FBO_t *oldFbo; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = false; + + // if we're doing multisample rendering, read from the correct FBO + oldFbo = glState.currentFBO; + if (tr.msaaResolveFbo) + { + FBO_Bind(tr.msaaResolveFbo); + } + + // read back the z buffer contents + qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + // if we're doing multisample rendering, switch to the old FBO + if (tr.msaaResolveFbo) + { + FBO_Bind(oldFbo); + } + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -f->eyeZ - -screenZ ) < 24; + + if ( visible ) { + if ( !f->visible ) { + f->visible = true; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = false; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + float distance, intensity, factor; + byte fogFactors[3] = {255, 255, 255}; + + backEnd.pc.c_flareRenders++; + + // We don't want too big values anyways when dividing by distance. + if(f->eyeZ > -1.0f) + distance = 1.0f; + else + distance = -f->eyeZ; + + // calculate the flare size.. + size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance ); + +/* + * This is an alternative to intensity scaling. It changes the size of the flare on screen instead + * with growing distance. See in the description at the top why this is not the way to go. + // size will change ~ 1/r. + size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f)); +*/ + +/* + * As flare sizes stay nearly constant with increasing distance we must decrease the intensity + * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be + * got by considering the ratio of + * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare) + * An important requirement is: + * intensity <= 1 for all distances. + * + * The formula used here to compute the intensity is as follows: + * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2 + * As you can see, the intensity will have a max. of 1 when the distance is 0. + * The coefficient flareCoeff will determine the falloff speed with increasing distance. + */ + + factor = distance + size * sqrt(flareCoeff); + + intensity = flareCoeff * size * size / (factor * factor); + + VectorScale(f->color, f->drawIntensity * intensity, color); + + // Calculations for fogging + if(tr.world && f->fogNum > 0 && f->fogNum < tr.world->numfogs) + { + tess.numVertexes = 1; + VectorCopy(f->origin, tess.xyz[0]); + tess.fogNum = f->fogNum; + + RB_CalcModulateColorsByFog(fogFactors); + + // We don't need to render the flare if colors are 0 anyways. + if(!(fogFactors[0] || fogFactors[1] || fogFactors[2])) + return; + } + + iColor[0] = color[0] * fogFactors[0] * 257; + iColor[1] = color[1] * fogFactors[1] * 257; + iColor[2] = color[2] * fogFactors[2] * 257; + + RB_BeginSurface( tr.flareShader, f->fogNum, 0 ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0] = 0; + tess.texCoords[tess.numVertexes][1] = 0; + tess.color[tess.numVertexes][0] = iColor[0]; + tess.color[tess.numVertexes][1] = iColor[1]; + tess.color[tess.numVertexes][2] = iColor[2]; + tess.color[tess.numVertexes][3] = 65535; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0] = 0; + tess.texCoords[tess.numVertexes][1] = 1; + tess.color[tess.numVertexes][0] = iColor[0]; + tess.color[tess.numVertexes][1] = iColor[1]; + tess.color[tess.numVertexes][2] = iColor[2]; + tess.color[tess.numVertexes][3] = 65535; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0] = 1; + tess.texCoords[tess.numVertexes][1] = 1; + tess.color[tess.numVertexes][0] = iColor[0]; + tess.color[tess.numVertexes][1] = iColor[1]; + tess.color[tess.numVertexes][2] = iColor[2]; + tess.color[tess.numVertexes][3] = 65535; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0] = 1; + tess.texCoords[tess.numVertexes][1] = 0; + tess.color[tess.numVertexes][0] = iColor[0]; + tess.color[tess.numVertexes][1] = iColor[1]; + tess.color[tess.numVertexes][2] = iColor[2]; + tess.color[tess.numVertexes][3] = 65535; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares (void) { + flare_t *f; + flare_t **prev; + bool draw; + mat4_t oldmodelview, oldprojection, matrix; + + if ( !r_flares->integer ) { + return; + } + + if(r_flareCoeff->modified) + { + R_SetFlareCoeff(); + r_flareCoeff->modified = false; + } + + // Reset currentEntity to world so that any previously referenced entities + // don't have influence on the rendering of these flares (i.e. RF_ renderer flags). + backEnd.currentEntity = &tr.worldEntity; + backEnd.orientation = backEnd.viewParms.world; + +// RB_AddDlightFlares(); + + // perform z buffer readback on each flare in this view + draw = false; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = true; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable (GL_CLIP_PLANE0); + } + + Mat4Copy(glState.projection, oldprojection); + Mat4Copy(glState.modelview, oldmodelview); + Mat4Identity(matrix); + GL_SetModelviewMatrix(matrix); + Mat4Ortho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999, matrix ); + GL_SetProjectionMatrix(matrix); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + GL_SetProjectionMatrix(oldprojection); + GL_SetModelviewMatrix(oldmodelview); +} diff --git a/src/renderergl2/tr_glsl.cpp b/src/renderergl2/tr_glsl.cpp new file mode 100644 index 0000000..469cb7c --- /dev/null +++ b/src/renderergl2/tr_glsl.cpp @@ -0,0 +1,1470 @@ +/* +=========================================================================== +Copyright (C) 2006-2009 Robert Beckebans +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_glsl.c +#include "tr_local.h" + +#include "tr_dsa.h" + +extern const char *fallbackShader_bokeh_vp; +extern const char *fallbackShader_bokeh_fp; +extern const char *fallbackShader_calclevels4x_vp; +extern const char *fallbackShader_calclevels4x_fp; +extern const char *fallbackShader_depthblur_vp; +extern const char *fallbackShader_depthblur_fp; +extern const char *fallbackShader_dlight_vp; +extern const char *fallbackShader_dlight_fp; +extern const char *fallbackShader_down4x_vp; +extern const char *fallbackShader_down4x_fp; +extern const char *fallbackShader_fogpass_vp; +extern const char *fallbackShader_fogpass_fp; +extern const char *fallbackShader_generic_vp; +extern const char *fallbackShader_generic_fp; +extern const char *fallbackShader_lightall_vp; +extern const char *fallbackShader_lightall_fp; +extern const char *fallbackShader_pshadow_vp; +extern const char *fallbackShader_pshadow_fp; +extern const char *fallbackShader_shadowfill_vp; +extern const char *fallbackShader_shadowfill_fp; +extern const char *fallbackShader_shadowmask_vp; +extern const char *fallbackShader_shadowmask_fp; +extern const char *fallbackShader_ssao_vp; +extern const char *fallbackShader_ssao_fp; +extern const char *fallbackShader_texturecolor_vp; +extern const char *fallbackShader_texturecolor_fp; +extern const char *fallbackShader_tonemap_vp; +extern const char *fallbackShader_tonemap_fp; + +struct uniformInfo_t +{ + const char *name; + int type; +}; + +// These must be in the same order as in uniform_t in tr_local.h. +static uniformInfo_t uniformsInfo[] = +{ + { "u_DiffuseMap", GLSL_INT }, + { "u_LightMap", GLSL_INT }, + { "u_NormalMap", GLSL_INT }, + { "u_DeluxeMap", GLSL_INT }, + { "u_SpecularMap", GLSL_INT }, + + { "u_TextureMap", GLSL_INT }, + { "u_LevelsMap", GLSL_INT }, + { "u_CubeMap", GLSL_INT }, + + { "u_ScreenImageMap", GLSL_INT }, + { "u_ScreenDepthMap", GLSL_INT }, + + { "u_ShadowMap", GLSL_INT }, + { "u_ShadowMap2", GLSL_INT }, + { "u_ShadowMap3", GLSL_INT }, + { "u_ShadowMap4", GLSL_INT }, + + { "u_ShadowMvp", GLSL_MAT16 }, + { "u_ShadowMvp2", GLSL_MAT16 }, + { "u_ShadowMvp3", GLSL_MAT16 }, + { "u_ShadowMvp4", GLSL_MAT16 }, + + { "u_EnableTextures", GLSL_VEC4 }, + + { "u_DiffuseTexMatrix", GLSL_VEC4 }, + { "u_DiffuseTexOffTurb", GLSL_VEC4 }, + + { "u_TCGen0", GLSL_INT }, + { "u_TCGen0Vector0", GLSL_VEC3 }, + { "u_TCGen0Vector1", GLSL_VEC3 }, + + { "u_DeformGen", GLSL_INT }, + { "u_DeformParams", GLSL_FLOAT5 }, + + { "u_ColorGen", GLSL_INT }, + { "u_AlphaGen", GLSL_INT }, + { "u_Color", GLSL_VEC4 }, + { "u_BaseColor", GLSL_VEC4 }, + { "u_VertColor", GLSL_VEC4 }, + + { "u_DlightInfo", GLSL_VEC4 }, + { "u_LightForward", GLSL_VEC3 }, + { "u_LightUp", GLSL_VEC3 }, + { "u_LightRight", GLSL_VEC3 }, + { "u_LightOrigin", GLSL_VEC4 }, + { "u_ModelLightDir", GLSL_VEC3 }, + { "u_LightRadius", GLSL_FLOAT }, + { "u_AmbientLight", GLSL_VEC3 }, + { "u_DirectedLight", GLSL_VEC3 }, + + { "u_PortalRange", GLSL_FLOAT }, + + { "u_FogDistance", GLSL_VEC4 }, + { "u_FogDepth", GLSL_VEC4 }, + { "u_FogEyeT", GLSL_FLOAT }, + { "u_FogColorMask", GLSL_VEC4 }, + + { "u_ModelMatrix", GLSL_MAT16 }, + { "u_ModelViewProjectionMatrix", GLSL_MAT16 }, + + { "u_Time", GLSL_FLOAT }, + { "u_VertexLerp" , GLSL_FLOAT }, + { "u_NormalScale", GLSL_VEC4 }, + { "u_SpecularScale", GLSL_VEC4 }, + + { "u_ViewInfo", GLSL_VEC4 }, + { "u_ViewOrigin", GLSL_VEC3 }, + { "u_LocalViewOrigin", GLSL_VEC3 }, + { "u_ViewForward", GLSL_VEC3 }, + { "u_ViewLeft", GLSL_VEC3 }, + { "u_ViewUp", GLSL_VEC3 }, + + { "u_InvTexRes", GLSL_VEC2 }, + { "u_AutoExposureMinMax", GLSL_VEC2 }, + { "u_ToneMinAvgMaxLinear", GLSL_VEC3 }, + + { "u_PrimaryLightOrigin", GLSL_VEC4 }, + { "u_PrimaryLightColor", GLSL_VEC3 }, + { "u_PrimaryLightAmbient", GLSL_VEC3 }, + { "u_PrimaryLightRadius", GLSL_FLOAT }, + + { "u_CubeMapInfo", GLSL_VEC4 }, + + { "u_AlphaTest", GLSL_INT }, +}; + +typedef enum +{ + GLSL_PRINTLOG_PROGRAM_INFO, + GLSL_PRINTLOG_SHADER_INFO, + GLSL_PRINTLOG_SHADER_SOURCE +} +glslPrintLog_t; + +static void GLSL_PrintLog(GLuint programOrShader, glslPrintLog_t type, bool developerOnly) +{ + char *msg; + static char msgPart[1024]; + int maxLength = 0; + int i; + int printLevel = developerOnly ? PRINT_DEVELOPER : PRINT_ALL; + + switch (type) + { + case GLSL_PRINTLOG_PROGRAM_INFO: + ri.Printf(printLevel, "Program info log:\n"); + qglGetProgramiv(programOrShader, GL_INFO_LOG_LENGTH, &maxLength); + break; + + case GLSL_PRINTLOG_SHADER_INFO: + ri.Printf(printLevel, "Shader info log:\n"); + qglGetShaderiv(programOrShader, GL_INFO_LOG_LENGTH, &maxLength); + break; + + case GLSL_PRINTLOG_SHADER_SOURCE: + ri.Printf(printLevel, "Shader source:\n"); + qglGetShaderiv(programOrShader, GL_SHADER_SOURCE_LENGTH, &maxLength); + break; + } + + if (maxLength <= 0) + { + ri.Printf(printLevel, "None.\n"); + return; + } + + if (maxLength < 1023) + msg = msgPart; + else + msg = (char*)ri.Malloc(maxLength); + + switch (type) + { + case GLSL_PRINTLOG_PROGRAM_INFO: + qglGetProgramInfoLog(programOrShader, maxLength, &maxLength, msg); + break; + + case GLSL_PRINTLOG_SHADER_INFO: + qglGetShaderInfoLog(programOrShader, maxLength, &maxLength, msg); + break; + + case GLSL_PRINTLOG_SHADER_SOURCE: + qglGetShaderSource(programOrShader, maxLength, &maxLength, msg); + break; + } + + if (maxLength < 1023) + { + msgPart[maxLength + 1] = '\0'; + + ri.Printf(printLevel, "%s\n", msgPart); + } + else + { + for(i = 0; i < maxLength; i += 1023) + { + Q_strncpyz(msgPart, msg + i, sizeof(msgPart)); + + ri.Printf(printLevel, "%s", msgPart); + } + + ri.Printf(printLevel, "\n"); + + ri.Free(msg); + } + +} + +static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *dest, int size ) +{ + float fbufWidthScale, fbufHeightScale; + + dest[0] = '\0'; + + // HACK: abuse the GLSL preprocessor to turn GLSL 1.20 shaders into 1.30 ones + if(glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 30)) + { + Q_strcat(dest, size, "#version 150\n"); + + if(shaderType == GL_VERTEX_SHADER) + { + Q_strcat(dest, size, "#define attribute in\n"); + Q_strcat(dest, size, "#define varying out\n"); + } + else + { + Q_strcat(dest, size, "#define varying in\n"); + + Q_strcat(dest, size, "out vec4 out_Color;\n"); + Q_strcat(dest, size, "#define gl_FragColor out_Color\n"); + Q_strcat(dest, size, "#define texture2D texture\n"); + Q_strcat(dest, size, "#define textureCubeLod textureLod\n"); + Q_strcat(dest, size, "#define shadow2D texture\n"); + } + } + else + { + Q_strcat(dest, size, "#version 120\n"); + Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n"); + } + + // HACK: add some macros to avoid extra uniforms and save speed and code maintenance + //Q_strcat(dest, size, + // va("#ifndef r_SpecularExponent\n#define r_SpecularExponent %f\n#endif\n", r_specularExponent->value)); + //Q_strcat(dest, size, + // va("#ifndef r_SpecularScale\n#define r_SpecularScale %f\n#endif\n", r_specularScale->value)); + //Q_strcat(dest, size, + // va("#ifndef r_NormalScale\n#define r_NormalScale %f\n#endif\n", r_normalScale->value)); + + + Q_strcat(dest, size, "#ifndef M_PI\n#define M_PI 3.14159265358979323846\n#endif\n"); + + //Q_strcat(dest, size, va("#ifndef MAX_SHADOWMAPS\n#define MAX_SHADOWMAPS %i\n#endif\n", MAX_SHADOWMAPS)); + + Q_strcat(dest, size, + va("#ifndef deformGen_t\n" + "#define deformGen_t\n" + "#define DGEN_WAVE_SIN %i\n" + "#define DGEN_WAVE_SQUARE %i\n" + "#define DGEN_WAVE_TRIANGLE %i\n" + "#define DGEN_WAVE_SAWTOOTH %i\n" + "#define DGEN_WAVE_INVERSE_SAWTOOTH %i\n" + "#define DGEN_BULGE %i\n" + "#define DGEN_MOVE %i\n" + "#endif\n", + DGEN_WAVE_SIN, + DGEN_WAVE_SQUARE, + DGEN_WAVE_TRIANGLE, + DGEN_WAVE_SAWTOOTH, + DGEN_WAVE_INVERSE_SAWTOOTH, + DGEN_BULGE, + DGEN_MOVE)); + + Q_strcat(dest, size, + va("#ifndef tcGen_t\n" + "#define tcGen_t\n" + "#define TCGEN_LIGHTMAP %i\n" + "#define TCGEN_TEXTURE %i\n" + "#define TCGEN_ENVIRONMENT_MAPPED %i\n" + "#define TCGEN_FOG %i\n" + "#define TCGEN_VECTOR %i\n" + "#endif\n", + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR)); + + Q_strcat(dest, size, + va("#ifndef colorGen_t\n" + "#define colorGen_t\n" + "#define CGEN_LIGHTING_DIFFUSE %i\n" + "#endif\n", + CGEN_LIGHTING_DIFFUSE)); + + Q_strcat(dest, size, + va("#ifndef alphaGen_t\n" + "#define alphaGen_t\n" + "#define AGEN_LIGHTING_SPECULAR %i\n" + "#define AGEN_PORTAL %i\n" + "#endif\n", + AGEN_LIGHTING_SPECULAR, + AGEN_PORTAL)); + + Q_strcat(dest, size, + va("#ifndef texenv_t\n" + "#define texenv_t\n" + "#define TEXENV_MODULATE %i\n" + "#define TEXENV_ADD %i\n" + "#define TEXENV_REPLACE %i\n" + "#endif\n", + GL_MODULATE, + GL_ADD, + GL_REPLACE)); + + fbufWidthScale = 1.0f / ((float)glConfig.vidWidth); + fbufHeightScale = 1.0f / ((float)glConfig.vidHeight); + Q_strcat(dest, size, + va("#ifndef r_FBufScale\n#define r_FBufScale vec2(%f, %f)\n#endif\n", fbufWidthScale, fbufHeightScale)); + + if (r_pbr->integer) + Q_strcat(dest, size, "#define USE_PBR\n"); + + if (r_cubeMapping->integer) + { + int cubeMipSize = r_cubemapSize->integer; + int numRoughnessMips = 0; + + while (cubeMipSize) + { + cubeMipSize >>= 1; + numRoughnessMips++; + } + numRoughnessMips = MAX(1, numRoughnessMips - 2); + Q_strcat(dest, size, va("#define ROUGHNESS_MIPS float(%d)\n", numRoughnessMips)); + } + + if (extra) + { + Q_strcat(dest, size, extra); + } + + // OK we added a lot of stuff but if we do something bad in the GLSL shaders then we want the proper line + // so we have to reset the line counting + Q_strcat(dest, size, "#line 0\n"); +} + +static int GLSL_CompileGPUShader(GLuint program, GLuint *prevShader, const GLchar *buffer, int size, GLenum shaderType) +{ + GLint compiled; + GLuint shader; + + shader = qglCreateShader(shaderType); + + qglShaderSource(shader, 1, (const GLchar **)&buffer, &size); + + // compile shader + qglCompileShader(shader); + + // check if shader compiled + qglGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if(!compiled) + { + GLSL_PrintLog(shader, GLSL_PRINTLOG_SHADER_SOURCE, false); + GLSL_PrintLog(shader, GLSL_PRINTLOG_SHADER_INFO, false); + ri.Error(ERR_DROP, "Couldn't compile shader"); + return 0; + } + + if (*prevShader) + { + qglDetachShader(program, *prevShader); + qglDeleteShader(*prevShader); + } + + // attach shader to program + qglAttachShader(program, shader); + + *prevShader = shader; + + return 1; +} + +static int GLSL_LoadGPUShaderText(const char *name, const char *fallback, + GLenum shaderType, char *dest, int destSize) +{ + char filename[MAX_QPATH]; + GLchar *buffer = NULL; + const GLchar *shaderText = NULL; + int size; + int result; + + if(shaderType == GL_VERTEX_SHADER) + { + Com_sprintf(filename, sizeof(filename), "glsl/%s_vp.glsl", name); + } + else + { + Com_sprintf(filename, sizeof(filename), "glsl/%s_fp.glsl", name); + } + + if ( r_externalGLSL->integer ) { + size = ri.FS_ReadFile(filename, (void **)&buffer); + } else { + size = 0; + buffer = NULL; + } + + if(!buffer) + { + if (fallback) + { + ri.Printf(PRINT_DEVELOPER, "...loading built-in '%s'\n", filename); + shaderText = fallback; + size = strlen(shaderText); + } + else + { + ri.Printf(PRINT_DEVELOPER, "couldn't load '%s'\n", filename); + return 0; + } + } + else + { + ri.Printf(PRINT_DEVELOPER, "...loading '%s'\n", filename); + shaderText = buffer; + } + + if (size > destSize) + { + result = 0; + } + else + { + Q_strncpyz(dest, shaderText, size + 1); + result = 1; + } + + if (buffer) + { + ri.FS_FreeFile(buffer); + } + + return result; +} + +static void GLSL_LinkProgram(GLuint program) +{ + GLint linked; + + qglLinkProgram(program); + + qglGetProgramiv(program, GL_LINK_STATUS, &linked); + if(!linked) + { + GLSL_PrintLog(program, GLSL_PRINTLOG_PROGRAM_INFO, false); + ri.Error(ERR_DROP, "shaders failed to link"); + } +} + +static void GLSL_ValidateProgram(GLuint program) +{ + GLint validated; + + qglValidateProgram(program); + + qglGetProgramiv(program, GL_VALIDATE_STATUS, &validated); + if(!validated) + { + GLSL_PrintLog(program, GLSL_PRINTLOG_PROGRAM_INFO, false); + ri.Error(ERR_DROP, "shaders failed to validate"); + } +} + +static void GLSL_ShowProgramUniforms(GLuint program) +{ + int i, count, size; + GLenum type; + char uniformName[1000]; + + // query the number of active uniforms + qglGetProgramiv(program, GL_ACTIVE_UNIFORMS, &count); + + // Loop over each of the active uniforms, and set their value + for(i = 0; i < count; i++) + { + qglGetActiveUniform(program, i, sizeof(uniformName), NULL, &size, &type, uniformName); + + ri.Printf(PRINT_DEVELOPER, "active uniform: '%s'\n", uniformName); + } +} + +static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int attribs, const char *vpCode, const char *fpCode) +{ + ri.Printf(PRINT_DEVELOPER, "------- GPU shader -------\n"); + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "GLSL_InitGPUShader2: \"%s\" is too long", name); + } + + Q_strncpyz(program->name, name, sizeof(program->name)); + + program->program = qglCreateProgram(); + program->attribs = attribs; + + if (!(GLSL_CompileGPUShader(program->program, &program->vertexShader, vpCode, strlen(vpCode), GL_VERTEX_SHADER))) + { + ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_VERTEX_SHADER\n", name); + qglDeleteProgram(program->program); + return 0; + } + + if(fpCode) + { + if(!(GLSL_CompileGPUShader(program->program, &program->fragmentShader, fpCode, strlen(fpCode), GL_FRAGMENT_SHADER))) + { + ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_FRAGMENT_SHADER\n", name); + qglDeleteProgram(program->program); + return 0; + } + } + + if(attribs & ATTR_POSITION) + qglBindAttribLocation(program->program, ATTR_INDEX_POSITION, "attr_Position"); + + if(attribs & ATTR_TEXCOORD) + qglBindAttribLocation(program->program, ATTR_INDEX_TEXCOORD, "attr_TexCoord0"); + + if(attribs & ATTR_LIGHTCOORD) + qglBindAttribLocation(program->program, ATTR_INDEX_LIGHTCOORD, "attr_TexCoord1"); + +// if(attribs & ATTR_TEXCOORD2) +// qglBindAttribLocation(program->program, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2"); + +// if(attribs & ATTR_TEXCOORD3) +// qglBindAttribLocation(program->program, ATTR_INDEX_TEXCOORD3, "attr_TexCoord3"); + + if(attribs & ATTR_TANGENT) + qglBindAttribLocation(program->program, ATTR_INDEX_TANGENT, "attr_Tangent"); + + if(attribs & ATTR_NORMAL) + qglBindAttribLocation(program->program, ATTR_INDEX_NORMAL, "attr_Normal"); + + if(attribs & ATTR_COLOR) + qglBindAttribLocation(program->program, ATTR_INDEX_COLOR, "attr_Color"); + + if(attribs & ATTR_PAINTCOLOR) + qglBindAttribLocation(program->program, ATTR_INDEX_PAINTCOLOR, "attr_PaintColor"); + + if(attribs & ATTR_LIGHTDIRECTION) + qglBindAttribLocation(program->program, ATTR_INDEX_LIGHTDIRECTION, "attr_LightDirection"); + + if(attribs & ATTR_POSITION2) + qglBindAttribLocation(program->program, ATTR_INDEX_POSITION2, "attr_Position2"); + + if(attribs & ATTR_NORMAL2) + qglBindAttribLocation(program->program, ATTR_INDEX_NORMAL2, "attr_Normal2"); + + if(attribs & ATTR_TANGENT2) + qglBindAttribLocation(program->program, ATTR_INDEX_TANGENT2, "attr_Tangent2"); + + GLSL_LinkProgram(program->program); + + return 1; +} + +static int GLSL_InitGPUShader(shaderProgram_t * program, const char *name, + int attribs, bool fragmentShader, const GLchar *extra, bool addHeader, + const char *fallback_vp, const char *fallback_fp) +{ + char vpCode[32000]; + char fpCode[32000]; + char *postHeader; + int size; + int result; + + size = sizeof(vpCode); + if (addHeader) + { + GLSL_GetShaderHeader(GL_VERTEX_SHADER, extra, vpCode, size); + postHeader = &vpCode[strlen(vpCode)]; + size -= strlen(vpCode); + } + else + { + postHeader = &vpCode[0]; + } + + if (!GLSL_LoadGPUShaderText(name, fallback_vp, GL_VERTEX_SHADER, postHeader, size)) + { + return 0; + } + + if (fragmentShader) + { + size = sizeof(fpCode); + if (addHeader) + { + GLSL_GetShaderHeader(GL_FRAGMENT_SHADER, extra, fpCode, size); + postHeader = &fpCode[strlen(fpCode)]; + size -= strlen(fpCode); + } + else + { + postHeader = &fpCode[0]; + } + + if (!GLSL_LoadGPUShaderText(name, fallback_fp, GL_FRAGMENT_SHADER, postHeader, size)) + { + return 0; + } + } + + result = GLSL_InitGPUShader2(program, name, attribs, vpCode, fragmentShader ? fpCode : NULL); + + return result; +} + +void GLSL_InitUniforms(shaderProgram_t *program) +{ + int i, size; + + GLint *uniforms = program->uniforms; + + size = 0; + for (i = 0; i < UNIFORM_COUNT; i++) + { + uniforms[i] = qglGetUniformLocation(program->program, uniformsInfo[i].name); + + if (uniforms[i] == -1) + continue; + + program->uniformBufferOffsets[i] = size; + + switch(uniformsInfo[i].type) + { + case GLSL_INT: + size += sizeof(GLint); + break; + case GLSL_FLOAT: + size += sizeof(GLfloat); + break; + case GLSL_FLOAT5: + size += sizeof(vec_t) * 5; + break; + case GLSL_VEC2: + size += sizeof(vec_t) * 2; + break; + case GLSL_VEC3: + size += sizeof(vec_t) * 3; + break; + case GLSL_VEC4: + size += sizeof(vec_t) * 4; + break; + case GLSL_MAT16: + size += sizeof(vec_t) * 16; + break; + default: + break; + } + } + + program->uniformBuffer = (char*)ri.Malloc(size); +} + +void GLSL_FinishGPUShader(shaderProgram_t *program) +{ + //GLSL_ValidateProgram(program->program); + GLSL_ShowProgramUniforms(program->program); + GL_CheckErrors(); +} + +void GLSL_SetUniformInt(shaderProgram_t *program, int uniformNum, GLint value) +{ + GLint *uniforms = program->uniforms; + GLint *compare = (GLint *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_INT) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformInt: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (value == *compare) + { + return; + } + + *compare = value; + + qglProgramUniform1iEXT(program->program, uniforms[uniformNum], value); +} + +void GLSL_SetUniformFloat(shaderProgram_t *program, int uniformNum, GLfloat value) +{ + GLint *uniforms = program->uniforms; + GLfloat *compare = (GLfloat *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_FLOAT) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformFloat: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (value == *compare) + { + return; + } + + *compare = value; + + qglProgramUniform1fEXT(program->program, uniforms[uniformNum], value); +} + +void GLSL_SetUniformVec2(shaderProgram_t *program, int uniformNum, const vec2_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_VEC2) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec2: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (v[0] == compare[0] && v[1] == compare[1]) + { + return; + } + + compare[0] = v[0]; + compare[1] = v[1]; + + qglProgramUniform2fEXT(program->program, uniforms[uniformNum], v[0], v[1]); +} + +void GLSL_SetUniformVec3(shaderProgram_t *program, int uniformNum, const vec3_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_VEC3) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec3: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (VectorCompare(v, compare)) + { + return; + } + + VectorCopy(v, compare); + + qglProgramUniform3fEXT(program->program, uniforms[uniformNum], v[0], v[1], v[2]); +} + +void GLSL_SetUniformVec4(shaderProgram_t *program, int uniformNum, const vec4_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_VEC4) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec4: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (VectorCompare4(v, compare)) + { + return; + } + + VectorCopy4(v, compare); + + qglProgramUniform4fEXT(program->program, uniforms[uniformNum], v[0], v[1], v[2], v[3]); +} + +void GLSL_SetUniformFloat5(shaderProgram_t *program, int uniformNum, const vec5_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_FLOAT5) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformFloat5: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (VectorCompare5(v, compare)) + { + return; + } + + VectorCopy5(v, compare); + + qglProgramUniform1fvEXT(program->program, uniforms[uniformNum], 5, v); +} + +void GLSL_SetUniformMat4(shaderProgram_t *program, int uniformNum, const mat4_t matrix) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (uniformsInfo[uniformNum].type != GLSL_MAT16) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformMat4: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (Mat4Compare(matrix, compare)) + { + return; + } + + Mat4Copy(matrix, compare); + + qglProgramUniformMatrix4fvEXT(program->program, uniforms[uniformNum], 1, GL_FALSE, matrix); +} + +void GLSL_DeleteGPUShader(shaderProgram_t *program) +{ + if(program->program) + { + if (program->vertexShader) + { + qglDetachShader(program->program, program->vertexShader); + qglDeleteShader(program->vertexShader); + } + + if (program->fragmentShader) + { + qglDetachShader(program->program, program->fragmentShader); + qglDeleteShader(program->fragmentShader); + } + + qglDeleteProgram(program->program); + + if (program->uniformBuffer) + { + ri.Free(program->uniformBuffer); + } + + Com_Memset(program, 0, sizeof(*program)); + } +} + +void GLSL_InitGPUShaders(void) +{ + int startTime, endTime; + int i; + char extradefines[1024]; + int attribs; + int numGenShaders = 0, numLightShaders = 0, numEtcShaders = 0; + + ri.Printf(PRINT_ALL, "------- GLSL_InitGPUShaders -------\n"); + + R_IssuePendingRenderCommands(); + + startTime = ri.Milliseconds(); + + for (i = 0; i < GENERICDEF_COUNT; i++) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_NORMAL | ATTR_COLOR; + extradefines[0] = '\0'; + + if (i & GENERICDEF_USE_DEFORM_VERTEXES) + Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); + + if (i & GENERICDEF_USE_TCGEN_AND_TCMOD) + { + Q_strcat(extradefines, 1024, "#define USE_TCGEN\n"); + Q_strcat(extradefines, 1024, "#define USE_TCMOD\n"); + } + + if (i & GENERICDEF_USE_VERTEX_ANIMATION) + { + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); + attribs |= ATTR_POSITION2 | ATTR_NORMAL2; + } + + if (i & GENERICDEF_USE_FOG) + Q_strcat(extradefines, 1024, "#define USE_FOG\n"); + + if (i & GENERICDEF_USE_RGBAGEN) + Q_strcat(extradefines, 1024, "#define USE_RGBAGEN\n"); + + if (!GLSL_InitGPUShader(&tr.genericShader[i], "generic", attribs, true, extradefines, true, fallbackShader_generic_vp, fallbackShader_generic_fp)) + { + ri.Error(ERR_FATAL, "Could not load generic shader!"); + } + + GLSL_InitUniforms(&tr.genericShader[i]); + + GLSL_SetUniformInt(&tr.genericShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); + GLSL_SetUniformInt(&tr.genericShader[i], UNIFORM_LIGHTMAP, TB_LIGHTMAP); + + GLSL_FinishGPUShader(&tr.genericShader[i]); + + numGenShaders++; + } + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + + if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, true, extradefines, true, fallbackShader_texturecolor_vp, fallbackShader_texturecolor_fp)) + { + ri.Error(ERR_FATAL, "Could not load texturecolor shader!"); + } + + GLSL_InitUniforms(&tr.textureColorShader); + + GLSL_SetUniformInt(&tr.textureColorShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + + GLSL_FinishGPUShader(&tr.textureColorShader); + + numEtcShaders++; + + for (i = 0; i < FOGDEF_COUNT; i++) + { + attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (i & FOGDEF_USE_DEFORM_VERTEXES) + Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); + + if (i & FOGDEF_USE_VERTEX_ANIMATION) + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); + + if (!GLSL_InitGPUShader(&tr.fogShader[i], "fogpass", attribs, true, extradefines, true, fallbackShader_fogpass_vp, fallbackShader_fogpass_fp)) + { + ri.Error(ERR_FATAL, "Could not load fogpass shader!"); + } + + GLSL_InitUniforms(&tr.fogShader[i]); + GLSL_FinishGPUShader(&tr.fogShader[i]); + + numEtcShaders++; + } + + + for (i = 0; i < DLIGHTDEF_COUNT; i++) + { + attribs = ATTR_POSITION | ATTR_NORMAL | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (i & DLIGHTDEF_USE_DEFORM_VERTEXES) + { + Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); + } + + if (!GLSL_InitGPUShader(&tr.dlightShader[i], "dlight", attribs, true, extradefines, true, fallbackShader_dlight_vp, fallbackShader_dlight_fp)) + { + ri.Error(ERR_FATAL, "Could not load dlight shader!"); + } + + GLSL_InitUniforms(&tr.dlightShader[i]); + + GLSL_SetUniformInt(&tr.dlightShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); + + GLSL_FinishGPUShader(&tr.dlightShader[i]); + + numEtcShaders++; + } + + + for (i = 0; i < LIGHTDEF_COUNT; i++) + { + int lightType = i & LIGHTDEF_LIGHTTYPE_MASK; + bool fastLight = !(r_normalMapping->integer || r_specularMapping->integer); + + // skip impossible combos + if ((i & LIGHTDEF_USE_PARALLAXMAP) && !r_parallaxMapping->integer) + continue; + + if ((i & LIGHTDEF_USE_SHADOWMAP) && (!lightType || !r_sunlightMode->integer)) + continue; + + attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR | ATTR_NORMAL; + + extradefines[0] = '\0'; + + if (r_dlightMode->integer >= 2) + Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n"); + + if (glRefConfig.swizzleNormalmap) + Q_strcat(extradefines, 1024, "#define SWIZZLE_NORMALMAP\n"); + + if (lightType) + { + Q_strcat(extradefines, 1024, "#define USE_LIGHT\n"); + + if (fastLight) + Q_strcat(extradefines, 1024, "#define USE_FAST_LIGHT\n"); + + switch (lightType) + { + case LIGHTDEF_USE_LIGHTMAP: + Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n"); + if (r_deluxeMapping->integer && !fastLight) + Q_strcat(extradefines, 1024, "#define USE_DELUXEMAP\n"); + attribs |= ATTR_LIGHTCOORD | ATTR_LIGHTDIRECTION; + break; + case LIGHTDEF_USE_LIGHT_VECTOR: + Q_strcat(extradefines, 1024, "#define USE_LIGHT_VECTOR\n"); + break; + case LIGHTDEF_USE_LIGHT_VERTEX: + Q_strcat(extradefines, 1024, "#define USE_LIGHT_VERTEX\n"); + attribs |= ATTR_LIGHTDIRECTION; + break; + default: + break; + } + + if (r_normalMapping->integer) + { + Q_strcat(extradefines, 1024, "#define USE_NORMALMAP\n"); + + attribs |= ATTR_TANGENT; + + if ((i & LIGHTDEF_USE_PARALLAXMAP) && !(i & LIGHTDEF_ENTITY) && r_parallaxMapping->integer) + { + Q_strcat(extradefines, 1024, "#define USE_PARALLAXMAP\n"); + if (r_parallaxMapping->integer > 1) + Q_strcat(extradefines, 1024, "#define USE_RELIEFMAP\n"); + } + } + + if (r_specularMapping->integer) + Q_strcat(extradefines, 1024, "#define USE_SPECULARMAP\n"); + + if (r_cubeMapping->integer) + Q_strcat(extradefines, 1024, "#define USE_CUBEMAP\n"); + + switch (r_glossType->integer) + { + case 0: + default: + Q_strcat(extradefines, 1024, "#define GLOSS_IS_GLOSS\n"); + break; + case 1: + Q_strcat(extradefines, 1024, "#define GLOSS_IS_SMOOTHNESS\n"); + break; + case 2: + Q_strcat(extradefines, 1024, "#define GLOSS_IS_ROUGHNESS\n"); + break; + case 3: + Q_strcat(extradefines, 1024, "#define GLOSS_IS_SHININESS\n"); + break; + } + } + + if (i & LIGHTDEF_USE_SHADOWMAP) + { + Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n"); + + if (r_sunlightMode->integer == 1) + Q_strcat(extradefines, 1024, "#define SHADOWMAP_MODULATE\n"); + else if (r_sunlightMode->integer == 2) + Q_strcat(extradefines, 1024, "#define USE_PRIMARY_LIGHT\n"); + } + + if (i & LIGHTDEF_USE_TCGEN_AND_TCMOD) + { + Q_strcat(extradefines, 1024, "#define USE_TCGEN\n"); + Q_strcat(extradefines, 1024, "#define USE_TCMOD\n"); + } + + if (i & LIGHTDEF_ENTITY) + { + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n"); + attribs |= ATTR_POSITION2 | ATTR_NORMAL2; + + if (r_normalMapping->integer) + { + attribs |= ATTR_TANGENT2; + } + } + + if (!GLSL_InitGPUShader(&tr.lightallShader[i], "lightall", attribs, true, extradefines, true, fallbackShader_lightall_vp, fallbackShader_lightall_fp)) + { + ri.Error(ERR_FATAL, "Could not load lightall shader!"); + } + + GLSL_InitUniforms(&tr.lightallShader[i]); + + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_LIGHTMAP, TB_LIGHTMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_NORMALMAP, TB_NORMALMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_DELUXEMAP, TB_DELUXEMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_SPECULARMAP, TB_SPECULARMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_SHADOWMAP, TB_SHADOWMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_CUBEMAP, TB_CUBEMAP); + + GLSL_FinishGPUShader(&tr.lightallShader[i]); + + numLightShaders++; + } + + attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD; + + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.shadowmapShader, "shadowfill", attribs, true, extradefines, true, fallbackShader_shadowfill_vp, fallbackShader_shadowfill_fp)) + { + ri.Error(ERR_FATAL, "Could not load shadowfill shader!"); + } + + GLSL_InitUniforms(&tr.shadowmapShader); + GLSL_FinishGPUShader(&tr.shadowmapShader); + + numEtcShaders++; + + attribs = ATTR_POSITION | ATTR_NORMAL; + extradefines[0] = '\0'; + + Q_strcat(extradefines, 1024, "#define USE_PCF\n#define USE_DISCARD\n"); + + if (!GLSL_InitGPUShader(&tr.pshadowShader, "pshadow", attribs, true, extradefines, true, fallbackShader_pshadow_vp, fallbackShader_pshadow_fp)) + { + ri.Error(ERR_FATAL, "Could not load pshadow shader!"); + } + + GLSL_InitUniforms(&tr.pshadowShader); + + GLSL_SetUniformInt(&tr.pshadowShader, UNIFORM_SHADOWMAP, TB_DIFFUSEMAP); + + GLSL_FinishGPUShader(&tr.pshadowShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.down4xShader, "down4x", attribs, true, extradefines, true, fallbackShader_down4x_vp, fallbackShader_down4x_fp)) + { + ri.Error(ERR_FATAL, "Could not load down4x shader!"); + } + + GLSL_InitUniforms(&tr.down4xShader); + + GLSL_SetUniformInt(&tr.down4xShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + + GLSL_FinishGPUShader(&tr.down4xShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.bokehShader, "bokeh", attribs, true, extradefines, true, fallbackShader_bokeh_vp, fallbackShader_bokeh_fp)) + { + ri.Error(ERR_FATAL, "Could not load bokeh shader!"); + } + + GLSL_InitUniforms(&tr.bokehShader); + + GLSL_SetUniformInt(&tr.bokehShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + + GLSL_FinishGPUShader(&tr.bokehShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.tonemapShader, "tonemap", attribs, true, extradefines, true, fallbackShader_tonemap_vp, fallbackShader_tonemap_fp)) + { + ri.Error(ERR_FATAL, "Could not load tonemap shader!"); + } + + GLSL_InitUniforms(&tr.tonemapShader); + + GLSL_SetUniformInt(&tr.tonemapShader, UNIFORM_TEXTUREMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.tonemapShader, UNIFORM_LEVELSMAP, TB_LEVELSMAP); + + GLSL_FinishGPUShader(&tr.tonemapShader); + + numEtcShaders++; + + + for (i = 0; i < 2; i++) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!i) + Q_strcat(extradefines, 1024, "#define FIRST_PASS\n"); + + if (!GLSL_InitGPUShader(&tr.calclevels4xShader[i], "calclevels4x", attribs, true, extradefines, true, fallbackShader_calclevels4x_vp, fallbackShader_calclevels4x_fp)) + { + ri.Error(ERR_FATAL, "Could not load calclevels4x shader!"); + } + + GLSL_InitUniforms(&tr.calclevels4xShader[i]); + + GLSL_SetUniformInt(&tr.calclevels4xShader[i], UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + + GLSL_FinishGPUShader(&tr.calclevels4xShader[i]); + + numEtcShaders++; + } + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (r_shadowFilter->integer >= 1) + Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n"); + + if (r_shadowFilter->integer >= 2) + Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n"); + + if (r_shadowCascadeZFar->integer != 0) + Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n"); + + Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value)); + Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value)); + + + if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, true, extradefines, true, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp)) + { + ri.Error(ERR_FATAL, "Could not load shadowmask shader!"); + } + + GLSL_InitUniforms(&tr.shadowmaskShader); + + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4); + + GLSL_FinishGPUShader(&tr.shadowmaskShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, true, extradefines, true, fallbackShader_ssao_vp, fallbackShader_ssao_fp)) + { + ri.Error(ERR_FATAL, "Could not load ssao shader!"); + } + + GLSL_InitUniforms(&tr.ssaoShader); + + GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); + + GLSL_FinishGPUShader(&tr.ssaoShader); + + numEtcShaders++; + + + for (i = 0; i < 4; i++) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (i & 1) + Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n"); + else + Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n"); + + if (!(i & 2)) + Q_strcat(extradefines, 1024, "#define USE_DEPTH\n"); + + + if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, true, extradefines, true, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp)) + { + ri.Error(ERR_FATAL, "Could not load depthBlur shader!"); + } + + GLSL_InitUniforms(&tr.depthBlurShader[i]); + + GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP); + + GLSL_FinishGPUShader(&tr.depthBlurShader[i]); + + numEtcShaders++; + } + +#if 0 + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.testcubeShader, "testcube", attribs, true, extradefines, true, NULL, NULL)) + { + ri.Error(ERR_FATAL, "Could not load testcube shader!"); + } + + GLSL_InitUniforms(&tr.testcubeShader); + + GLSL_SetUniformInt(&tr.testcubeShader, UNIFORM_TEXTUREMAP, TB_COLORMAP); + + GLSL_FinishGPUShader(&tr.testcubeShader); + + numEtcShaders++; +#endif + + + endTime = ri.Milliseconds(); + + ri.Printf(PRINT_ALL, "loaded %i GLSL shaders (%i gen %i light %i etc) in %5.2f seconds\n", + numGenShaders + numLightShaders + numEtcShaders, numGenShaders, numLightShaders, + numEtcShaders, (endTime - startTime) / 1000.0); +} + +void GLSL_ShutdownGPUShaders(void) +{ + int i; + + ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n"); + + for (i = 0; i < ATTR_INDEX_COUNT; i++) + qglDisableVertexAttribArray(i); + + GL_BindNullProgram(); + + for ( i = 0; i < GENERICDEF_COUNT; i++) + GLSL_DeleteGPUShader(&tr.genericShader[i]); + + GLSL_DeleteGPUShader(&tr.textureColorShader); + + for ( i = 0; i < FOGDEF_COUNT; i++) + GLSL_DeleteGPUShader(&tr.fogShader[i]); + + for ( i = 0; i < DLIGHTDEF_COUNT; i++) + GLSL_DeleteGPUShader(&tr.dlightShader[i]); + + for ( i = 0; i < LIGHTDEF_COUNT; i++) + GLSL_DeleteGPUShader(&tr.lightallShader[i]); + + GLSL_DeleteGPUShader(&tr.shadowmapShader); + GLSL_DeleteGPUShader(&tr.pshadowShader); + GLSL_DeleteGPUShader(&tr.down4xShader); + GLSL_DeleteGPUShader(&tr.bokehShader); + GLSL_DeleteGPUShader(&tr.tonemapShader); + + for ( i = 0; i < 2; i++) + GLSL_DeleteGPUShader(&tr.calclevels4xShader[i]); + + GLSL_DeleteGPUShader(&tr.shadowmaskShader); + GLSL_DeleteGPUShader(&tr.ssaoShader); + + for ( i = 0; i < 4; i++) + GLSL_DeleteGPUShader(&tr.depthBlurShader[i]); +} + + +void GLSL_BindProgram(shaderProgram_t * program) +{ + GLuint programObject = program ? program->program : 0; + const char *name = program ? program->name : "NULL"; + + if(r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment((char*)va("--- GLSL_BindProgram( %s ) ---\n", name)); + } + + if (GL_UseProgram(programObject)) + backEnd.pc.c_glslShaderBinds++; +} + + +shaderProgram_t *GLSL_GetGenericShaderProgram(int stage) +{ + shaderStage_t *pStage = tess.xstages[stage]; + int shaderAttribs = 0; + + if (tess.fogNum && pStage->adjustColorsForFog) + { + shaderAttribs |= GENERICDEF_USE_FOG; + } + + switch (pStage->rgbGen) + { + case CGEN_LIGHTING_DIFFUSE: + shaderAttribs |= GENERICDEF_USE_RGBAGEN; + break; + default: + break; + } + + switch (pStage->alphaGen) + { + case AGEN_LIGHTING_SPECULAR: + case AGEN_PORTAL: + shaderAttribs |= GENERICDEF_USE_RGBAGEN; + break; + default: + break; + } + + if (pStage->bundle[0].tcGen != TCGEN_TEXTURE) + { + shaderAttribs |= GENERICDEF_USE_TCGEN_AND_TCMOD; + } + + if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader)) + { + shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES; + } + + if (glState.vertexAnimation) + { + shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION; + } + + if (pStage->bundle[0].numTexMods) + { + shaderAttribs |= GENERICDEF_USE_TCGEN_AND_TCMOD; + } + + return &tr.genericShader[shaderAttribs]; +} diff --git a/src/renderergl2/tr_image.cpp b/src/renderergl2/tr_image.cpp new file mode 100644 index 0000000..5e29ff7 --- /dev/null +++ b/src/renderergl2/tr_image.cpp @@ -0,0 +1,3235 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_image.c +#include "tr_local.h" + +#include "tr_dsa.h" + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 1024 +static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +struct textureMode_t { + const char *name; + int minimize, maximize; +}; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +=============== +GL_TextureMode +=============== +*/ +void GL_TextureMode( const char *string ) { + int i; + image_t *glt; + + for ( i=0 ; i< 6 ; i++ ) { + if ( !Q_stricmp( modes[i].name, string ) ) { + break; + } + } + + // hack to prevent trilinear from being set on voodoo, + // because their driver freaks... + if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) { + ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" ); + i = 3; + } + + + if ( i == 6 ) { + ri.Printf (PRINT_ALL, "bad filter name\n"); + return; + } + + gl_filter_min = modes[i].minimize; + gl_filter_max = modes[i].maximize; + + // change all the existing mipmap texture objects + for ( i = 0 ; i < tr.numImages ; i++ ) { + glt = tr.images[ i ]; + if ( glt->flags & IMGFLAG_MIPMAP && !(glt->flags & IMGFLAG_CUBEMAP)) { + qglTextureParameterfEXT(glt->texnum, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTextureParameterfEXT(glt->texnum, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +int R_SumOfUsedImages( void ) { + int total; + int i; + + total = 0; + for ( i = 0; i < tr.numImages; i++ ) { + if ( tr.images[i]->frameUsed == tr.frameCount ) { + total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight; + } + } + + return total; +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i; + int estTotalSize = 0; + + ri.Printf(PRINT_ALL, "\n -w-- -h-- -type-- -size- --name-------\n"); + + for ( i = 0 ; i < tr.numImages ; i++ ) + { + image_t *image = tr.images[i]; + const char *format = "???? "; + const char *sizeSuffix; + int estSize; + int displaySize; + + estSize = image->uploadHeight * image->uploadWidth; + + switch(image->internalFormat) + { + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + format = "sDXT1 "; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + format = "sDXT5 "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + format = "sBPTC "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RG_RGTC2: + format = "RGTC2 "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + format = "DXT1 "; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + format = "DXT1a "; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + format = "DXT5 "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + format = "BPTC "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_RGB4_S3TC: + format = "S3TC "; + // same as DXT1? + estSize /= 2; + break; + case GL_RGBA16F: + format = "RGBA16F"; + // 8 bytes per pixel + estSize *= 8; + break; + case GL_RGBA16: + format = "RGBA16 "; + // 8 bytes per pixel + estSize *= 8; + break; + case GL_RGBA4: + case GL_RGBA8: + case GL_RGBA: + format = "RGBA "; + // 4 bytes per pixel + estSize *= 4; + break; + case GL_LUMINANCE8: + case GL_LUMINANCE16: + case GL_LUMINANCE: + format = "L "; + // 1 byte per pixel? + break; + case GL_RGB5: + case GL_RGB8: + case GL_RGB: + format = "RGB "; + // 3 bytes per pixel? + estSize *= 3; + break; + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE16_ALPHA16: + case GL_LUMINANCE_ALPHA: + format = "LA "; + // 2 bytes per pixel? + estSize *= 2; + break; + case GL_SRGB_EXT: + case GL_SRGB8_EXT: + format = "sRGB "; + // 3 bytes per pixel? + estSize *= 3; + break; + case GL_SRGB_ALPHA_EXT: + case GL_SRGB8_ALPHA8_EXT: + format = "sRGBA "; + // 4 bytes per pixel? + estSize *= 4; + break; + case GL_SLUMINANCE_EXT: + case GL_SLUMINANCE8_EXT: + format = "sL "; + // 1 byte per pixel? + break; + case GL_SLUMINANCE_ALPHA_EXT: + case GL_SLUMINANCE8_ALPHA8_EXT: + format = "sLA "; + // 2 byte per pixel? + estSize *= 2; + break; + case GL_DEPTH_COMPONENT16: + format = "Depth16"; + // 2 bytes per pixel + estSize *= 2; + break; + case GL_DEPTH_COMPONENT24: + format = "Depth24"; + // 3 bytes per pixel + estSize *= 3; + break; + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT32: + format = "Depth32"; + // 4 bytes per pixel + estSize *= 4; + break; + } + + // mipmap adds about 50% + if (image->flags & IMGFLAG_MIPMAP) + estSize += estSize / 2; + + sizeSuffix = "b "; + displaySize = estSize; + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "kb"; + } + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "Mb"; + } + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "Gb"; + } + + ri.Printf(PRINT_ALL, "%4i: %4ix%4i %s %4i%s %s\n", i, image->uploadWidth, image->uploadHeight, format, displaySize, sizeSuffix, image->imgName); + estTotalSize += estSize; + } + + ri.Printf (PRINT_ALL, " ---------\n"); + ri.Printf (PRINT_ALL, " approx %i bytes\n", estTotalSize); + ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages ); +} + +//======================================================================= + +/* +================ +ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only be filtered properly if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +before or after. +================ +*/ +static void ResampleTexture( byte *in, int inwidth, int inheight, byte *out, + int outwidth, int outheight ) { + int i, j; + byte *inrow, *inrow2; + int frac, fracstep; + int p1[2048], p2[2048]; + byte *pix1, *pix2, *pix3, *pix4; + + if (outwidth>2048) + ri.Error(ERR_DROP, "ResampleTexture: max width"); + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for ( i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for ( i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i>2; + *out++ = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + *out++ = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + *out++ = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } +} + +static void RGBAtoYCoCgA(const byte *in, byte *out, int width, int height) +{ + int x, y; + + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + byte r, g, b, a, rb2; + + r = *inbyte++; + g = *inbyte++; + b = *inbyte++; + a = *inbyte++; + rb2 = (r + b) >> 1; + + *outbyte++ = (g + rb2) >> 1; // Y = R/4 + G/2 + B/4 + *outbyte++ = (r - b + 256) >> 1; // Co = R/2 - B/2 + *outbyte++ = (g - rb2 + 256) >> 1; // Cg = -R/4 + G/2 - B/4 + *outbyte++ = a; + } + } +} + +static void YCoCgAtoRGBA(const byte *in, byte *out, int width, int height) +{ + int x, y; + + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + byte _Y, Co, Cg, a; + + _Y = *inbyte++; + Co = *inbyte++; + Cg = *inbyte++; + a = *inbyte++; + + *outbyte++ = CLAMP(_Y + Co - Cg, 0, 255); // R = Y + Co - Cg + *outbyte++ = CLAMP(_Y + Cg - 128, 0, 255); // G = Y + Cg + *outbyte++ = CLAMP(_Y - Co - Cg + 256, 0, 255); // B = Y - Co - Cg + *outbyte++ = a; + } + } +} + + +// uses a sobel filter to change a texture to a normal map +static void RGBAtoNormal(const byte *in, byte *out, int width, int height, bool clampToEdge) +{ + int x, y, max; + + // convert to heightmap, storing in alpha + // same as converting to Y in YCoCg + max = 1; + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4 + 3; + + for (x = 0; x < width; x++) + { + byte result = (inbyte[0] >> 2) + (inbyte[1] >> 1) + (inbyte[2] >> 2); + result = result * result / 255; // Make linear + *outbyte = result; + max = MAX(max, *outbyte); + outbyte += 4; + inbyte += 4; + } + } + + // level out heights + if (max < 255) + { + for (y = 0; y < height; y++) + { + byte *outbyte = out + y * width * 4 + 3; + + for (x = 0; x < width; x++) + { + *outbyte = *outbyte + (255 - max); + outbyte += 4; + } + } + } + + + // now run sobel filter over height values to generate X and Y + // then normalize + for (y = 0; y < height; y++) + { + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + // 0 1 2 + // 3 4 5 + // 6 7 8 + + byte s[9]; + int x2, y2, i; + vec3_t normal; + + i = 0; + for (y2 = -1; y2 <= 1; y2++) + { + int src_y = y + y2; + + if (clampToEdge) + { + src_y = CLAMP(src_y, 0, height - 1); + } + else + { + src_y = (src_y + height) % height; + } + + + for (x2 = -1; x2 <= 1; x2++) + { + int src_x = x + x2; + + if (clampToEdge) + { + src_x = CLAMP(src_x, 0, width - 1); + } + else + { + src_x = (src_x + width) % width; + } + + s[i++] = *(out + (src_y * width + src_x) * 4 + 3); + } + } + + normal[0] = s[0] - s[2] + + 2 * s[3] - 2 * s[5] + + s[6] - s[8]; + + normal[1] = s[0] + 2 * s[1] + s[2] + + - s[6] - 2 * s[7] - s[8]; + + normal[2] = s[4] * 4; + + if (!VectorNormalize2(normal, normal)) + { + VectorSet(normal, 0, 0, 1); + } + + *outbyte++ = FloatToOffsetByte(normal[0]); + *outbyte++ = FloatToOffsetByte(normal[1]); + *outbyte++ = FloatToOffsetByte(normal[2]); + outbyte++; + } + } +} + +#define COPYSAMPLE(a,b) *(unsigned int *)(a) = *(unsigned int *)(b) + +// based on Fast Curve Based Interpolation +// from Fast Artifacts-Free Image Interpolation (http://www.andreagiachetti.it/icbi/) +// assumes data has a 2 pixel thick border of clamped or wrapped data +// expects data to be a grid with even (0, 0), (2, 0), (0, 2), (2, 2) etc pixels filled +// only performs FCBI on specified component +static void DoFCBI(byte *in, byte *out, int width, int height, int component) +{ + int x, y; + byte *outbyte, *inbyte; + + // copy in to out + for (y = 2; y < height - 2; y += 2) + { + inbyte = in + (y * width + 2) * 4 + component; + outbyte = out + (y * width + 2) * 4 + component; + + for (x = 2; x < width - 2; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 3; y < height - 3; y += 2) + { + // diagonals + // + // NWp - northwest interpolated pixel + // NEp - northeast interpolated pixel + // NWd - northwest first derivative + // NEd - northeast first derivative + // NWdd - northwest second derivative + // NEdd - northeast second derivative + // + // Uses these samples: + // + // 0 + // - - a - b - - + // - - - - - - - + // c - d - e - f + // 0 - - - - - - - + // g - h - i - j + // - - - - - - - + // - - k - l - - + // + // x+2 uses these samples: + // + // 0 + // - - - - a - b - - + // - - - - - - - - - + // - - c - d - e - f + // 0 - - - - - - - - - + // - - g - h - i - j + // - - - - - - - - - + // - - - - k - l - - + // + // so we can reuse 8 of them on next iteration + // + // a=b, c=d, d=e, e=f, g=h, h=i, i=j, k=l + // + // only b, f, j, and l need to be sampled on next iteration + + byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl; + byte *line1, *line2, *line3, *line4; + + x = 3; + + // optimization one + // SAMPLE2(sa, x-1, y-3); + //SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); + //SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); + // SAMPLE2(sk, x-1, y+3); + + // optimization two + line1 = in + ((y - 3) * width + (x - 1)) * 4 + component; + line2 = in + ((y - 1) * width + (x - 3)) * 4 + component; + line3 = in + ((y + 1) * width + (x - 3)) * 4 + component; + line4 = in + ((y + 3) * width + (x - 1)) * 4 + component; + + // COPYSAMPLE(sa, line1); line1 += 8; + //COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; COPYSAMPLE(se, line2); line2 += 8; + //COPYSAMPLE(sg, line3); line3 += 8; COPYSAMPLE(sh, line3); line3 += 8; COPYSAMPLE(si, line3); line3 += 8; + // COPYSAMPLE(sk, line4); line4 += 8; + + sa = *line1; line1 += 8; + sc = *line2; line2 += 8; sd = *line2; line2 += 8; se = *line2; line2 += 8; + sg = *line3; line3 += 8; sh = *line3; line3 += 8; si = *line3; line3 += 8; + sk = *line4; line4 += 8; + + outbyte = out + (y * width + x) * 4 + component; + + for ( ; x < width - 3; x += 2) + { + int NWd, NEd, NWp, NEp; + + // original + // SAMPLE2(sa, x-1, y-3); SAMPLE2(sb, x+1, y-3); + //SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); SAMPLE2(sf, x+3, y-1); + //SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); SAMPLE2(sj, x+3, y+1); + // SAMPLE2(sk, x-1, y+3); SAMPLE2(sl, x+1, y+3); + + // optimization one + //SAMPLE2(sb, x+1, y-3); + //SAMPLE2(sf, x+3, y-1); + //SAMPLE2(sj, x+3, y+1); + //SAMPLE2(sl, x+1, y+3); + + // optimization two + //COPYSAMPLE(sb, line1); line1 += 8; + //COPYSAMPLE(sf, line2); line2 += 8; + //COPYSAMPLE(sj, line3); line3 += 8; + //COPYSAMPLE(sl, line4); line4 += 8; + + sb = *line1; line1 += 8; + sf = *line2; line2 += 8; + sj = *line3; line3 += 8; + sl = *line4; line4 += 8; + + NWp = sd + si; + NEp = se + sh; + NWd = abs(sd - si); + NEd = abs(se - sh); + + if (NWd > 100 || NEd > 100 || abs(NWp-NEp) > 200) + { + if (NWd < NEd) + *outbyte = NWp >> 1; + else + *outbyte = NEp >> 1; + } + else + { + int NWdd, NEdd; + + //NEdd = abs(sg + sd + sb - 3 * (se + sh) + sk + si + sf); + //NWdd = abs(sa + se + sj - 3 * (sd + si) + sc + sh + sl); + NEdd = abs(sg + sb - 3 * NEp + sk + sf + NWp); + NWdd = abs(sa + sj - 3 * NWp + sc + sl + NEp); + + if (NWdd > NEdd) + *outbyte = NWp >> 1; + else + *outbyte = NEp >> 1; + } + + outbyte += 8; + + // COPYSAMPLE(sa, sb); + //COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); COPYSAMPLE(se, sf); + //COPYSAMPLE(sg, sh); COPYSAMPLE(sh, si); COPYSAMPLE(si, sj); + // COPYSAMPLE(sk, sl); + + sa = sb; + sc = sd; sd = se; se = sf; + sg = sh; sh = si; si = sj; + sk = sl; + } + } + + // hack: copy out to in again + for (y = 3; y < height - 3; y += 2) + { + inbyte = out + (y * width + 3) * 4 + component; + outbyte = in + (y * width + 3) * 4 + component; + + for (x = 3; x < width - 3; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 2; y < height - 3; y++) + { + // horizontal & vertical + // + // hp - horizontally interpolated pixel + // vp - vertically interpolated pixel + // hd - horizontal first derivative + // vd - vertical first derivative + // hdd - horizontal second derivative + // vdd - vertical second derivative + // Uses these samples: + // + // 0 + // - a - b - + // c - d - e + // 0 - f - g - + // h - i - j + // - k - l - + // + // x+2 uses these samples: + // + // 0 + // - - - a - b - + // - - c - d - e + // 0 - - - f - g - + // - - h - i - j + // - - - k - l - + // + // so we can reuse 7 of them on next iteration + // + // a=b, c=d, d=e, f=g, h=i, i=j, k=l + // + // only b, e, g, j, and l need to be sampled on next iteration + + byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl; + byte *line1, *line2, *line3, *line4, *line5; + + //x = (y + 1) % 2; + x = (y + 1) % 2 + 2; + + // optimization one + // SAMPLE2(sa, x-1, y-2); + //SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x, y-1); + // SAMPLE2(sf, x-1, y ); + //SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x, y+1); + // SAMPLE2(sk, x-1, y+2); + + line1 = in + ((y - 2) * width + (x - 1)) * 4 + component; + line2 = in + ((y - 1) * width + (x - 2)) * 4 + component; + line3 = in + ((y ) * width + (x - 1)) * 4 + component; + line4 = in + ((y + 1) * width + (x - 2)) * 4 + component; + line5 = in + ((y + 2) * width + (x - 1)) * 4 + component; + + // COPYSAMPLE(sa, line1); line1 += 8; + //COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; + // COPYSAMPLE(sf, line3); line3 += 8; + //COPYSAMPLE(sh, line4); line4 += 8; COPYSAMPLE(si, line4); line4 += 8; + // COPYSAMPLE(sk, line5); line5 += 8; + + sa = *line1; line1 += 8; + sc = *line2; line2 += 8; sd = *line2; line2 += 8; + sf = *line3; line3 += 8; + sh = *line4; line4 += 8; si = *line4; line4 += 8; + sk = *line5; line5 += 8; + + outbyte = out + (y * width + x) * 4 + component; + + for ( ; x < width - 3; x+=2) + { + int hd, vd, hp, vp; + + // SAMPLE2(sa, x-1, y-2); SAMPLE2(sb, x+1, y-2); + //SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x, y-1); SAMPLE2(se, x+2, y-1); + // SAMPLE2(sf, x-1, y ); SAMPLE2(sg, x+1, y ); + //SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x, y+1); SAMPLE2(sj, x+2, y+1); + // SAMPLE2(sk, x-1, y+2); SAMPLE2(sl, x+1, y+2); + + // optimization one + //SAMPLE2(sb, x+1, y-2); + //SAMPLE2(se, x+2, y-1); + //SAMPLE2(sg, x+1, y ); + //SAMPLE2(sj, x+2, y+1); + //SAMPLE2(sl, x+1, y+2); + + //COPYSAMPLE(sb, line1); line1 += 8; + //COPYSAMPLE(se, line2); line2 += 8; + //COPYSAMPLE(sg, line3); line3 += 8; + //COPYSAMPLE(sj, line4); line4 += 8; + //COPYSAMPLE(sl, line5); line5 += 8; + + sb = *line1; line1 += 8; + se = *line2; line2 += 8; + sg = *line3; line3 += 8; + sj = *line4; line4 += 8; + sl = *line5; line5 += 8; + + hp = sf + sg; + vp = sd + si; + hd = abs(sf - sg); + vd = abs(sd - si); + + if (hd > 100 || vd > 100 || abs(hp-vp) > 200) + { + if (hd < vd) + *outbyte = hp >> 1; + else + *outbyte = vp >> 1; + } + else + { + int hdd, vdd; + + //hdd = abs(sc[i] + sd[i] + se[i] - 3 * (sf[i] + sg[i]) + sh[i] + si[i] + sj[i]); + //vdd = abs(sa[i] + sf[i] + sk[i] - 3 * (sd[i] + si[i]) + sb[i] + sg[i] + sl[i]); + + hdd = abs(sc + se - 3 * hp + sh + sj + vp); + vdd = abs(sa + sk - 3 * vp + sb + sl + hp); + + if (hdd > vdd) + *outbyte = hp >> 1; + else + *outbyte = vp >> 1; + } + + outbyte += 8; + + // COPYSAMPLE(sa, sb); + //COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); + // COPYSAMPLE(sf, sg); + //COPYSAMPLE(sh, si); COPYSAMPLE(si, sj); + // COPYSAMPLE(sk, sl); + sa = sb; + sc = sd; sd = se; + sf = sg; + sh = si; si = sj; + sk = sl; + } + } +} + +// Similar to FCBI, but throws out the second order derivatives for speed +static void DoFCBIQuick(byte *in, byte *out, int width, int height, int component) +{ + int x, y; + byte *outbyte, *inbyte; + + // copy in to out + for (y = 2; y < height - 2; y += 2) + { + inbyte = in + (y * width + 2) * 4 + component; + outbyte = out + (y * width + 2) * 4 + component; + + for (x = 2; x < width - 2; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 3; y < height - 4; y += 2) + { + byte sd, se, sh, si; + byte *line2, *line3; + + x = 3; + + line2 = in + ((y - 1) * width + (x - 1)) * 4 + component; + line3 = in + ((y + 1) * width + (x - 1)) * 4 + component; + + sd = *line2; line2 += 8; + sh = *line3; line3 += 8; + + outbyte = out + (y * width + x) * 4 + component; + + for ( ; x < width - 4; x += 2) + { + int NWd, NEd, NWp, NEp; + + se = *line2; line2 += 8; + si = *line3; line3 += 8; + + NWp = sd + si; + NEp = se + sh; + NWd = abs(sd - si); + NEd = abs(se - sh); + + if (NWd < NEd) + *outbyte = NWp >> 1; + else + *outbyte = NEp >> 1; + + outbyte += 8; + + sd = se; + sh = si; + } + } + + // hack: copy out to in again + for (y = 3; y < height - 3; y += 2) + { + inbyte = out + (y * width + 3) * 4 + component; + outbyte = in + (y * width + 3) * 4 + component; + + for (x = 3; x < width - 3; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 2; y < height - 3; y++) + { + byte sd, sf, sg, si; + byte *line2, *line3, *line4; + + x = (y + 1) % 2 + 2; + + line2 = in + ((y - 1) * width + (x )) * 4 + component; + line3 = in + ((y ) * width + (x - 1)) * 4 + component; + line4 = in + ((y + 1) * width + (x )) * 4 + component; + + outbyte = out + (y * width + x) * 4 + component; + + sf = *line3; line3 += 8; + + for ( ; x < width - 3; x+=2) + { + int hd, vd, hp, vp; + + sd = *line2; line2 += 8; + sg = *line3; line3 += 8; + si = *line4; line4 += 8; + + hp = sf + sg; + vp = sd + si; + hd = abs(sf - sg); + vd = abs(sd - si); + + if (hd < vd) + *outbyte = hp >> 1; + else + *outbyte = vp >> 1; + + outbyte += 8; + + sf = sg; + } + } +} + +// Similar to DoFCBIQuick, but just takes the average instead of checking derivatives +// as well, this operates on all four components +static void DoLinear(byte *in, byte *out, int width, int height) +{ + int x, y, i; + byte *outbyte, *inbyte; + + // copy in to out + for (y = 2; y < height - 2; y += 2) + { + x = 2; + + inbyte = in + (y * width + x) * 4; + outbyte = out + (y * width + x) * 4; + + for ( ; x < width - 2; x += 2) + { + COPYSAMPLE(outbyte, inbyte); + outbyte += 8; + inbyte += 8; + } + } + + for (y = 1; y < height - 1; y += 2) + { + byte sd[4] = {0}, se[4] = {0}, sh[4] = {0}, si[4] = {0}; + byte *line2, *line3; + + x = 1; + + line2 = in + ((y - 1) * width + (x - 1)) * 4; + line3 = in + ((y + 1) * width + (x - 1)) * 4; + + COPYSAMPLE(sd, line2); line2 += 8; + COPYSAMPLE(sh, line3); line3 += 8; + + outbyte = out + (y * width + x) * 4; + + for ( ; x < width - 1; x += 2) + { + COPYSAMPLE(se, line2); line2 += 8; + COPYSAMPLE(si, line3); line3 += 8; + + for (i = 0; i < 4; i++) + { + *outbyte++ = (sd[i] + si[i] + se[i] + sh[i]) >> 2; + } + + outbyte += 4; + + COPYSAMPLE(sd, se); + COPYSAMPLE(sh, si); + } + } + + // hack: copy out to in again + for (y = 1; y < height - 1; y += 2) + { + x = 1; + + inbyte = out + (y * width + x) * 4; + outbyte = in + (y * width + x) * 4; + + for ( ; x < width - 1; x += 2) + { + COPYSAMPLE(outbyte, inbyte); + outbyte += 8; + inbyte += 8; + } + } + + for (y = 1; y < height - 1; y++) + { + byte sd[4], sf[4], sg[4], si[4]; + byte *line2, *line3, *line4; + + x = y % 2 + 1; + + line2 = in + ((y - 1) * width + (x )) * 4; + line3 = in + ((y ) * width + (x - 1)) * 4; + line4 = in + ((y + 1) * width + (x )) * 4; + + COPYSAMPLE(sf, line3); line3 += 8; + + outbyte = out + (y * width + x) * 4; + + for ( ; x < width - 1; x += 2) + { + COPYSAMPLE(sd, line2); line2 += 8; + COPYSAMPLE(sg, line3); line3 += 8; + COPYSAMPLE(si, line4); line4 += 8; + + for (i = 0; i < 4; i++) + { + *outbyte++ = (sf[i] + sg[i] + sd[i] + si[i]) >> 2; + } + + outbyte += 4; + + COPYSAMPLE(sf, sg); + } + } +} + + +static void ExpandHalfTextureToGrid( byte *data, int width, int height) +{ + int x, y; + + for (y = height / 2; y > 0; y--) + { + byte *outbyte = data + ((y * 2 - 1) * (width) - 2) * 4; + byte *inbyte = data + (y * (width / 2) - 1) * 4; + + for (x = width / 2; x > 0; x--) + { + COPYSAMPLE(outbyte, inbyte); + + outbyte -= 8; + inbyte -= 4; + } + } +} + +static void FillInNormalizedZ(const byte *in, byte *out, int width, int height) +{ + int x, y; + + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + byte nx, ny, nz, h; + float fnx, fny, fll, fnz; + + nx = *inbyte++; + ny = *inbyte++; + inbyte++; + h = *inbyte++; + + fnx = OffsetByteToFloat(nx); + fny = OffsetByteToFloat(ny); + fll = 1.0f - fnx * fnx - fny * fny; + if (fll >= 0.0f) + fnz = (float)sqrt(fll); + else + fnz = 0.0f; + + nz = FloatToOffsetByte(fnz); + + *outbyte++ = nx; + *outbyte++ = ny; + *outbyte++ = nz; + *outbyte++ = h; + } + } +} + + +// size must be even +#define WORKBLOCK_SIZE 128 +#define WORKBLOCK_BORDER 4 +#define WORKBLOCK_REALSIZE (WORKBLOCK_SIZE + WORKBLOCK_BORDER * 2) + +// assumes that data has already been expanded into a 2x2 grid +static void FCBIByBlock(byte *data, int width, int height, bool clampToEdge, bool normalized) +{ + byte workdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4]; + byte outdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4]; + byte *inbyte, *outbyte; + int x, y; + int srcx, srcy; + + ExpandHalfTextureToGrid(data, width, height); + + for (y = 0; y < height; y += WORKBLOCK_SIZE) + { + for (x = 0; x < width; x += WORKBLOCK_SIZE) + { + int x2, y2; + int workwidth, workheight, fullworkwidth, fullworkheight; + + workwidth = MIN(WORKBLOCK_SIZE, width - x); + workheight = MIN(WORKBLOCK_SIZE, height - y); + + fullworkwidth = workwidth + WORKBLOCK_BORDER * 2; + fullworkheight = workheight + WORKBLOCK_BORDER * 2; + + //memset(workdata, 0, WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4); + + // fill in work block + for (y2 = 0; y2 < fullworkheight; y2 += 2) + { + srcy = y + y2 - WORKBLOCK_BORDER; + + if (clampToEdge) + { + srcy = CLAMP(srcy, 0, height - 2); + } + else + { + srcy = (srcy + height) % height; + } + + outbyte = workdata + y2 * fullworkwidth * 4; + inbyte = data + srcy * width * 4; + + for (x2 = 0; x2 < fullworkwidth; x2 += 2) + { + srcx = x + x2 - WORKBLOCK_BORDER; + + if (clampToEdge) + { + srcx = CLAMP(srcx, 0, width - 2); + } + else + { + srcx = (srcx + width) % width; + } + + COPYSAMPLE(outbyte, inbyte + srcx * 4); + outbyte += 8; + } + } + + // submit work block + DoLinear(workdata, outdata, fullworkwidth, fullworkheight); + + if (!normalized) + { + switch (r_imageUpsampleType->integer) + { + case 0: + break; + case 1: + DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0); + break; + case 2: + default: + DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0); + break; + } + } + else + { + switch (r_imageUpsampleType->integer) + { + case 0: + break; + case 1: + DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0); + DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 1); + break; + case 2: + default: + DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0); + DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 1); + break; + } + } + + // copy back work block + for (y2 = 0; y2 < workheight; y2++) + { + inbyte = outdata + ((y2 + WORKBLOCK_BORDER) * fullworkwidth + WORKBLOCK_BORDER) * 4; + outbyte = data + ((y + y2) * width + x) * 4; + for (x2 = 0; x2 < workwidth; x2++) + { + COPYSAMPLE(outbyte, inbyte); + outbyte += 4; + inbyte += 4; + } + } + } + } +} +#undef COPYSAMPLE + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +void R_LightScaleTexture (byte *in, int inwidth, int inheight, bool only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = in; + + c = inwidth*inheight; + for (i=0 ; i> 1; x; x--) { + for (c = 3; c; c--, in++) { + total = (downmipSrgbLookup[*(in)] + downmipSrgbLookup[*(in + 4)]) * 2.0f; + + *out++ = (byte)(powf(total, 1.0f / 2.2f) * 255.0f); + } + *out++ = (*(in) + *(in + 4)) >> 1; in += 5; + } + + return; + } + + stride = inWidth * 4; + inWidth >>= 1; inHeight >>= 1; + + in2 = in + stride; + for (y = inHeight; y; y--, in += stride, in2 += stride) { + for (x = inWidth; x; x--) { + for (c = 3; c; c--, in++, in2++) { + total = downmipSrgbLookup[*(in)] + downmipSrgbLookup[*(in + 4)] + + downmipSrgbLookup[*(in2)] + downmipSrgbLookup[*(in2 + 4)]; + + *out++ = (byte)(powf(total, 1.0f / 2.2f) * 255.0f); + } + + *out++ = (*(in) + *(in + 4) + *(in2) + *(in2 + 4)) >> 2; in += 5, in2 += 5; + } + } +} + + +static void R_MipMapNormalHeight (const byte *in, byte *out, int width, int height, bool swizzle) +{ + int i, j; + int row; + int sx = swizzle ? 3 : 0; + int sa = swizzle ? 0 : 3; + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + width >>= 1; + height >>= 1; + + for (i=0 ; i> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + +static void RawImage_SwizzleRA( byte *data, int width, int height ) +{ + int i; + byte *ptr = data, swap; + + for (i=0; iinteger && scaled_width > width ) + scaled_width >>= 1; + if ( r_roundImagesDown->integer && scaled_height > height ) + scaled_height >>= 1; + + if ( picmip && data && resampledBuffer && r_imageUpsample->integer && + scaled_width < r_imageUpsampleMaxSize->integer && scaled_height < r_imageUpsampleMaxSize->integer) + { + int finalwidth, finalheight; + //int startTime, endTime; + + //startTime = ri.Milliseconds(); + + finalwidth = scaled_width << r_imageUpsample->integer; + finalheight = scaled_height << r_imageUpsample->integer; + + while ( finalwidth > r_imageUpsampleMaxSize->integer + || finalheight > r_imageUpsampleMaxSize->integer ) { + finalwidth >>= 1; + finalheight >>= 1; + } + + while ( finalwidth > glConfig.maxTextureSize + || finalheight > glConfig.maxTextureSize ) { + finalwidth >>= 1; + finalheight >>= 1; + } + + *resampledBuffer = (byte*)ri.Hunk_AllocateTempMemory( finalwidth * finalheight * 4 ); + + if (scaled_width != width || scaled_height != height) + ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height); + else + Com_Memcpy(*resampledBuffer, *data, width * height * 4); + + if (type == IMGTYPE_COLORALPHA) + RGBAtoYCoCgA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); + + while (scaled_width < finalwidth || scaled_height < finalheight) + { + scaled_width <<= 1; + scaled_height <<= 1; + + FCBIByBlock(*resampledBuffer, scaled_width, scaled_height, clampToEdge, (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)); + } + + if (type == IMGTYPE_COLORALPHA) + YCoCgAtoRGBA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); + else if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) + FillInNormalizedZ(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); + + //endTime = ri.Milliseconds(); + + //ri.Printf(PRINT_ALL, "upsampled %dx%d to %dx%d in %dms\n", width, height, scaled_width, scaled_height, endTime - startTime); + + *data = *resampledBuffer; + } + else if ( scaled_width != width || scaled_height != height ) + { + if (data && resampledBuffer) + { + *resampledBuffer = (byte*)ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); + ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height); + *data = *resampledBuffer; + } + } + + width = scaled_width; + height = scaled_height; + + // + // perform optional picmip operation + // + if ( picmip ) { + scaled_width >>= r_picmip->integer; + scaled_height >>= r_picmip->integer; + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( scaled_width > glConfig.maxTextureSize + || scaled_height > glConfig.maxTextureSize ) { + scaled_width >>= 1; + scaled_height >>= 1; + } + + // + // clamp to minimum size + // + scaled_width = MAX(1, scaled_width); + scaled_height = MAX(1, scaled_height); + + scaled = (width != scaled_width) || (height != scaled_height); + + // + // rescale texture to new size using existing mipmap functions + // + if (data) + { + while (width > scaled_width || height > scaled_height) + { + if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) + R_MipMapNormalHeight(*data, *data, width, height, false); + else + R_MipMapsRGB(*data, width, height); + + width = MAX(1, width >> 1); + height = MAX(1, height >> 1); + } + } + + *inout_width = width; + *inout_height = height; + + return scaled; +} + + +static bool RawImage_HasAlpha(const byte *scan, int numPixels) +{ + int i; + + if (!scan) + return true; + + for ( i = 0; i < numPixels; i++ ) + { + if ( scan[i*4 + 3] != 255 ) + { + return true; + } + } + + return false; +} + +static GLenum RawImage_GetFormat(const byte *data, int numPixels, GLenum picFormat, bool lightMap, imgType_t type, int/*imgFlags_t*/ flags) +{ + int samples = 3; + GLenum internalFormat = GL_RGB; + bool forceNoCompression = (flags & IMGFLAG_NO_COMPRESSION); + bool normalmap = (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT); + + if (picFormat != GL_RGBA8) + return picFormat; + + if(normalmap) + { + if ((type == IMGTYPE_NORMALHEIGHT) && RawImage_HasAlpha(data, numPixels) && r_parallaxMapping->integer) + { + if (!forceNoCompression && glRefConfig.textureCompression & TCR_BPTC) + { + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + } + else if (!forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGBA8; + } + else + { + internalFormat = GL_RGBA; + } + } + else + { + if (!forceNoCompression && glRefConfig.textureCompression & TCR_RGTC) + { + internalFormat = GL_COMPRESSED_RG_RGTC2; + } + else if (!forceNoCompression && glRefConfig.textureCompression & TCR_BPTC) + { + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + } + else if (!forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if (r_texturebits->integer == 16) + { + internalFormat = GL_RGB5; + } + else if (r_texturebits->integer == 32) + { + internalFormat = GL_RGB8; + } + else + { + internalFormat = GL_RGB; + } + } + } + else if(lightMap) + { + if(r_greyscale->integer) + internalFormat = GL_LUMINANCE; + else + internalFormat = GL_RGBA; + } + else + { + if (RawImage_HasAlpha(data, numPixels)) + { + samples = 4; + } + + // select proper internal format + if ( samples == 3 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16; + else + internalFormat = GL_LUMINANCE; + } + else + { + if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) ) + { + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + } + else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } + else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC ) + { + internalFormat = GL_RGB4_S3TC; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGB8; + } + else + { + internalFormat = GL_RGB; + } + } + } + else if ( samples == 4 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8_ALPHA8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16_ALPHA16; + else + internalFormat = GL_LUMINANCE_ALPHA; + } + else + { + if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) ) + { + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + } + else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGBA8; + } + else + { + internalFormat = GL_RGBA; + } + } + } + } + + return internalFormat; +} + +static void CompressMonoBlock(byte outdata[8], const byte indata[16]) +{ + int hi, lo, diff, bias, outbyte, shift, i; + byte *p = outdata; + + hi = lo = indata[0]; + for (i = 1; i < 16; i++) + { + hi = MAX(indata[i], hi); + lo = MIN(indata[i], lo); + } + + *p++ = hi; + *p++ = lo; + + diff = hi - lo; + + if (diff == 0) + { + outbyte = (hi == 255) ? 255 : 0; + + for (i = 0; i < 6; i++) + *p++ = outbyte; + + return; + } + + bias = diff / 2 - lo * 7; + outbyte = shift = 0; + for (i = 0; i < 16; i++) + { + const byte fixIndex[8] = { 1, 7, 6, 5, 4, 3, 2, 0 }; + byte index = fixIndex[(indata[i] * 7 + bias) / diff]; + + outbyte |= index << shift; + shift += 3; + if (shift >= 8) + { + *p++ = outbyte & 0xff; + shift -= 8; + outbyte >>= 8; + } + } +} + +static void RawImage_UploadToRgtc2Texture(GLuint texture, int miplevel, int x, int y, int width, int height, byte *data) +{ + int wBlocks, hBlocks, iy, ix, size; + byte *compressedData, *p; + + wBlocks = (width + 3) / 4; + hBlocks = (height + 3) / 4; + size = wBlocks * hBlocks * 16; + + p = compressedData = (byte*)ri.Hunk_AllocateTempMemory(size); + for (iy = 0; iy < height; iy += 4) + { + int oh = MIN(4, height - iy); + + for (ix = 0; ix < width; ix += 4) + { + byte workingData[16]; + int component; + + int ow = MIN(4, width - ix); + + for (component = 0; component < 2; component++) + { + int ox, oy; + + for (oy = 0; oy < oh; oy++) + for (ox = 0; ox < ow; ox++) + workingData[oy * 4 + ox] = data[((iy + oy) * width + ix + ox) * 4 + component]; + + // dupe data to fill + for (oy = 0; oy < 4; oy++) + for (ox = (oy < oh) ? ow : 0; ox < 4; ox++) + workingData[oy * 4 + ox] = workingData[(oy % oh) * 4 + ox % ow]; + + CompressMonoBlock(p, workingData); + p += 8; + } + } + } + + // FIXME: Won't work for x/y that aren't multiples of 4. + qglCompressedTextureSubImage2DEXT(texture, GL_TEXTURE_2D, miplevel, x, y, width, height, GL_COMPRESSED_RG_RGTC2, size, compressedData); + + ri.Hunk_FreeTempMemory(compressedData); +} + +static int CalculateMipSize(int width, int height, GLenum picFormat) +{ + int numBlocks = ((width + 3) / 4) * ((height + 3) / 4); + int numPixels = width * height; + + switch (picFormat) + { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RED_RGTC1: + case GL_COMPRESSED_SIGNED_RED_RGTC1: + return numBlocks * 8; + + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + case GL_COMPRESSED_RG_RGTC2: + case GL_COMPRESSED_SIGNED_RG_RGTC2: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: + case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB: + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + return numBlocks * 16; + + case GL_RGBA8: + case GL_SRGB8_ALPHA8_EXT: + return numPixels * 4; + + case GL_RGBA16: + return numPixels * 8; + + default: + ri.Printf(PRINT_ALL, "Unsupported texture format %08x\n", picFormat); + return 0; + } + + return 0; +} + + +static GLenum PixelDataFormatFromInternalFormat(GLenum internalFormat) +{ + switch (internalFormat) + { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16_ARB: + case GL_DEPTH_COMPONENT24_ARB: + case GL_DEPTH_COMPONENT32_ARB: + return GL_DEPTH_COMPONENT; + default: + return GL_RGBA; + break; + } +} + +static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, + int width, int height, GLenum target, GLenum picFormat, + int numMips, GLenum internalFormat, imgType_t type, int/*imgFlags_t*/ flags, bool subtexture ) +{ + GLenum dataFormat, dataType; + bool rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2; + bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; + bool rgba = rgba8 || picFormat == GL_RGBA16; + bool mipmap = !!(flags & IMGFLAG_MIPMAP); + int size, miplevel; + bool lastMip = false; + + dataFormat = PixelDataFormatFromInternalFormat(internalFormat); + dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + + miplevel = 0; + do + { + lastMip = (width == 1 && height == 1) || !mipmap; + size = CalculateMipSize(width, height, picFormat); + + if (!rgba) + { + qglCompressedTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, picFormat, size, data); + } + else + { + if (rgba8 && miplevel != 0 && r_colorMipLevels->integer) + R_BlendOverTexture((byte *)data, width * height, mipBlendColors[miplevel]); + + if (rgba8 && rgtc) + RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data); + else + qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data); + } + + if (!lastMip && numMips < 2) + { + if (glRefConfig.framebufferObject) + { + qglGenerateTextureMipmapEXT(texture, target); + break; + } + else if (rgba8) + { + if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) + R_MipMapNormalHeight(data, data, width, height, glRefConfig.swizzleNormalmap); + else + R_MipMapsRGB(data, width, height); + } + } + + x >>= 1; + y >>= 1; + width = MAX(1, width >> 1); + height = MAX(1, height >> 1); + miplevel++; + + if (numMips > 1) + { + data += size; + numMips--; + } + } + while (!lastMip); +} + + +/* +=============== +Upload32 + +=============== +*/ +static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, bool scaled) +{ + int i, c; + byte *scan; + + imgType_t type = image->type; + int/*imgFlags_t*/ flags = image->flags; + GLenum internalFormat = image->internalFormat; + bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; + bool mipmap = !!(flags & IMGFLAG_MIPMAP) && (rgba8 || numMips > 1); + bool cubemap = !!(flags & IMGFLAG_CUBEMAP); + + // These operations cannot be performed on non-rgba8 images. + if (rgba8 && !cubemap) + { + c = width*height; + scan = data; + + if (type == IMGTYPE_COLORALPHA) + { + if( r_greyscale->integer ) + { + for ( i = 0; i < c; i++ ) + { + byte luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = luma; + scan[i*4 + 1] = luma; + scan[i*4 + 2] = luma; + } + } + else if( r_greyscale->value ) + { + for ( i = 0; i < c; i++ ) + { + float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); + scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); + scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); + } + } + + // This corresponds to what the OpenGL1 renderer does. + if (!(flags & IMGFLAG_NOLIGHTSCALE) && (scaled || mipmap)) + R_LightScaleTexture(data, width, height, !mipmap); + } + + if (glRefConfig.swizzleNormalmap && (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)) + RawImage_SwizzleRA(data, width, height); + } + + if (cubemap) + { + for (i = 0; i < 6; i++) + { + int w2 = width, h2 = height; + RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, numMips, internalFormat, type, flags, false); + for (c = numMips; c; c--) + { + data += CalculateMipSize(w2, h2, picFormat); + w2 = MAX(1, w2 >> 1); + h2 = MAX(1, h2 >> 1); + } + } + } + else + { + RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, numMips, internalFormat, type, flags, false); + } + + GL_CheckErrors(); +} + + +/* +================ +R_CreateImage2 + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLenum picFormat, int numMips, imgType_t type, int/*imgFlags_t*/ flags, int internalFormat ) { + byte *resampledBuffer = NULL; + image_t *image; + bool isLightmap = false, scaled = false; + long hash; + int glWrapClampMode, mipWidth, mipHeight, miplevel; + bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; + bool mipmap = !!(flags & IMGFLAG_MIPMAP); + bool cubemap = !!(flags & IMGFLAG_CUBEMAP); + bool picmip = !!(flags & IMGFLAG_PICMIP); + bool lastMip; + GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; + GLenum dataFormat; + + if (strlen(name) >= MAX_QPATH ) { + ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); + } + if ( !strncmp( name, "*lightmap", 9 ) ) { + isLightmap = true; + } + + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit"); + } + + image = tr.images[tr.numImages] = (image_t*)ri.Hunk_Alloc( sizeof( image_t ), h_low ); + qglGenTextures(1, &image->texnum); + tr.numImages++; + + image->type = type; + image->flags = flags; + + strcpy (image->imgName, name); + + image->width = width; + image->height = height; + if (flags & IMGFLAG_CLAMPTOEDGE) + glWrapClampMode = GL_CLAMP_TO_EDGE; + else + glWrapClampMode = GL_REPEAT; + + if (!internalFormat) + internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags); + + image->internalFormat = internalFormat; + + // Possibly scale image before uploading. + // if not rgba8 and uploading an image, skip picmips. + if (!cubemap) + { + if (rgba8) + scaled = RawImage_ScaleToPower2(&pic, &width, &height, type, flags, &resampledBuffer); + else if (pic && picmip) + { + for (miplevel = r_picmip->integer; miplevel > 0 && numMips > 1; miplevel--, numMips--) + { + int size = CalculateMipSize(width, height, picFormat); + width = MAX(1, width >> 1); + height = MAX(1, height >> 1); + pic += size; + } + } + } + + image->uploadWidth = width; + image->uploadHeight = height; + + // Allocate texture storage so we don't have to worry about it later. + dataFormat = PixelDataFormatFromInternalFormat(internalFormat); + mipWidth = width; + mipHeight = height; + miplevel = 0; + do + { + lastMip = !mipmap || (mipWidth == 1 && mipHeight == 1); + if (cubemap) + { + int i; + + for (i = 0; i < 6; i++) + qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL); + } + else + { + qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL); + } + + mipWidth = MAX(1, mipWidth >> 1); + mipHeight = MAX(1, mipHeight >> 1); + miplevel++; + } + while (!lastMip); + + // Upload data. + if (pic) + Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled); + + if (resampledBuffer != NULL) + ri.Hunk_FreeTempMemory(resampledBuffer); + + // Set all necessary texture parameters. + qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_S, glWrapClampMode); + qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_T, glWrapClampMode); + + if (cubemap) + qglTextureParameteriEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_R, glWrapClampMode); + + if (glConfig.textureFilterAnisotropic && !cubemap) + qglTextureParameteriEXT(image->texnum, textureTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, + mipmap ? (GLint)Com_Clamp(1, glConfig.maxAnisotropy, r_ext_max_anisotropy->integer) : 1); + + switch(internalFormat) + { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16_ARB: + case GL_DEPTH_COMPONENT24_ARB: + case GL_DEPTH_COMPONENT32_ARB: + // Fix for sampling depth buffer on old nVidia cards. + // from http://www.idevgames.com/forums/thread-4141-post-34844.html#pid34844 + qglTextureParameterfEXT(image->texnum, textureTarget, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE); + qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + default: + qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MIN_FILTER, mipmap ? gl_filter_min : GL_LINEAR); + qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MAG_FILTER, mipmap ? gl_filter_max : GL_LINEAR); + break; + } + + GL_CheckErrors(); + + hash = generateHashValue(name); + image->next = hashTable[hash]; + hashTable[hash] = image; + + return image; +} + + +/* +================ +R_CreateImage + +Wrapper for R_CreateImage2(), for the old parameters. +================ +*/ +image_t *R_CreateImage(const char *name, byte *pic, int width, int height, + imgType_t type, int/*imgFlags_t*/ flags, int internalFormat) +{ + return R_CreateImage2(name, pic, width, height, GL_RGBA8, 0, type, flags, internalFormat); +} + + +void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat ) +{ + Upload32(pic, x, y, width, height, picFormat, 0, image, false); +} + +//=================================================================== + +// Prototype for dds loader function which isn't common to both renderers +void R_LoadDDS(const char *filename, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips); + +typedef struct +{ + const char *ext; + void (*ImageLoader)( const char *, unsigned char **, int *, int * ); +} imageExtToLoaderMap_t; + +// Note that the ordering indicates the order of preference used +// when there are multiple images of different formats available +static imageExtToLoaderMap_t imageLoaders[ ] = +{ + { "png", R_LoadPNG }, + { "tga", R_LoadTGA }, + { "jpg", R_LoadJPG }, + { "jpeg", R_LoadJPG }, + { "pcx", R_LoadPCX }, + { "bmp", R_LoadBMP } +}; + +static int numImageLoaders = ARRAY_LEN( imageLoaders ); + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *name, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips ) +{ + bool orgNameFailed = false; + int orgLoader = -1; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + const char *altName; + + *pic = NULL; + *width = 0; + *height = 0; + *picFormat = GL_RGBA8; + *numMips = 0; + + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + // If compressed textures are enabled, try loading a DDS first, it'll load fastest + if (r_ext_compressed_textures->integer) + { + char ddsName[MAX_QPATH]; + + COM_StripExtension(name, ddsName, MAX_QPATH); + Q_strcat(ddsName, MAX_QPATH, ".dds"); + + R_LoadDDS(ddsName, pic, width, height, picFormat, numMips); + + // If loaded, we're done. + if (*pic) + return; + } + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numImageLoaders; i++ ) + { + if( !Q_stricmp( ext, imageLoaders[ i ].ext ) ) + { + // Load + imageLoaders[ i ].ImageLoader( localName, pic, width, height ); + break; + } + } + + // A loader was found + if( i < numImageLoaders ) + { + if( *pic == NULL ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = true; + orgLoader = i; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return; + } + } + } + + // Try and find a suitable match using all + // the image formats supported + for( i = 0; i < numImageLoaders; i++ ) + { + if (i == orgLoader) + continue; + + altName = va( "%s.%s", localName, imageLoaders[ i ].ext ); + + // Load + imageLoaders[ i ].ImageLoader( altName, pic, width, height ); + + if( *pic ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } +} + + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, imgType_t type, int/*imgFlags_t*/ flags ) +{ + image_t *image; + int width, height; + byte *pic; + GLenum picFormat; + int picNumMips; + long hash; + int/*imgFlags_t*/ checkFlagsTrue, checkFlagsFalse; + + if (!name) { + return NULL; + } + + hash = generateHashValue(name); + + // + // see if the image is already loaded + // + for (image=hashTable[hash]; image; image=image->next) { + if ( !strcmp( name, image->imgName ) ) { + // the white image can be used with any set of parms, but other mismatches are errors + if ( strcmp( name, "*white" ) ) { + if ( image->flags != flags ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed flags (%i vs %i)\n", name, image->flags, flags ); + } + } + return image; + } + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height, &picFormat, &picNumMips ); + if ( pic == NULL ) { + return NULL; + } + + checkFlagsTrue = IMGFLAG_PICMIP | IMGFLAG_MIPMAP | IMGFLAG_GENNORMALMAP; + checkFlagsFalse = IMGFLAG_CUBEMAP; + if (r_normalMapping->integer && (picFormat == GL_RGBA8) && (type == IMGTYPE_COLORALPHA) && + ((flags & checkFlagsTrue) == checkFlagsTrue) && !(flags & checkFlagsFalse)) + { + char normalName[MAX_QPATH]; + image_t *normalImage; + int normalWidth, normalHeight; + int/*imgFlags_t*/ normalFlags; + + normalFlags = (flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE; + + COM_StripExtension(name, normalName, MAX_QPATH); + Q_strcat(normalName, MAX_QPATH, "_n"); + + // find normalmap in case it's there + normalImage = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags); + + // if not, generate it + if (normalImage == NULL) + { + byte *normalPic; + int x, y; + + normalWidth = width; + normalHeight = height; + normalPic = (byte*)ri.Malloc(width * height * 4); + RGBAtoNormal(pic, normalPic, width, height, flags & IMGFLAG_CLAMPTOEDGE); + +#if 1 + // Brighten up the original image to work with the normal map + RGBAtoYCoCgA(pic, pic, width, height); + for (y = 0; y < height; y++) + { + byte *picbyte = pic + y * width * 4; + byte *normbyte = normalPic + y * width * 4; + for (x = 0; x < width; x++) + { + int div = MAX(normbyte[2] - 127, 16); + picbyte[0] = CLAMP(picbyte[0] * 128 / div, 0, 255); + picbyte += 4; + normbyte += 4; + } + } + YCoCgAtoRGBA(pic, pic, width, height); +#else + // Blur original image's luma to work with the normal map + { + byte *blurPic; + + RGBAtoYCoCgA(pic, pic, width, height); + blurPic = ri.Malloc(width * height); + + for (y = 1; y < height - 1; y++) + { + byte *picbyte = pic + y * width * 4; + byte *blurbyte = blurPic + y * width; + + picbyte += 4; + blurbyte += 1; + + for (x = 1; x < width - 1; x++) + { + int result; + + result = *(picbyte - (width + 1) * 4) + *(picbyte - width * 4) + *(picbyte - (width - 1) * 4) + + *(picbyte - 1 * 4) + *(picbyte ) + *(picbyte + 1 * 4) + + *(picbyte + (width - 1) * 4) + *(picbyte + width * 4) + *(picbyte + (width + 1) * 4); + + result /= 9; + + *blurbyte = result; + picbyte += 4; + blurbyte += 1; + } + } + + // FIXME: do borders + + for (y = 1; y < height - 1; y++) + { + byte *picbyte = pic + y * width * 4; + byte *blurbyte = blurPic + y * width; + + picbyte += 4; + blurbyte += 1; + + for (x = 1; x < width - 1; x++) + { + picbyte[0] = *blurbyte; + picbyte += 4; + blurbyte += 1; + } + } + + ri.Free(blurPic); + + YCoCgAtoRGBA(pic, pic, width, height); + } +#endif + + R_CreateImage( normalName, normalPic, normalWidth, normalHeight, IMGTYPE_NORMAL, normalFlags, 0 ); + ri.Free( normalPic ); + } + } + + // force mipmaps off if image is compressed but doesn't have enough mips + if ((flags & IMGFLAG_MIPMAP) && picFormat != GL_RGBA8 && picFormat != GL_SRGB8_ALPHA8_EXT) + { + int wh = MAX(width, height); + int neededMips = 0; + while (wh) + { + neededMips++; + wh >>= 1; + } + if (neededMips > picNumMips) + flags &= ~IMGFLAG_MIPMAP; + } + + image = R_CreateImage2( ( char * ) name, pic, width, height, picFormat, picNumMips, type, flags, 0 ); + ri.Free( pic ); + return image; +} + + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) { + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 ); +} + + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0f/32.0f) / (30.0f/32.0f); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float d; + + data = (byte*)ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); + + // S is distance, T is depth + for (x=0 ; xinteger >= 2) + { + for( x = 0; x < MAX_DLIGHTS; x++) + { + tr.shadowCubemaps[x] = R_CreateImage(va("*shadowcubemap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE | IMGFLAG_CUBEMAP, 0); + } + } + + // with overbright bits active, we need an image which is some fraction of full color, + // for default lightmaps, etc + for (x=0 ; xinteger && glRefConfig.textureFloat) + hdrFormat = GL_RGBA16F_ARB; + + rgbFormat = GL_RGBA8; + + tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + + if (r_shadowBlur->integer) + tr.screenScratchImage = R_CreateImage("screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat); + + //if (r_shadowBlur->integer || r_ssao->integer) + // tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_INTENSITY32F_ARB); + if (r_shadowBlur->integer || r_ssao->integer) + tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_R32F); + + if (r_drawSunRays->integer) + tr.sunRaysImage = R_CreateImage("*sunRays", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat); + + tr.renderDepthImage = R_CreateImage("*renderdepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + tr.textureDepthImage = R_CreateImage("*texturedepth", NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + + { + byte *p; + + data[0][0][0] = 0; + data[0][0][1] = 0.45f * 255; + data[0][0][2] = 255; + data[0][0][3] = 255; + p = (byte*)data; + + tr.calcLevelsImage = R_CreateImage("*calcLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + tr.targetLevelsImage = R_CreateImage("*targetLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + tr.fixedLevelsImage = R_CreateImage("*fixedLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + } + + for (x = 0; x < 2; x++) + { + tr.textureScratchImage[x] = R_CreateImage(va("*textureScratch%d", x), NULL, 256, 256, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + for (x = 0; x < 2; x++) + { + tr.quarterImage[x] = R_CreateImage(va("*quarter%d", x), NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + + if (r_ssao->integer) + { + tr.screenSsaoImage = R_CreateImage("*screenSsao", NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + + for( x = 0; x < MAX_DRAWN_PSHADOWS; x++) + { + tr.pshadowMaps[x] = R_CreateImage(va("*shadowmap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24); + //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + } + + if (r_sunlightMode->integer) + { + for ( x = 0; x < 4; x++) + { + tr.sunShadowDepthImage[x] = R_CreateImage(va("*sunshadowdepth%i", x), NULL, r_shadowMapSize->integer, r_shadowMapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + } + + tr.screenShadowImage = R_CreateImage("*screenShadow", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + + if (r_cubeMapping->integer) + { + tr.renderCubeImage = R_CreateImage("*renderCube", NULL, r_cubemapSize->integer, r_cubemapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_CUBEMAP, rgbFormat); + } + } +} + + +/* +=============== +R_SetColorMappings +=============== +*/ +void R_SetColorMappings( void ) { + int i, j; + float g; + int inf; + + // setup the overbright lighting + tr.overbrightBits = r_overBrightBits->integer; + + // allow 2 overbright bits + if ( tr.overbrightBits > 2 ) { + tr.overbrightBits = 2; + } else if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + // don't allow more overbright bits than map overbright bits + if ( tr.overbrightBits > r_mapOverBrightBits->integer ) { + tr.overbrightBits = r_mapOverBrightBits->integer; + } + + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value <= 1 ) { + ri.Cvar_Set( "r_intensity", "1" ); + } + + if ( r_gamma->value < 0.5f ) { + ri.Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + ri.Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } + + if ( glConfig.deviceSupportsGamma ) + { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + Com_Memset(hashTable, 0, sizeof(hashTable)); + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +void R_DeleteTextures( void ) { + int i; + + for ( i=0; itexnum ); + } + Com_Memset( tr.images, 0, sizeof( tr.images ) ); + + tr.numImages = 0; + + GL_BindNullTextures(); +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static const char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + data += 2; + while (*data && *data != '\n') { + data++; + } + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + data += 2; + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if (len < MAX_TOKEN_CHARS - 1) + { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if (len < MAX_TOKEN_CHARS - 1) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32 && c != ',' ); + + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + + +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name ) { + skinSurface_t parseSurfaces[MAX_SKIN_SURFACES]; + qhandle_t hSkin; + skin_t *skin; + skinSurface_t *surf; + union { + char *c; + void *v; + } text; + char *text_p; + const char *token; + char surfName[MAX_QPATH]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_DEVELOPER, "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return hSkin; + } + } + + // allocate a new skin + if ( tr.numSkins == MAX_SKINS ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + tr.numSkins++; + skin = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + + R_IssuePendingRenderCommands(); + + // If not a .skin file, load as a single shader + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { + skin->numSurfaces = 1; + skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = R_FindShader( name, LIGHTMAP_NONE, true ); + return hSkin; + } + + // load and parse the skin file + ri.FS_ReadFile( name, &text.v ); + if ( !text.c ) { + return 0; + } + + int totalSurfaces = 0; + text_p = text.c; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( strstr( token, "tag_" ) ) { + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + if ( skin->numSurfaces < MAX_SKIN_SURFACES ) { + surf = &parseSurfaces[skin->numSurfaces]; + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, true ); + skin->numSurfaces++; + } + + totalSurfaces++; + } + + ri.FS_FreeFile( text.v ); + + if ( totalSurfaces > MAX_SKIN_SURFACES ) { + ri.Printf( PRINT_WARNING, "WARNING: Ignoring excess surfaces (found %d, max is %d) in skin '%s'!\n", + totalSurfaces, MAX_SKIN_SURFACES, name ); + } + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + // copy surfaces to skin + skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( skin->numSurfaces * sizeof( skinSurface_t ), h_low ); + memcpy( skin->surfaces, parseSurfaces, skin->numSurfaces * sizeof( skinSurface_t ) ); + + return hSkin; +} + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f( void ) { + int i, j; + skin_t *skin; + + ri.Printf (PRINT_ALL, "------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + + ri.Printf( PRINT_ALL, "%3i:%s (%d surfaces)\n", i, skin->name, skin->numSurfaces ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + ri.Printf( PRINT_ALL, " %s = %s\n", + skin->surfaces[j].name, skin->surfaces[j].shader->name ); + } + } + ri.Printf (PRINT_ALL, "------------------\n"); +} diff --git a/src/renderergl2/tr_image_dds.cpp b/src/renderergl2/tr_image_dds.cpp new file mode 100644 index 0000000..7c52ffb --- /dev/null +++ b/src/renderergl2/tr_image_dds.cpp @@ -0,0 +1,499 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + 2015 James Canete +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "renderercommon/tr_common.h" + +typedef unsigned int ui32_t; + +typedef struct ddsHeader_s +{ + ui32_t headerSize; + ui32_t flags; + ui32_t height; + ui32_t width; + ui32_t pitchOrFirstMipSize; + ui32_t volumeDepth; + ui32_t numMips; + ui32_t reserved1[11]; + ui32_t always_0x00000020; + ui32_t pixelFormatFlags; + ui32_t fourCC; + ui32_t rgbBitCount; + ui32_t rBitMask; + ui32_t gBitMask; + ui32_t bBitMask; + ui32_t aBitMask; + ui32_t caps; + ui32_t caps2; + ui32_t caps3; + ui32_t caps4; + ui32_t reserved2; +} +ddsHeader_t; + +// flags: +#define _DDSFLAGS_REQUIRED 0x001007 +#define _DDSFLAGS_PITCH 0x8 +#define _DDSFLAGS_MIPMAPCOUNT 0x20000 +#define _DDSFLAGS_FIRSTMIPSIZE 0x80000 +#define _DDSFLAGS_VOLUMEDEPTH 0x800000 + +// pixelFormatFlags: +#define DDSPF_ALPHAPIXELS 0x1 +#define DDSPF_ALPHA 0x2 +#define DDSPF_FOURCC 0x4 +#define DDSPF_RGB 0x40 +#define DDSPF_YUV 0x200 +#define DDSPF_LUMINANCE 0x20000 + +// caps: +#define DDSCAPS_COMPLEX 0x8 +#define DDSCAPS_MIPMAP 0x400000 +#define DDSCAPS_REQUIRED 0x1000 + +// caps2: +#define DDSCAPS2_CUBEMAP 0xFE00 +#define DDSCAPS2_VOLUME 0x200000 + +typedef struct ddsHeaderDxt10_s +{ + ui32_t dxgiFormat; + ui32_t dimensions; + ui32_t miscFlags; + ui32_t arraySize; + ui32_t miscFlags2; +} +ddsHeaderDxt10_t; + +// dxgiFormat +// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb173059%28v=vs.85%29.aspx +typedef enum DXGI_FORMAT { + DXGI_FORMAT_UNKNOWN = 0, + DXGI_FORMAT_R32G32B32A32_TYPELESS = 1, + DXGI_FORMAT_R32G32B32A32_FLOAT = 2, + DXGI_FORMAT_R32G32B32A32_UINT = 3, + DXGI_FORMAT_R32G32B32A32_SINT = 4, + DXGI_FORMAT_R32G32B32_TYPELESS = 5, + DXGI_FORMAT_R32G32B32_FLOAT = 6, + DXGI_FORMAT_R32G32B32_UINT = 7, + DXGI_FORMAT_R32G32B32_SINT = 8, + DXGI_FORMAT_R16G16B16A16_TYPELESS = 9, + DXGI_FORMAT_R16G16B16A16_FLOAT = 10, + DXGI_FORMAT_R16G16B16A16_UNORM = 11, + DXGI_FORMAT_R16G16B16A16_UINT = 12, + DXGI_FORMAT_R16G16B16A16_SNORM = 13, + DXGI_FORMAT_R16G16B16A16_SINT = 14, + DXGI_FORMAT_R32G32_TYPELESS = 15, + DXGI_FORMAT_R32G32_FLOAT = 16, + DXGI_FORMAT_R32G32_UINT = 17, + DXGI_FORMAT_R32G32_SINT = 18, + DXGI_FORMAT_R32G8X24_TYPELESS = 19, + DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22, + DXGI_FORMAT_R10G10B10A2_TYPELESS = 23, + DXGI_FORMAT_R10G10B10A2_UNORM = 24, + DXGI_FORMAT_R10G10B10A2_UINT = 25, + DXGI_FORMAT_R11G11B10_FLOAT = 26, + DXGI_FORMAT_R8G8B8A8_TYPELESS = 27, + DXGI_FORMAT_R8G8B8A8_UNORM = 28, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29, + DXGI_FORMAT_R8G8B8A8_UINT = 30, + DXGI_FORMAT_R8G8B8A8_SNORM = 31, + DXGI_FORMAT_R8G8B8A8_SINT = 32, + DXGI_FORMAT_R16G16_TYPELESS = 33, + DXGI_FORMAT_R16G16_FLOAT = 34, + DXGI_FORMAT_R16G16_UNORM = 35, + DXGI_FORMAT_R16G16_UINT = 36, + DXGI_FORMAT_R16G16_SNORM = 37, + DXGI_FORMAT_R16G16_SINT = 38, + DXGI_FORMAT_R32_TYPELESS = 39, + DXGI_FORMAT_D32_FLOAT = 40, + DXGI_FORMAT_R32_FLOAT = 41, + DXGI_FORMAT_R32_UINT = 42, + DXGI_FORMAT_R32_SINT = 43, + DXGI_FORMAT_R24G8_TYPELESS = 44, + DXGI_FORMAT_D24_UNORM_S8_UINT = 45, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46, + DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47, + DXGI_FORMAT_R8G8_TYPELESS = 48, + DXGI_FORMAT_R8G8_UNORM = 49, + DXGI_FORMAT_R8G8_UINT = 50, + DXGI_FORMAT_R8G8_SNORM = 51, + DXGI_FORMAT_R8G8_SINT = 52, + DXGI_FORMAT_R16_TYPELESS = 53, + DXGI_FORMAT_R16_FLOAT = 54, + DXGI_FORMAT_D16_UNORM = 55, + DXGI_FORMAT_R16_UNORM = 56, + DXGI_FORMAT_R16_UINT = 57, + DXGI_FORMAT_R16_SNORM = 58, + DXGI_FORMAT_R16_SINT = 59, + DXGI_FORMAT_R8_TYPELESS = 60, + DXGI_FORMAT_R8_UNORM = 61, + DXGI_FORMAT_R8_UINT = 62, + DXGI_FORMAT_R8_SNORM = 63, + DXGI_FORMAT_R8_SINT = 64, + DXGI_FORMAT_A8_UNORM = 65, + DXGI_FORMAT_R1_UNORM = 66, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67, + DXGI_FORMAT_R8G8_B8G8_UNORM = 68, + DXGI_FORMAT_G8R8_G8B8_UNORM = 69, + DXGI_FORMAT_BC1_TYPELESS = 70, + DXGI_FORMAT_BC1_UNORM = 71, + DXGI_FORMAT_BC1_UNORM_SRGB = 72, + DXGI_FORMAT_BC2_TYPELESS = 73, + DXGI_FORMAT_BC2_UNORM = 74, + DXGI_FORMAT_BC2_UNORM_SRGB = 75, + DXGI_FORMAT_BC3_TYPELESS = 76, + DXGI_FORMAT_BC3_UNORM = 77, + DXGI_FORMAT_BC3_UNORM_SRGB = 78, + DXGI_FORMAT_BC4_TYPELESS = 79, + DXGI_FORMAT_BC4_UNORM = 80, + DXGI_FORMAT_BC4_SNORM = 81, + DXGI_FORMAT_BC5_TYPELESS = 82, + DXGI_FORMAT_BC5_UNORM = 83, + DXGI_FORMAT_BC5_SNORM = 84, + DXGI_FORMAT_B5G6R5_UNORM = 85, + DXGI_FORMAT_B5G5R5A1_UNORM = 86, + DXGI_FORMAT_B8G8R8A8_UNORM = 87, + DXGI_FORMAT_B8G8R8X8_UNORM = 88, + DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89, + DXGI_FORMAT_B8G8R8A8_TYPELESS = 90, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91, + DXGI_FORMAT_B8G8R8X8_TYPELESS = 92, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93, + DXGI_FORMAT_BC6H_TYPELESS = 94, + DXGI_FORMAT_BC6H_UF16 = 95, + DXGI_FORMAT_BC6H_SF16 = 96, + DXGI_FORMAT_BC7_TYPELESS = 97, + DXGI_FORMAT_BC7_UNORM = 98, + DXGI_FORMAT_BC7_UNORM_SRGB = 99, + DXGI_FORMAT_AYUV = 100, + DXGI_FORMAT_Y410 = 101, + DXGI_FORMAT_Y416 = 102, + DXGI_FORMAT_NV12 = 103, + DXGI_FORMAT_P010 = 104, + DXGI_FORMAT_P016 = 105, + DXGI_FORMAT_420_OPAQUE = 106, + DXGI_FORMAT_YUY2 = 107, + DXGI_FORMAT_Y210 = 108, + DXGI_FORMAT_Y216 = 109, + DXGI_FORMAT_NV11 = 110, + DXGI_FORMAT_AI44 = 111, + DXGI_FORMAT_IA44 = 112, + DXGI_FORMAT_P8 = 113, + DXGI_FORMAT_A8P8 = 114, + DXGI_FORMAT_B4G4R4A4_UNORM = 115, + DXGI_FORMAT_FORCE_UINT = 0xffffffffUL +} DXGI_FORMAT; + +#define EncodeFourCC(x) ((((ui32_t)((x)[0])) ) | \ + (((ui32_t)((x)[1])) << 8 ) | \ + (((ui32_t)((x)[2])) << 16) | \ + (((ui32_t)((x)[3])) << 24) ) + + +void R_LoadDDS ( const char *filename, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips ) +{ + union { + byte *b; + void *v; + } buffer; + int len; + ddsHeader_t *ddsHeader = NULL; + ddsHeaderDxt10_t *ddsHeaderDxt10 = NULL; + byte *data; + + if (!picFormat) + { + ri.Printf(PRINT_ERROR, "R_LoadDDS() called without picFormat parameter!"); + return; + } + + if (width) + *width = 0; + if (height) + *height = 0; + if (picFormat) + *picFormat = GL_RGBA8; + if (numMips) + *numMips = 1; + + *pic = NULL; + + // + // load the file + // + len = ri.FS_ReadFile( ( char * ) filename, &buffer.v); + if (!buffer.b || len < 0) { + return; + } + + // + // reject files that are too small to hold even a header + // + if (len < 4 + sizeof(*ddsHeader)) + { + ri.Printf(PRINT_ALL, "File %s is too small to be a DDS file.\n", filename); + ri.FS_FreeFile(buffer.v); + return; + } + + // + // reject files that don't start with "DDS " + // + if (*((ui32_t *)(buffer.b)) != EncodeFourCC("DDS ")) + { + ri.Printf(PRINT_ALL, "File %s is not a DDS file.\n", filename); + ri.FS_FreeFile(buffer.v); + return; + } + + // + // parse header and dx10 header if available + // + ddsHeader = (ddsHeader_t *)(buffer.b + 4); + if ((ddsHeader->pixelFormatFlags & DDSPF_FOURCC) && ddsHeader->fourCC == EncodeFourCC("DX10")) + { + if (len < 4 + sizeof(*ddsHeader) + sizeof(*ddsHeaderDxt10)) + { + ri.Printf(PRINT_ALL, "File %s indicates a DX10 header it is too small to contain.\n", filename); + ri.FS_FreeFile(buffer.v); + return; + } + + ddsHeaderDxt10 = (ddsHeaderDxt10_t *)(buffer.b + 4 + sizeof(ddsHeader_t)); + data = buffer.b + 4 + sizeof(*ddsHeader) + sizeof(*ddsHeaderDxt10); + len -= 4 + sizeof(*ddsHeader) + sizeof(*ddsHeaderDxt10); + } + else + { + data = buffer.b + 4 + sizeof(*ddsHeader); + len -= 4 + sizeof(*ddsHeader); + } + + if (width) + *width = ddsHeader->width; + if (height) + *height = ddsHeader->height; + + if (numMips) + { + if (ddsHeader->flags & _DDSFLAGS_MIPMAPCOUNT) + *numMips = ddsHeader->numMips; + else + *numMips = 1; + } + + // FIXME: handle cube map + //if ((ddsHeader->caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP) + + // + // Convert DXGI format/FourCC into OpenGL format + // + if (ddsHeaderDxt10) + { + switch (ddsHeaderDxt10->dxgiFormat) + { + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + // FIXME: check for GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + *picFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + break; + + case DXGI_FORMAT_BC1_UNORM_SRGB: + // FIXME: check for GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT + *picFormat = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT; + break; + + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + + case DXGI_FORMAT_BC2_UNORM_SRGB: + *picFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + break; + + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + + case DXGI_FORMAT_BC3_UNORM_SRGB: + *picFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + break; + + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + *picFormat = GL_COMPRESSED_RED_RGTC1; + break; + + case DXGI_FORMAT_BC4_SNORM: + *picFormat = GL_COMPRESSED_SIGNED_RED_RGTC1; + break; + + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + *picFormat = GL_COMPRESSED_RG_RGTC2; + break; + + case DXGI_FORMAT_BC5_SNORM: + *picFormat = GL_COMPRESSED_SIGNED_RG_RGTC2; + break; + + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + *picFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; + break; + + case DXGI_FORMAT_BC6H_SF16: + *picFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; + break; + + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + *picFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + break; + + case DXGI_FORMAT_BC7_UNORM_SRGB: + *picFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; + break; + + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + *picFormat = GL_SRGB8_ALPHA8_EXT; + break; + + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_SNORM: + *picFormat = GL_RGBA8; + break; + + default: + ri.Printf(PRINT_ALL, "DDS File %s has unsupported DXGI format %d.", filename, ddsHeaderDxt10->dxgiFormat); + ri.FS_FreeFile(buffer.v); + return; + break; + } + } + else + { + if (ddsHeader->pixelFormatFlags & DDSPF_FOURCC) + { + if (ddsHeader->fourCC == EncodeFourCC("DXT1")) + *picFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + else if (ddsHeader->fourCC == EncodeFourCC("DXT2")) + *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + else if (ddsHeader->fourCC == EncodeFourCC("DXT3")) + *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + else if (ddsHeader->fourCC == EncodeFourCC("DXT4")) + *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + else if (ddsHeader->fourCC == EncodeFourCC("DXT5")) + *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + else if (ddsHeader->fourCC == EncodeFourCC("ATI1")) + *picFormat = GL_COMPRESSED_RED_RGTC1; + else if (ddsHeader->fourCC == EncodeFourCC("BC4U")) + *picFormat = GL_COMPRESSED_RED_RGTC1; + else if (ddsHeader->fourCC == EncodeFourCC("BC4S")) + *picFormat = GL_COMPRESSED_SIGNED_RED_RGTC1; + else if (ddsHeader->fourCC == EncodeFourCC("ATI2")) + *picFormat = GL_COMPRESSED_RG_RGTC2; + else if (ddsHeader->fourCC == EncodeFourCC("BC5U")) + *picFormat = GL_COMPRESSED_RG_RGTC2; + else if (ddsHeader->fourCC == EncodeFourCC("BC5S")) + *picFormat = GL_COMPRESSED_SIGNED_RG_RGTC2; + else + { + ri.Printf(PRINT_ALL, "DDS File %s has unsupported FourCC.", filename); + ri.FS_FreeFile(buffer.v); + return; + } + } + else if (ddsHeader->pixelFormatFlags == (DDSPF_RGB | DDSPF_ALPHAPIXELS) + && ddsHeader->rgbBitCount == 32 + && ddsHeader->rBitMask == 0x000000ff + && ddsHeader->gBitMask == 0x0000ff00 + && ddsHeader->bBitMask == 0x00ff0000 + && ddsHeader->aBitMask == 0xff000000) + { + *picFormat = GL_RGBA8; + } + else + { + ri.Printf(PRINT_ALL, "DDS File %s has unsupported RGBA format.", filename); + ri.FS_FreeFile(buffer.v); + return; + } + } + + *pic = (byte*)ri.Malloc(len); + Com_Memcpy(*pic, data, len); + + ri.FS_FreeFile(buffer.v); +} + +void R_SaveDDS(const char *filename, byte *pic, int width, int height, int depth) +{ + byte *data; + ddsHeader_t *ddsHeader; + int picSize, size; + + if (!depth) + depth = 1; + + picSize = width * height * depth * 4; + size = 4 + sizeof(*ddsHeader) + picSize; + data = (byte*)ri.Malloc(size); + + data[0] = 'D'; + data[1] = 'D'; + data[2] = 'S'; + data[3] = ' '; + + ddsHeader = (ddsHeader_t *)(data + 4); + memset(ddsHeader, 0, sizeof(ddsHeader_t)); + + ddsHeader->headerSize = 0x7c; + ddsHeader->flags = _DDSFLAGS_REQUIRED; + ddsHeader->height = height; + ddsHeader->width = width; + ddsHeader->always_0x00000020 = 0x00000020; + ddsHeader->caps = DDSCAPS_COMPLEX | DDSCAPS_REQUIRED; + + if (depth == 6) + ddsHeader->caps2 = DDSCAPS2_CUBEMAP; + + ddsHeader->pixelFormatFlags = DDSPF_RGB | DDSPF_ALPHAPIXELS; + ddsHeader->rgbBitCount = 32; + ddsHeader->rBitMask = 0x000000ff; + ddsHeader->gBitMask = 0x0000ff00; + ddsHeader->bBitMask = 0x00ff0000; + ddsHeader->aBitMask = 0xff000000; + + Com_Memcpy(data + 4 + sizeof(*ddsHeader), pic, picSize); + + ri.FS_WriteFile(filename, data, size); + + ri.Free(data); +} diff --git a/src/renderergl2/tr_init.cpp b/src/renderergl2/tr_init.cpp new file mode 100644 index 0000000..12e46a2 --- /dev/null +++ b/src/renderergl2/tr_init.cpp @@ -0,0 +1,1534 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_init.c -- functions that are not called every frame + +#include "tr_local.h" + +#include "tr_dsa.h" + +glconfig_t glConfig; +glRefConfig_t glRefConfig; +bool textureFilterAnisotropic = false; +int maxAnisotropy = 0; +float displayAspect = 0.0f; + +glstate_t glState; + +static void GfxInfo_f( void ); +static void GfxMemInfo_f( void ); + +#ifdef USE_RENDERER_DLOPEN +cvar_t *com_altivec; +#endif + +cvar_t *r_flareSize; +cvar_t *r_flareFade; +cvar_t *r_flareCoeff; + +cvar_t *r_railWidth; +cvar_t *r_railCoreWidth; +cvar_t *r_railSegmentLength; + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; +cvar_t *r_zproj; +cvar_t *r_stereoSeparation; + +cvar_t *r_skipBackEnd; + +cvar_t *r_stereoEnabled; +cvar_t *r_anaglyphMode; + +cvar_t *r_greyscale; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_inGameVideo; +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; +cvar_t *r_ext_texture_filter_anisotropic; +cvar_t *r_ext_max_anisotropy; + +cvar_t *r_ext_framebuffer_object; +cvar_t *r_ext_texture_float; +cvar_t *r_ext_framebuffer_multisample; +cvar_t *r_arb_seamless_cube_map; +cvar_t *r_arb_vertex_array_object; +cvar_t *r_ext_direct_state_access; + +cvar_t *r_cameraExposure; + +cvar_t *r_externalGLSL; + +cvar_t *r_hdr; +cvar_t *r_floatLightmap; +cvar_t *r_postProcess; + +cvar_t *r_toneMap; +cvar_t *r_forceToneMap; +cvar_t *r_forceToneMapMin; +cvar_t *r_forceToneMapAvg; +cvar_t *r_forceToneMapMax; + +cvar_t *r_autoExposure; +cvar_t *r_forceAutoExposure; +cvar_t *r_forceAutoExposureMin; +cvar_t *r_forceAutoExposureMax; + +cvar_t *r_depthPrepass; +cvar_t *r_ssao; + +cvar_t *r_normalMapping; +cvar_t *r_specularMapping; +cvar_t *r_deluxeMapping; +cvar_t *r_parallaxMapping; +cvar_t *r_cubeMapping; +cvar_t *r_cubemapSize; +cvar_t *r_pbr; +cvar_t *r_baseNormalX; +cvar_t *r_baseNormalY; +cvar_t *r_baseParallax; +cvar_t *r_baseSpecular; +cvar_t *r_baseGloss; +cvar_t *r_glossType; +cvar_t *r_mergeLightmaps; +cvar_t *r_dlightMode; +cvar_t *r_pshadowDist; +cvar_t *r_imageUpsample; +cvar_t *r_imageUpsampleMaxSize; +cvar_t *r_imageUpsampleType; +cvar_t *r_genNormalMaps; +cvar_t *r_forceSun; +cvar_t *r_forceSunLightScale; +cvar_t *r_forceSunAmbientScale; +cvar_t *r_sunlightMode; +cvar_t *r_drawSunRays; +cvar_t *r_sunShadows; +cvar_t *r_shadowFilter; +cvar_t *r_shadowBlur; +cvar_t *r_shadowMapSize; +cvar_t *r_shadowCascadeZNear; +cvar_t *r_shadowCascadeZFar; +cvar_t *r_shadowCascadeZBias; +cvar_t *r_ignoreDstAlpha; + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_alphabits; +cvar_t *r_texturebits; +cvar_t *r_ext_multisample; + +cvar_t *r_drawBuffer; +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_uiFullScreen; +cvar_t *r_shadows; +cvar_t *r_flares; +cvar_t *r_mode; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_roundImagesDown; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen; +cvar_t *r_noborder; + +cvar_t *r_width; +cvar_t *r_height; +cvar_t *r_pixelAspect; + +cvar_t *r_overBrightBits; +cvar_t *r_mapOverBrightBits; + +cvar_t *r_mapLightmapMin; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; +cvar_t *r_printShaders; +cvar_t *r_saveFontData; + +cvar_t *r_marksOnTriangleMeshes; + +cvar_t *r_aviMotionJpegQuality; +cvar_t *r_screenshotJpegQuality; + +cvar_t *r_maxpolys; +int max_polys; +cvar_t *r_maxpolyverts; +int max_polyverts; + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) +{ + char renderer_buffer[1024]; + + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_mode + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) + { + GLint temp; + + GLimp_Init( qtrue ); + GLimp_InitExtraExtensions(); + + strcpy( renderer_buffer, glConfig.renderer_string ); + Q_strlwr( renderer_buffer ); + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) + { + glConfig.maxTextureSize = 0; + } + } + + // set default state + GL_SetDefaultState(); +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrs( const char *file, int line ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof(s), "%i", err); + break; + } + + ri.Error( ERR_FATAL, "GL_CheckErrors: %s in %s at line %d", s , file, line); +} + + +/* +============================================================================== + + SCREEN SHOTS + +NOTE TTimo +some thoughts about the screenshots system: +screenshots get written in fs_homepath + fs_gamedir +vanilla q3 .. baseq3/screenshots/ *.tga +team arena .. missionpack/screenshots/ *.tga + +two commands: "screenshot" and "screenshotJPEG" +we use statics to store a count and start writing the first screenshot/screenshot????.tga (.jpg) available +(with FS_FileExists / FS_FOpenFileWrite calls) +FIXME: the statics don't get a reinit between fs_game changes + +============================================================================== +*/ + +/* +================== +RB_ReadPixels + +Reads an image but takes care of alignment issues for reading RGB images. + +Reads a minimum offset for where the RGB data starts in the image from +integer stored at pointer offset. When the function has returned the actual +offset was written back to address offset. This address will always have an +alignment of packAlign to ensure efficient copying. + +Stores the length of padding after a line of pixels to address padlen + +Return value must be freed with ri.Hunk_FreeTempMemory() +================== +*/ + +byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen) +{ + byte *buffer, *bufstart; + int padwidth, linelen; + GLint packAlign; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = width * 3; + padwidth = PAD(linelen, packAlign); + + // Allocate a few more bytes so that we can choose an alignment we like + buffer = (byte*)ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1); + + bufstart = (byte*)PADP((intptr_t) buffer + *offset, packAlign); + + qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart); + + *offset = bufstart - buffer; + *padlen = padwidth - linelen; + + return buffer; +} + +/* +================== +RB_TakeScreenshot +================== +*/ +void RB_TakeScreenshot(int x, int y, int width, int height, char *fileName) +{ + byte *allbuf, *buffer; + byte *srcptr, *destptr; + byte *endline, *endmem; + byte temp; + + int linelen, padlen; + size_t offset = 18, memcount; + + allbuf = RB_ReadPixels(x, y, width, height, &offset, &padlen); + buffer = allbuf + offset - 18; + + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + // swap rgb to bgr and remove padding from line endings + linelen = width * 3; + + srcptr = destptr = allbuf + offset; + endmem = srcptr + (linelen + padlen) * height; + + while(srcptr < endmem) + { + endline = srcptr + linelen; + + while(srcptr < endline) + { + temp = srcptr[0]; + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = temp; + + srcptr += 3; + } + + // Skip the pad + srcptr += padlen; + } + + memcount = linelen * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(allbuf + offset, memcount); + + ri.FS_WriteFile(fileName, buffer, memcount + 18); + + ri.Hunk_FreeTempMemory(allbuf); +} + +/* +================== +RB_TakeScreenshotJPEG +================== +*/ + +void RB_TakeScreenshotJPEG(int x, int y, int width, int height, char *fileName) +{ + byte *buffer; + size_t offset = 0, memcount; + int padlen; + + buffer = RB_ReadPixels(x, y, width, height, &offset, &padlen); + memcount = (width * 3 + padlen) * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(buffer + offset, memcount); + + RE_SaveJPG(fileName, r_screenshotJpegQuality->integer, width, height, buffer + offset, padlen); + ri.Hunk_FreeTempMemory(buffer); +} + +/* +================== +RB_TakeScreenshotCmd +================== +*/ +const void *RB_TakeScreenshotCmd( const void *data ) { + const screenshotCommand_t *cmd; + + cmd = (const screenshotCommand_t *)data; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + if (cmd->jpeg) + RB_TakeScreenshotJPEG( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + else + RB_TakeScreenshot( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + + return (const void *)(cmd + 1); +} + +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshot( int x, int y, int width, int height, char *name, bool jpeg ) { + static char fileName[MAX_OSPATH]; // bad things if two screenshots per frame? + screenshotCommand_t *cmd; + + cmd = (screenshotCommand_t*)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SCREENSHOT; + + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + Q_strncpyz( fileName, name, sizeof(fileName) ); + cmd->fileName = fileName; + cmd->jpeg = jpeg; +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga" + , a, b, c, d ); +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg" + , a, b, c, d ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 128*128 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +void R_LevelShot( void ) { + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source, *allsource; + byte *src, *dst; + size_t offset = 0; + int padlen; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + Com_sprintf(checkname, sizeof(checkname), "levelshots/%s.tga", tr.world->baseName); + + allsource = RB_ReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, &offset, &padlen); + source = allsource + offset; + + buffer = (byte*)ri.Hunk_AllocateTempMemory(128 * 128*3 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = 128; + buffer[14] = 128; + buffer[16] = 24; // pixel size + + // resample from source + xScale = glConfig.vidWidth / 512.0f; + yScale = glConfig.vidHeight / 384.0f; + for ( y = 0 ; y < 128 ; y++ ) { + for ( x = 0 ; x < 128 ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + (3 * glConfig.vidWidth + padlen) * (int)((y*3 + yy) * yScale) + + 3 * (int) ((x*4 + xx) * xScale); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * 128 + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, 128 * 128 * 3 ); + } + + ri.FS_WriteFile( checkname, buffer, 128 * 128*3 + 18 ); + + ri.Hunk_FreeTempMemory(buffer); + ri.Hunk_FreeTempMemory(allsource); + + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); +} + +/* +================== +R_ScreenShot_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShot_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + bool silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = true; + } else { + silent = false; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber >= 9999 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, false ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +void R_ScreenShotJPEG_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + bool silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = true; + } else { + silent = false; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilenameJPEG( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber == 10000 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, true ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +//============================================================================ + +/* +================== +R_ExportCubemaps +================== +*/ +void R_ExportCubemaps(void) +{ + exportCubemapsCommand_t *cmd; + + cmd = (exportCubemapsCommand_t*)R_GetCommandBuffer(sizeof(*cmd)); + if (!cmd) { + return; + } + cmd->commandId = RC_EXPORT_CUBEMAPS; +} + + +/* +================== +R_ExportCubemaps_f +================== +*/ +void R_ExportCubemaps_f(void) +{ + R_ExportCubemaps(); +} + +//============================================================================ + +/* +================== +RB_TakeVideoFrameCmd +================== +*/ +const void *RB_TakeVideoFrameCmd( const void *data ) +{ + const videoFrameCommand_t *cmd; + byte *cBuf; + size_t memcount, linelen; + int padwidth, avipadwidth, padlen, avipadlen; + GLint packAlign; + + // finish any 2D drawing if needed + if(tess.numIndexes) + RB_EndSurface(); + + cmd = (const videoFrameCommand_t *)data; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = cmd->width * 3; + + // Alignment stuff for glReadPixels + padwidth = PAD(linelen, packAlign); + padlen = padwidth - linelen; + // AVI line padding + avipadwidth = PAD(linelen, AVI_LINE_PADDING); + avipadlen = avipadwidth - linelen; + + cBuf = (byte*)PADP(cmd->captureBuffer, packAlign); + + qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB, + GL_UNSIGNED_BYTE, cBuf); + + memcount = padwidth * cmd->height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(cBuf, memcount); + + if(cmd->motionJpeg) + { + memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, + r_aviMotionJpegQuality->integer, + cmd->width, cmd->height, cBuf, padlen); + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); + } + else + { + byte *lineend, *memend; + byte *srcptr, *destptr; + + srcptr = cBuf; + destptr = cmd->encodeBuffer; + memend = srcptr + memcount; + + // swap R and B and remove line paddings + while(srcptr < memend) + { + lineend = srcptr + linelen; + while(srcptr < lineend) + { + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = srcptr[0]; + srcptr += 3; + } + + Com_Memset(destptr, '\0', avipadlen); + destptr += avipadlen; + + srcptr += padlen; + } + + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, avipadwidth * cmd->height); + } + + return (const void *)(cmd + 1); +} + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) +{ + qglClearDepth( 1.0f ); + + qglCullFace(GL_FRONT); + + GL_BindNullTextures(); + + if (glRefConfig.framebufferObject) + GL_BindNullFramebuffers(); + + GL_TextureMode( r_textureMode->string ); + + //qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + glState.storedGlState = 0; + glState.faceCulling = CT_TWO_SIDED; + glState.faceCullFront = true; + + GL_BindNullProgram(); + + if (glRefConfig.vertexArrayObject) + qglBindVertexArray(0); + + qglBindBuffer(GL_ARRAY_BUFFER, 0); + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glState.currentVao = NULL; + glState.vertexAttribsEnabled = 0; + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); + + if (glRefConfig.seamlessCubeMap) + qglEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + // GL_POLYGON_OFFSET_FILL will be glEnable()d when this is used + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky +} + +/* +================ +R_PrintLongString + +Workaround for ri.Printf's 1024 characters buffer limit. +================ +*/ +void R_PrintLongString(const char *string) { + char buffer[1024]; + const char *p; + int size = strlen(string); + + p = string; + while(size > 0) + { + Q_strncpyz(buffer, p, sizeof (buffer) ); + ri.Printf( PRINT_ALL, "%s", buffer ); + p += 1023; + size -= 1023; + } +} + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) +{ + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); + ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); + ri.Printf( PRINT_ALL, "GL_EXTENSIONS: " ); + R_PrintLongString( glConfig.extensions_string ); + ri.Printf( PRINT_ALL, "\n" ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits ); + ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + ri.Printf( PRINT_ALL, "MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) + { + ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency ); + } + else + { + ri.Printf( PRINT_ALL, "N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) + { + ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } + else + { + ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + + ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); + ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); + ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); + ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] ); + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) + { + ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" ); + } + if ( glConfig.hardwareType == GLHW_RAGEPRO ) + { + ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" ); + } + if ( glConfig.hardwareType == GLHW_RIVA128 ) + { + ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" ); + } + if ( r_finish->integer ) { + ri.Printf( PRINT_ALL, "Forcing glFinish\n" ); + } +} + +/* +================ +GfxMemInfo_f +================ +*/ +void GfxMemInfo_f( void ) +{ + switch (glRefConfig.memInfo) + { + case MI_NONE: + { + ri.Printf(PRINT_ALL, "No extension found for GPU memory info.\n"); + } + break; + case MI_NVX: + { + int value; + + qglGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: %ikb\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX: %ikb\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX: %ikb\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_EVICTION_COUNT_NVX: %i\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_EVICTED_MEMORY_NVX: %ikb\n", value); + } + break; + case MI_ATI: + { + // GL_ATI_meminfo + int value[4]; + + qglGetIntegerv(GL_VBO_FREE_MEMORY_ATI, &value[0]); + ri.Printf(PRINT_ALL, "VBO_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]); + + qglGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &value[0]); + ri.Printf(PRINT_ALL, "TEXTURE_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]); + + qglGetIntegerv(GL_RENDERBUFFER_FREE_MEMORY_ATI, &value[0]); + ri.Printf(PRINT_ALL, "RENDERBUFFER_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]); + } + break; + } +} + +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) +{ + #ifdef USE_RENDERER_DLOPEN + com_altivec = ri.Cvar_Get("com_altivec", "1", CVAR_ARCHIVE); + #endif + + // + // latched and archived variables + // + r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); + + r_ext_framebuffer_object = ri.Cvar_Get( "r_ext_framebuffer_object", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_float = ri.Cvar_Get( "r_ext_texture_float", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_arb_seamless_cube_map = ri.Cvar_Get( "r_arb_seamless_cube_map", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_arb_vertex_array_object = ri.Cvar_Get( "r_arb_vertex_array_object", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_direct_state_access = ri.Cvar_Get("r_ext_direct_state_access", "1", CVAR_ARCHIVE | CVAR_LATCH); + + r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic", + "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_max_anisotropy = ri.Cvar_Get( "r_ext_max_anisotropy", "2", CVAR_ARCHIVE | CVAR_LATCH ); + + r_picmip = ri.Cvar_Get ("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); + ri.Cvar_CheckRange( r_picmip, 0, 16, true ); + r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_alphabits = ri.Cvar_Get( "r_alphabits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_stencilbits = ri.Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH ); + r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH ); + ri.Cvar_CheckRange( r_ext_multisample, 0, 4, true ); + r_overBrightBits = ri.Cvar_Get ("r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_mode = ri.Cvar_Get( "r_mode", "-2", CVAR_ARCHIVE | CVAR_LATCH ); + r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE ); + r_noborder = ri.Cvar_Get("r_noborder", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_width = ri.Cvar_Get( "r_width", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_height = ri.Cvar_Get( "r_height", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_pixelAspect = ri.Cvar_Get( "r_pixelAspect", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0); + r_subdivisions = ri.Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH); + r_stereoEnabled = ri.Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_greyscale = ri.Cvar_Get("r_greyscale", "0", CVAR_ARCHIVE | CVAR_LATCH); + ri.Cvar_CheckRange(r_greyscale, 0, 1, false); + + r_externalGLSL = ri.Cvar_Get( "r_externalGLSL", "0", CVAR_LATCH ); + + r_hdr = ri.Cvar_Get( "r_hdr", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_floatLightmap = ri.Cvar_Get( "r_floatLightmap", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_postProcess = ri.Cvar_Get( "r_postProcess", "1", CVAR_ARCHIVE ); + + r_toneMap = ri.Cvar_Get( "r_toneMap", "1", CVAR_ARCHIVE ); + r_forceToneMap = ri.Cvar_Get( "r_forceToneMap", "0", CVAR_CHEAT ); + r_forceToneMapMin = ri.Cvar_Get( "r_forceToneMapMin", "-8.0", CVAR_CHEAT ); + r_forceToneMapAvg = ri.Cvar_Get( "r_forceToneMapAvg", "-2.0", CVAR_CHEAT ); + r_forceToneMapMax = ri.Cvar_Get( "r_forceToneMapMax", "0.0", CVAR_CHEAT ); + + r_autoExposure = ri.Cvar_Get( "r_autoExposure", "1", CVAR_ARCHIVE ); + r_forceAutoExposure = ri.Cvar_Get( "r_forceAutoExposure", "0", CVAR_CHEAT ); + r_forceAutoExposureMin = ri.Cvar_Get( "r_forceAutoExposureMin", "-2.0", CVAR_CHEAT ); + r_forceAutoExposureMax = ri.Cvar_Get( "r_forceAutoExposureMax", "2.0", CVAR_CHEAT ); + + r_cameraExposure = ri.Cvar_Get( "r_cameraExposure", "0", CVAR_CHEAT ); + + r_depthPrepass = ri.Cvar_Get( "r_depthPrepass", "1", CVAR_ARCHIVE ); + r_ssao = ri.Cvar_Get( "r_ssao", "0", CVAR_LATCH | CVAR_ARCHIVE ); + + r_normalMapping = ri.Cvar_Get( "r_normalMapping", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_specularMapping = ri.Cvar_Get( "r_specularMapping", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_deluxeMapping = ri.Cvar_Get( "r_deluxeMapping", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_parallaxMapping = ri.Cvar_Get( "r_parallaxMapping", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_cubeMapping = ri.Cvar_Get( "r_cubeMapping", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_cubemapSize = ri.Cvar_Get( "r_cubemapSize", "128", CVAR_ARCHIVE | CVAR_LATCH ); + r_pbr = ri.Cvar_Get("r_pbr", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_baseNormalX = ri.Cvar_Get( "r_baseNormalX", "1.0", CVAR_ARCHIVE | CVAR_LATCH ); + r_baseNormalY = ri.Cvar_Get( "r_baseNormalY", "1.0", CVAR_ARCHIVE | CVAR_LATCH ); + r_baseParallax = ri.Cvar_Get( "r_baseParallax", "0.05", CVAR_ARCHIVE | CVAR_LATCH ); + r_baseSpecular = ri.Cvar_Get( "r_baseSpecular", "0.04", CVAR_ARCHIVE | CVAR_LATCH ); + r_baseGloss = ri.Cvar_Get( "r_baseGloss", "0.3", CVAR_ARCHIVE | CVAR_LATCH ); + r_glossType = ri.Cvar_Get("r_glossType", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_dlightMode = ri.Cvar_Get( "r_dlightMode", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_pshadowDist = ri.Cvar_Get( "r_pshadowDist", "128", CVAR_ARCHIVE ); + r_mergeLightmaps = ri.Cvar_Get( "r_mergeLightmaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_imageUpsample = ri.Cvar_Get( "r_imageUpsample", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_imageUpsampleMaxSize = ri.Cvar_Get( "r_imageUpsampleMaxSize", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_imageUpsampleType = ri.Cvar_Get( "r_imageUpsampleType", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_genNormalMaps = ri.Cvar_Get( "r_genNormalMaps", "0", CVAR_ARCHIVE | CVAR_LATCH ); + + r_forceSun = ri.Cvar_Get( "r_forceSun", "0", CVAR_CHEAT ); + r_forceSunLightScale = ri.Cvar_Get( "r_forceSunLightScale", "1.0", CVAR_CHEAT ); + r_forceSunAmbientScale = ri.Cvar_Get( "r_forceSunAmbientScale", "0.5", CVAR_CHEAT ); + r_drawSunRays = ri.Cvar_Get( "r_drawSunRays", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_sunlightMode = ri.Cvar_Get( "r_sunlightMode", "1", CVAR_ARCHIVE | CVAR_LATCH ); + + r_sunShadows = ri.Cvar_Get( "r_sunShadows", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowFilter = ri.Cvar_Get( "r_shadowFilter", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowBlur = ri.Cvar_Get("r_shadowBlur", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_shadowMapSize = ri.Cvar_Get("r_shadowMapSize", "1024", CVAR_ARCHIVE | CVAR_LATCH); + r_shadowCascadeZNear = ri.Cvar_Get( "r_shadowCascadeZNear", "8", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowCascadeZFar = ri.Cvar_Get( "r_shadowCascadeZFar", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowCascadeZBias = ri.Cvar_Get( "r_shadowCascadeZBias", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignoreDstAlpha = ri.Cvar_Get( "r_ignoreDstAlpha", "1", CVAR_ARCHIVE | CVAR_LATCH ); + + // + // temporary latched variables that can only change over a restart + // + r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT ); + r_mapOverBrightBits = ri.Cvar_Get ("r_mapOverBrightBits", "2", CVAR_LATCH ); + r_mapLightmapMin = ri.Cvar_Get ("r_mapLightmapMin", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_singleShader = ri.Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE|CVAR_CHEAT ); + r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_flares = ri.Cvar_Get ("r_flares", "0", CVAR_ARCHIVE ); + r_znear = ri.Cvar_Get( "r_znear", "4", CVAR_CHEAT ); + ri.Cvar_CheckRange( r_znear, 0.001f, 200, false ); + r_zproj = ri.Cvar_Get( "r_zproj", "64", CVAR_ARCHIVE ); + r_stereoSeparation = ri.Cvar_Get( "r_stereoSeparation", "64", CVAR_ARCHIVE ); + r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + r_drawSun = ri.Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE ); + r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); + r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE ); + r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE); + r_textureMode = ri.Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE ); + r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", + CVAR_ARCHIVE | CVAR_LATCH ); + r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE ); + r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE ); + r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "6", CVAR_ARCHIVE ); + r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE ); + + r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT ); + r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE); + + // + // temporary variables that can change at any time + // + r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_CHEAT|CVAR_TEMP ); + + r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 ); + r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 ); + + r_nocurves = ri.Cvar_Get ("r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = ri.Cvar_Get ("r_drawworld", "1", CVAR_CHEAT ); + r_lightmap = ri.Cvar_Get ("r_lightmap", "0", 0 ); + r_portalOnly = ri.Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT ); + + r_flareSize = ri.Cvar_Get ("r_flareSize", "40", CVAR_CHEAT); + r_flareFade = ri.Cvar_Get ("r_flareFade", "7", CVAR_CHEAT); + r_flareCoeff = ri.Cvar_Get ("r_flareCoeff", FLARE_STDCOEFF, CVAR_CHEAT); + + r_skipBackEnd = ri.Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT); + + r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT ); + r_norefresh = ri.Cvar_Get ("r_norefresh", "0", CVAR_CHEAT); + r_drawentities = ri.Cvar_Get ("r_drawentities", "1", CVAR_CHEAT ); + r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT ); + r_nocull = ri.Cvar_Get ("r_nocull", "0", CVAR_CHEAT); + r_novis = ri.Cvar_Get ("r_novis", "0", CVAR_CHEAT); + r_showcluster = ri.Cvar_Get ("r_showcluster", "0", CVAR_CHEAT); + r_speeds = ri.Cvar_Get ("r_speeds", "0", CVAR_CHEAT); + r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = ri.Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT); + r_nobind = ri.Cvar_Get ("r_nobind", "0", CVAR_CHEAT); + r_showtris = ri.Cvar_Get ("r_showtris", "0", CVAR_CHEAT); + r_showsky = ri.Cvar_Get ("r_showsky", "0", CVAR_CHEAT); + r_shownormals = ri.Cvar_Get ("r_shownormals", "0", CVAR_CHEAT); + r_clear = ri.Cvar_Get ("r_clear", "0", CVAR_CHEAT); + r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); + r_lockpvs = ri.Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT); + r_noportals = ri.Cvar_Get ("r_noportals", "0", CVAR_CHEAT); + r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 ); + + r_marksOnTriangleMeshes = ri.Cvar_Get("r_marksOnTriangleMeshes", "0", CVAR_ARCHIVE); + + r_aviMotionJpegQuality = ri.Cvar_Get("r_aviMotionJpegQuality", "90", CVAR_ARCHIVE); + r_screenshotJpegQuality = ri.Cvar_Get("r_screenshotJpegQuality", "90", CVAR_ARCHIVE); + + r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); + r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + + // make sure all the commands added here are also + // removed in R_Shutdown + ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); + ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + ri.Cmd_AddCommand( "skinlist", R_SkinList_f ); + ri.Cmd_AddCommand( "modellist", R_Modellist_f ); + ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f ); + ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f ); + ri.Cmd_AddCommand( "minimize", GLimp_Minimize ); + ri.Cmd_AddCommand( "gfxmeminfo", GfxMemInfo_f ); + ri.Cmd_AddCommand( "exportCubemaps", R_ExportCubemaps_f ); +} + +void R_InitQueries(void) +{ + if (!glRefConfig.occlusionQuery) + return; + + if (r_drawSunRays->integer) + qglGenQueries(ARRAY_LEN(tr.sunFlareQuery), tr.sunFlareQuery); +} + +void R_ShutDownQueries(void) +{ + if (!glRefConfig.occlusionQuery) + return; + + if (r_drawSunRays->integer) + qglDeleteQueries(ARRAY_LEN(tr.sunFlareQuery), tr.sunFlareQuery); +} + +/* +=============== +R_Init +=============== +*/ +void R_Init( void ) { + int err; + int i; + byte *ptr; + + ri.Printf( PRINT_ALL, "----- R_Init -----\n" ); + + // clear all our internal state + Com_Memset( &tr, 0, sizeof( tr ) ); + Com_Memset( &backEnd, 0, sizeof( backEnd ) ); + Com_Memset( &tess, 0, sizeof( tess ) ); + +// Swap_Init(); + + if ( (intptr_t)tess.xyz & 15 ) { + ri.Printf( PRINT_WARNING, "tess.xyz not 16 byte aligned\n" ); + } + //Com_Memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) ); + + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) + { + if ( i < FUNCTABLE_SIZE / 4 ) + { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } + else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; + } + } + else + { + tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; + } + } + + R_InitFogTable(); + + R_NoiseInit(); + + R_Register(); + + max_polys = r_maxpolys->integer; + if (max_polys < MAX_POLYS) + max_polys = MAX_POLYS; + + max_polyverts = r_maxpolyverts->integer; + if (max_polyverts < MAX_POLYVERTS) + max_polyverts = MAX_POLYVERTS; + + ptr = (byte*)ri.Hunk_Alloc( sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData = (backEndData_t *) ptr; + backEndData->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData )); + backEndData->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys); + R_InitNextFrame(); + + InitOpenGL(); + + R_InitImages(); + + if (glRefConfig.framebufferObject) + FBO_Init(); + + GLSL_InitGPUShaders(); + + R_InitVaos(); + + R_InitShaders(); + + R_InitSkins(); + + R_ModelInit(); + + R_InitFreeType(); + + R_InitQueries(); + + + err = qglGetError(); + if ( err != GL_NO_ERROR ) + ri.Printf (PRINT_ALL, "glGetError() = 0x%x\n", err); + + // print info + GfxInfo_f(); + ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" ); +} + +/* +=============== +RE_Shutdown +=============== +*/ +void RE_Shutdown( bool destroyWindow ) { + + ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); + + ri.Cmd_RemoveCommand("exportCubemaps"); + ri.Cmd_RemoveCommand("gfxinfo"); + ri.Cmd_RemoveCommand("gfxmeminfo"); + ri.Cmd_RemoveCommand("imagelist"); + ri.Cmd_RemoveCommand("minimize"); + ri.Cmd_RemoveCommand("modellist"); + ri.Cmd_RemoveCommand("screenshot"); + ri.Cmd_RemoveCommand("screenshotJPEG"); + ri.Cmd_RemoveCommand("shaderlist"); + ri.Cmd_RemoveCommand("skinlist"); + + if ( tr.registered ) { + R_IssuePendingRenderCommands(); + R_ShutDownQueries(); + if (glRefConfig.framebufferObject) + FBO_Shutdown(); + R_DeleteTextures(); + R_ShutdownVaos(); + GLSL_ShutdownGPUShaders(); + } + + R_DoneFreeType(); + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + + Com_Memset( &glConfig, 0, sizeof( glConfig ) ); + Com_Memset( &glState, 0, sizeof( glState ) ); + } + + tr.registered = false; +} + + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +void RE_EndRegistration( void ) { + R_IssuePendingRenderCommands(); + if (!ri.Sys_LowPhysicalMemory()) { + RB_ShowImages(); + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +#ifdef USE_RENDERER_DLOPEN +extern "C" Q_EXPORT refexport_t* QDECL GetRefAPI ( int apiVersion, refimport_t *rimp ) { +#else +refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) { +#endif + + static refexport_t re; + + ri = *rimp; + + Com_Memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + ri.Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; + + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.ClearScene = RE_ClearScene; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.AddPolyToScene = RE_AddPolyToScene; + re.LightForPoint = R_LightForPoint; + re.AddLightToScene = RE_AddLightToScene; + re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene; + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.SetClipRegion = RE_SetClipRegion; + re.DrawStretchPic = RE_StretchPic; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + + re.RegisterFont = RE_RegisterFont; + re.RemapShader = R_RemapShader; + re.GetEntityToken = R_GetEntityToken; + re.inPVS = R_inPVS; + + re.TakeVideoFrame = RE_TakeVideoFrame; + + return &re; +} diff --git a/src/renderergl2/tr_light.cpp b/src/renderergl2/tr_light.cpp new file mode 100644 index 0000000..d14217f --- /dev/null +++ b/src/renderergl2/tr_light.cpp @@ -0,0 +1,513 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_light.c + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *orientation) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, orientation->origin, temp ); + dl->transformed[0] = DotProduct( temp, orientation->axis[0] ); + dl->transformed[1] = DotProduct( temp, orientation->axis[1] ); + dl->transformed[2] = DotProduct( temp, orientation->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void R_DlightBmodel( bmodel_t *bmodel ) { + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.orientation ); + + mask = 0; + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + + tr.currentEntity->needDlights = (mask != 0); + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = tr.world->surfaces + bmodel->firstSurface + i; + + switch(*surf->data) + { + case SF_FACE: + case SF_GRID: + case SF_TRIANGLES: + ((srfBspSurface_t *)surf->data)->dlightBits = mask; + break; + + default: + break; + } + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +static void R_SetupEntityLightingGrid( trRefEntity_t *ent, world_t *world ) { + vec3_t lightOrigin; + int pos[3]; + int i, j; + byte *gridData; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + VectorSubtract( lightOrigin, world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i]*world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] > world->lightGridBounds[i] - 1 ) { + pos[i] = world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + assert( world->lightGridData ); // NULL with -nolight maps + + // trilerp the light value + gridStep[0] = 8; + gridStep[1] = 8 * world->lightGridBounds[0]; + gridStep[2] = 8 * world->lightGridBounds[0] * world->lightGridBounds[1]; + gridData = world->lightGridData + pos[0] * gridStep[0] + + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + byte *data; + int lat, lng; + vec3_t normal; + #if idppc + float d0, d1, d2, d3, d4, d5; + #endif + factor = 1.0; + data = gridData; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & (1< world->lightGridBounds[j] - 1 ) { + break; // ignore values outside lightgrid + } + factor *= frac[j]; + data += gridStep[j]; + } else { + factor *= (1.0f - frac[j]); + } + } + + if ( j != 3 ) { + continue; + } + + if (world->lightGrid16) + { + uint16_t *data16 = world->lightGrid16 + (int)(data - world->lightGridData) / 8 * 6; + if (!(data16[0]+data16[1]+data16[2]+data16[3]+data16[4]+data16[5])) { + continue; // ignore samples in walls + } + } + else + { + if (!(data[0]+data[1]+data[2]+data[3]+data[4]+data[5]) ) { + continue; // ignore samples in walls + } + } + totalFactor += factor; + #if idppc + d0 = data[0]; d1 = data[1]; d2 = data[2]; + d3 = data[3]; d4 = data[4]; d5 = data[5]; + + ent->ambientLight[0] += factor * d0; + ent->ambientLight[1] += factor * d1; + ent->ambientLight[2] += factor * d2; + + ent->directedLight[0] += factor * d3; + ent->directedLight[1] += factor * d4; + ent->directedLight[2] += factor * d5; + #else + if (world->lightGrid16) + { + // FIXME: this is hideous + uint16_t *data16 = world->lightGrid16 + (int)(data - world->lightGridData) / 8 * 6; + + ent->ambientLight[0] += factor * data16[0] / 257.0f; + ent->ambientLight[1] += factor * data16[1] / 257.0f; + ent->ambientLight[2] += factor * data16[2] / 257.0f; + + ent->directedLight[0] += factor * data16[3] / 257.0f; + ent->directedLight[1] += factor * data16[4] / 257.0f; + ent->directedLight[2] += factor * data16[5] / 257.0f; + } + else + { + ent->ambientLight[0] += factor * data[0]; + ent->ambientLight[1] += factor * data[1]; + ent->ambientLight[2] += factor * data[2]; + + ent->directedLight[0] += factor * data[3]; + ent->directedLight[1] += factor * data[4]; + ent->directedLight[2] += factor * data[5]; + } + #endif + lat = data[7]; + lng = data[6]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) { + totalFactor = 1.0f / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + + ri.Printf( PRINT_ALL, "amb:%i dir:%i\n", max1, max2 ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = true; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent, tr.world ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + // bonus items and view weapons have a fixed minimum add + if ( !r_hdr->integer /* ent->e.renderfx & RF_MINLIGHT */ ) { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp lights + // FIXME: old renderer clamps (ambient + NL * directed) per vertex + // check if that's worth implementing + { + float r, g, b, max; + + r = ent->ambientLight[0]; + g = ent->ambientLight[1]; + b = ent->ambientLight[2]; + + max = MAX(MAX(r, g), b); + + if (max > 255.0f) + { + max = 255.0f / max; + ent->ambientLight[0] *= max; + ent->ambientLight[1] *= max; + ent->ambientLight[2] *= max; + } + + r = ent->directedLight[0]; + g = ent->directedLight[1]; + b = ent->directedLight[2]; + + max = MAX(MAX(r, g), b); + + if (max > 255.0f) + { + max = 255.0f / max; + ent->directedLight[0] *= max; + ent->directedLight[1] *= max; + ent->directedLight[2] *= max; + } + } + + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = static_cast(ent->ambientLight[0]); + ((byte *)&ent->ambientLightInt)[1] = static_cast(ent->ambientLight[1]); + ((byte *)&ent->ambientLightInt)[2] = static_cast(ent->ambientLight[2]); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + ent->modelLightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->modelLightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->modelLightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); + + VectorCopy(lightDir, ent->lightDir); +} + +/* +================= +R_LightForPoint +================= +*/ +bool R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + trRefEntity_t ent; + + if ( tr.world->lightGridData == NULL ) + return false; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent, tr.world ); + VectorCopy(ent.ambientLight, ambientLight); + VectorCopy(ent.directedLight, directedLight); + VectorCopy(ent.lightDir, lightDir); + + return true; +} + + +bool R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world ) +{ + trRefEntity_t ent; + + if ( world->lightGridData == NULL ) + return false; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent, world ); + + if (DotProduct(ent.lightDir, normal) > 0.2f) + VectorCopy(ent.lightDir, lightDir); + else + VectorCopy(normal, lightDir); + + return true; +} + + +int R_CubemapForPoint( vec3_t point ) +{ + int cubemapIndex = -1; + + if (r_cubeMapping->integer && tr.numCubemaps) + { + int i; + vec_t shortest = (float)WORLD_SIZE * (float)WORLD_SIZE; + + for (i = 0; i < tr.numCubemaps; i++) + { + vec3_t diff; + vec_t length; + + VectorSubtract(point, tr.cubemaps[i].origin, diff); + length = DotProduct(diff, diff); + + if (shortest > length) + { + shortest = length; + cubemapIndex = i; + } + } + } + + return cubemapIndex + 1; +} diff --git a/src/renderergl2/tr_local.h b/src/renderergl2/tr_local.h new file mode 100644 index 0000000..39df925 --- /dev/null +++ b/src/renderergl2/tr_local.h @@ -0,0 +1,2422 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + + +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + +#include + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/qfiles.h" +#include "qcommon/qcommon.h" +#include "renderercommon/tr_public.h" +#include "renderercommon/tr_common.h" +#include "tr_extratypes.h" +#include "tr_extramath.h" +#include "tr_fbo.h" +#include "tr_postprocess.h" +#include "renderercommon/iqm.h" + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; + +#define BUFFER_OFFSET(i) ((char *)NULL + (i)) + +// 14 bits +// can't be increased without changing bit packing for drawsurfs +// see QSORT_SHADERNUM_SHIFT +#define SHADERNUM_BITS 14 +#define MAX_SHADERS (1<or.origin in local coordinates + float modelMatrix[16]; + float transformMatrix[16]; +} orientationr_t; + +// Ensure this is >= the ATTR_INDEX_COUNT enum below +#define VAO_MAX_ATTRIBS 16 + +typedef enum +{ + VAO_USAGE_STATIC, + VAO_USAGE_DYNAMIC +} vaoUsage_t; + +typedef struct vaoAttrib_s +{ + uint32_t enabled; + uint32_t count; + uint32_t type; + uint32_t normalized; + uint32_t stride; + uint32_t offset; +} +vaoAttrib_t; + +typedef struct vao_s +{ + char name[MAX_QPATH]; + + uint32_t vao; + + uint32_t vertexesVBO; + int vertexesSize; // amount of memory data allocated for all vertices in bytes + vaoAttrib_t attribs[VAO_MAX_ATTRIBS]; + + uint32_t frameSize; // bytes to skip per frame when doing vertex animation + + uint32_t indexesIBO; + int indexesSize; // amount of memory data allocated for all triangles in bytes +} vao_t; + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +// deformVertexes types that can be handled by the GPU +typedef enum +{ + // do not edit: same as genFunc_t + + DGEN_NONE, + DGEN_WAVE_SIN, + DGEN_WAVE_SQUARE, + DGEN_WAVE_TRIANGLE, + DGEN_WAVE_SAWTOOTH, + DGEN_WAVE_INVERSE_SAWTOOTH, + DGEN_WAVE_NOISE, + + // do not edit until this line + + DGEN_BULGE, + DGEN_MOVE +} deformGen_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_CONST, +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_EXACT_VERTEX_LIT, // like CGEN_EXACT_VERTEX but takes a light direction from the lightgrid + CGEN_VERTEX_LIT, // like CGEN_VERTEX but takes a light direction from the lightgrid + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_FOG, // standard fog + CGEN_CONST // fixed color +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE + float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL + float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // + = clockwise + // - = counterclockwise + float rotateSpeed; + +} texModInfo_t; + + +#define MAX_IMAGE_ANIMATIONS 8 + +typedef struct { + image_t *image[MAX_IMAGE_ANIMATIONS]; + int numImageAnimations; + float imageAnimationSpeed; + + texCoordGen_t tcGen; + vec3_t tcGenVectors[2]; + + int numTexMods; + texModInfo_t *texMods; + + int videoMapHandle; + bool isLightmap; + bool isVideoMap; +} textureBundle_t; + +enum +{ + TB_COLORMAP = 0, + TB_DIFFUSEMAP = 0, + TB_LIGHTMAP = 1, + TB_LEVELSMAP = 1, + TB_SHADOWMAP3 = 1, + TB_NORMALMAP = 2, + TB_DELUXEMAP = 3, + TB_SHADOWMAP2 = 3, + TB_SPECULARMAP = 4, + TB_SHADOWMAP = 5, + TB_CUBEMAP = 6, + TB_SHADOWMAP4 = 6, + NUM_TEXTURE_BUNDLES = 7 +}; + +typedef enum +{ + // material shader stage types + ST_COLORMAP = 0, // vanilla Q3A style shader treatening + ST_DIFFUSEMAP = 0, // treat color and diffusemap the same + ST_NORMALMAP, + ST_NORMALPARALLAXMAP, + ST_SPECULARMAP, + ST_GLSL +} stageType_t; + +typedef struct { + bool active; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + bool isDetail; + + stageType_t type; + struct shaderProgram_s *glslShaderGroup; + int glslShaderIndex; + + vec4_t normalScale; + vec4_t specularScale; + +} shaderStage_t; + +struct shaderCommands_s; + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface) +} fogPass_t; + +typedef struct { + float cloudHeight; + image_t *outerbox[6], *innerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + int lightmapIndex; // for a shader to match, both name and lightmapIndex must match + + int index; // this shader == tr.shaders[index] + int sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + bool defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + + bool explicitlyDefined; // found in a .shader file + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + bool entityMergable; // merge across entites optimizable (smoke, blood) + + bool isSky; + skyParms_t sky; + fogParms_t fogParms; + + float portalRange; // distance to fog out at + bool isPortal; + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + bool polygonOffset; // set for decals and other items that must be offset + bool noMipMaps; // for console fonts, 2D elements, etc. + bool noPicMip; // for images that must always be full resolution + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + + int vertexAttribs; // not all shaders will need all data to be gathered + + int numDeforms; + deformStage_t deforms[MAX_SHADER_DEFORMS]; + + int numUnfoggedPasses; + shaderStage_t *stages[MAX_SHADER_STAGES]; + + void (*optimalStageIteratorFunc)( void ); + + double clampTime; // time this shader is clamped to + double timeOffset; // current time offset for this shader + + struct shader_s *remappedShader; // current shader this one is remapped too + + struct shader_s *next; +} shader_t; + +bool ShaderRequiresCPUDeforms(const shader_t*); + +enum +{ + ATTR_INDEX_POSITION = 0, + ATTR_INDEX_TEXCOORD = 1, + ATTR_INDEX_LIGHTCOORD = 2, + ATTR_INDEX_TANGENT = 3, + ATTR_INDEX_NORMAL = 4, + ATTR_INDEX_COLOR = 5, + ATTR_INDEX_PAINTCOLOR = 6, + ATTR_INDEX_LIGHTDIRECTION = 7, + ATTR_INDEX_BONE_INDEXES = 8, + ATTR_INDEX_BONE_WEIGHTS = 9, + + // GPU vertex animations + ATTR_INDEX_POSITION2 = 10, + ATTR_INDEX_TANGENT2 = 11, + ATTR_INDEX_NORMAL2 = 12, + + ATTR_INDEX_COUNT = 13 +}; + +enum +{ + ATTR_POSITION = 1 << ATTR_INDEX_POSITION, + ATTR_TEXCOORD = 1 << ATTR_INDEX_TEXCOORD, + ATTR_LIGHTCOORD = 1 << ATTR_INDEX_LIGHTCOORD, + ATTR_TANGENT = 1 << ATTR_INDEX_TANGENT, + ATTR_NORMAL = 1 << ATTR_INDEX_NORMAL, + ATTR_COLOR = 1 << ATTR_INDEX_COLOR, + ATTR_PAINTCOLOR = 1 << ATTR_INDEX_PAINTCOLOR, + ATTR_LIGHTDIRECTION = 1 << ATTR_INDEX_LIGHTDIRECTION, + ATTR_BONE_INDEXES = 1 << ATTR_INDEX_BONE_INDEXES, + ATTR_BONE_WEIGHTS = 1 << ATTR_INDEX_BONE_WEIGHTS, + + // for .md3 interpolation + ATTR_POSITION2 = 1 << ATTR_INDEX_POSITION2, + ATTR_TANGENT2 = 1 << ATTR_INDEX_TANGENT2, + ATTR_NORMAL2 = 1 << ATTR_INDEX_NORMAL2, + + ATTR_DEFAULT = ATTR_POSITION, + ATTR_BITS = ATTR_POSITION | + ATTR_TEXCOORD | + ATTR_LIGHTCOORD | + ATTR_TANGENT | + ATTR_NORMAL | + ATTR_COLOR | + ATTR_PAINTCOLOR | + ATTR_LIGHTDIRECTION | + ATTR_BONE_INDEXES | + ATTR_BONE_WEIGHTS | + ATTR_POSITION2 | + ATTR_TANGENT2 | + ATTR_NORMAL2 +}; + +enum +{ + GENERICDEF_USE_DEFORM_VERTEXES = 0x0001, + GENERICDEF_USE_TCGEN_AND_TCMOD = 0x0002, + GENERICDEF_USE_VERTEX_ANIMATION = 0x0004, + GENERICDEF_USE_FOG = 0x0008, + GENERICDEF_USE_RGBAGEN = 0x0010, + GENERICDEF_ALL = 0x001F, + GENERICDEF_COUNT = 0x0020, +}; + +enum +{ + FOGDEF_USE_DEFORM_VERTEXES = 0x0001, + FOGDEF_USE_VERTEX_ANIMATION = 0x0002, + FOGDEF_ALL = 0x0003, + FOGDEF_COUNT = 0x0004, +}; + +enum +{ + DLIGHTDEF_USE_DEFORM_VERTEXES = 0x0001, + DLIGHTDEF_ALL = 0x0001, + DLIGHTDEF_COUNT = 0x0002, +}; + +enum +{ + LIGHTDEF_USE_LIGHTMAP = 0x0001, + LIGHTDEF_USE_LIGHT_VECTOR = 0x0002, + LIGHTDEF_USE_LIGHT_VERTEX = 0x0003, + LIGHTDEF_LIGHTTYPE_MASK = 0x0003, + LIGHTDEF_ENTITY = 0x0004, + LIGHTDEF_USE_TCGEN_AND_TCMOD = 0x0008, + LIGHTDEF_USE_PARALLAXMAP = 0x0010, + LIGHTDEF_USE_SHADOWMAP = 0x0020, + LIGHTDEF_ALL = 0x003F, + LIGHTDEF_COUNT = 0x0040 +}; + +enum +{ + GLSL_INT, + GLSL_FLOAT, + GLSL_FLOAT5, + GLSL_VEC2, + GLSL_VEC3, + GLSL_VEC4, + GLSL_MAT16 +}; + +typedef enum +{ + UNIFORM_DIFFUSEMAP = 0, + UNIFORM_LIGHTMAP, + UNIFORM_NORMALMAP, + UNIFORM_DELUXEMAP, + UNIFORM_SPECULARMAP, + + UNIFORM_TEXTUREMAP, + UNIFORM_LEVELSMAP, + UNIFORM_CUBEMAP, + + UNIFORM_SCREENIMAGEMAP, + UNIFORM_SCREENDEPTHMAP, + + UNIFORM_SHADOWMAP, + UNIFORM_SHADOWMAP2, + UNIFORM_SHADOWMAP3, + UNIFORM_SHADOWMAP4, + + UNIFORM_SHADOWMVP, + UNIFORM_SHADOWMVP2, + UNIFORM_SHADOWMVP3, + UNIFORM_SHADOWMVP4, + + UNIFORM_ENABLETEXTURES, + + UNIFORM_DIFFUSETEXMATRIX, + UNIFORM_DIFFUSETEXOFFTURB, + + UNIFORM_TCGEN0, + UNIFORM_TCGEN0VECTOR0, + UNIFORM_TCGEN0VECTOR1, + + UNIFORM_DEFORMGEN, + UNIFORM_DEFORMPARAMS, + + UNIFORM_COLORGEN, + UNIFORM_ALPHAGEN, + UNIFORM_COLOR, + UNIFORM_BASECOLOR, + UNIFORM_VERTCOLOR, + + UNIFORM_DLIGHTINFO, + UNIFORM_LIGHTFORWARD, + UNIFORM_LIGHTUP, + UNIFORM_LIGHTRIGHT, + UNIFORM_LIGHTORIGIN, + UNIFORM_MODELLIGHTDIR, + UNIFORM_LIGHTRADIUS, + UNIFORM_AMBIENTLIGHT, + UNIFORM_DIRECTEDLIGHT, + + UNIFORM_PORTALRANGE, + + UNIFORM_FOGDISTANCE, + UNIFORM_FOGDEPTH, + UNIFORM_FOGEYET, + UNIFORM_FOGCOLORMASK, + + UNIFORM_MODELMATRIX, + UNIFORM_MODELVIEWPROJECTIONMATRIX, + + UNIFORM_TIME, + UNIFORM_VERTEXLERP, + UNIFORM_NORMALSCALE, + UNIFORM_SPECULARSCALE, + + UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2 + UNIFORM_VIEWORIGIN, + UNIFORM_LOCALVIEWORIGIN, + UNIFORM_VIEWFORWARD, + UNIFORM_VIEWLEFT, + UNIFORM_VIEWUP, + + UNIFORM_INVTEXRES, + UNIFORM_AUTOEXPOSUREMINMAX, + UNIFORM_TONEMINAVGMAXLINEAR, + + UNIFORM_PRIMARYLIGHTORIGIN, + UNIFORM_PRIMARYLIGHTCOLOR, + UNIFORM_PRIMARYLIGHTAMBIENT, + UNIFORM_PRIMARYLIGHTRADIUS, + + UNIFORM_CUBEMAPINFO, + + UNIFORM_ALPHATEST, + + UNIFORM_COUNT +} uniform_t; + +// shaderProgram_t represents a pair of one +// GLSL vertex and one GLSL fragment shader +typedef struct shaderProgram_s +{ + char name[MAX_QPATH]; + + GLuint program; + GLuint vertexShader; + GLuint fragmentShader; + uint32_t attribs; // vertex array attributes + + // uniform parameters + GLint uniforms[UNIFORM_COUNT]; + short uniformBufferOffsets[UNIFORM_COUNT]; // max 32767/64=511 uniforms + char *uniformBuffer; +} shaderProgram_t; + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + stereoFrame_t stereoFrame; + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + bool areamaskModified; // true if areamask changed since last scene + + double floatTime; // tr.refdef.time / 1000.0 + + float blurFactor; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + + int num_dlights; + struct dlight_s *dlights; + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; + + unsigned int dlightMask; + int num_pshadows; + struct pshadow_s *pshadows; + + float sunShadowMvp[4][16]; + float sunDir[4]; + float sunCol[4]; + float sunAmbCol[4]; + + float autoExposureMinMax[2]; + float toneMinAvgMaxLinear[3]; +} trRefdef_t; + + +//================================================================================= + +// max surfaces per-skin +// This is an arbitry limit. Vanilla Q3 only supported 32 surfaces in skins but failed to +// enforce the maximum limit when reading skin files. It was possile to use more than 32 +// surfaces which accessed out of bounds memory past end of skin->surfaces hunk block. +#define MAX_SKIN_SURFACES 256 + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + skinSurface_t *surfaces; // dynamically allocated array of surfaces +} skin_t; + + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + bool hasSurface; + float surface[4]; +} fog_t; + +typedef enum { + VPF_NONE = 0x00, + VPF_NOVIEWMODEL = 0x01, + VPF_SHADOWMAP = 0x02, + VPF_DEPTHSHADOW = 0x04, + VPF_DEPTHCLAMP = 0x08, + VPF_ORTHOGRAPHIC = 0x10, + VPF_USESUNLIGHT = 0x20, + VPF_FARPLANEFRUSTUM = 0x40, + VPF_NOCUBEMAPS = 0x80 +} viewParmFlags_t; + +typedef struct { + orientationr_t orientation; + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + bool isPortal; // true if this view is through a portal + bool isMirror; // the portal is a mirror, invert the face culling + int/*viewParmFlags_t*/ flags; + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + FBO_t *targetFbo; + int targetFboLayer; + int targetFboCubemapIndex; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[5]; + vec3_t visBounds[2]; + float zFar; + float zNear; + stereoFrame_t stereoFrame; +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ +typedef byte color4ub_t[4]; + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_MDV, + SF_MDR, + SF_IQM, + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + SF_VAO_MDVMESH, + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0x7fffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned int sort; // bit combination for fast compares + int cubemapIndex; + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +typedef struct +{ + vec3_t xyz; + vec2_t st; + vec2_t lightmap; + int16_t normal[4]; + int16_t tangent[4]; + int16_t lightdir[4]; + uint16_t color[4]; + +#if DEBUG_OPTIMIZEVERTICES + unsigned int id; +#endif +} srfVert_t; + +#define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} + +// srfBspSurface_t covers SF_GRID, SF_TRIANGLES and SF_POLY +typedef struct srfBspSurface_s +{ + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + int pshadowBits; + + // culling information + vec3_t cullBounds[2]; + vec3_t cullOrigin; + float cullRadius; + cplane_t cullPlane; + + // indexes + int numIndexes; + glIndex_t *indexes; + + // vertexes + int numVerts; + srfVert_t *verts; + + // SF_GRID specific variables after here + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + int lodFixed; + int lodStitched; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; +} srfBspSurface_t; + +// inter-quake-model +typedef struct { + int num_vertexes; + int num_triangles; + int num_frames; + int num_surfaces; + int num_joints; + int num_poses; + struct srfIQModel_s *surfaces; + + float *positions; + float *texcoords; + float *normals; + float *tangents; + byte *blendIndexes; + union { + float *f; + byte *b; + } blendWeights; + byte *colors; + int *triangles; + + // depending upon the exporter, blend indices and weights might be int/float + // as opposed to the recommended byte/byte, for example Noesis exports + // int/float whereas the official IQM tool exports byte/byte + byte blendWeightsType; // IQM_UBYTE or IQM_FLOAT + + int *jointParents; + float *jointMats; + float *poseMats; + float *bounds; + char *names; +} iqmData_t; + +// inter-quake-model surface +typedef struct srfIQModel_s { + surfaceType_t surfaceType; + char name[MAX_QPATH]; + shader_t *shader; + iqmData_t *data; + int first_vertex, num_vertexes; + int first_triangle, num_triangles; +} srfIQModel_t; + +typedef struct srfVaoMdvMesh_s +{ + surfaceType_t surfaceType; + + struct mdvModel_s *mdvModel; + struct mdvSurface_s *mdvSurface; + + // backEnd stats + int numIndexes; + int numVerts; + glIndex_t minIndex; + glIndex_t maxIndex; + + // static render data + vao_t *vao; +} srfVaoMdvMesh_t; + +extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *); + +/* +============================================================================== + +SHADOWS + +============================================================================== +*/ + +typedef struct pshadow_s +{ + float sort; + + int numEntities; + int entityNums[8]; + vec3_t entityOrigins[8]; + float entityRadiuses[8]; + + float viewRadius; + vec3_t viewOrigin; + + vec3_t lightViewAxis[3]; + vec3_t lightOrigin; + float lightRadius; + cplane_t cullPlane; +} pshadow_t; + + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +#define CULLINFO_NONE 0 +#define CULLINFO_BOX 1 +#define CULLINFO_SPHERE 2 +#define CULLINFO_PLANE 4 + +typedef struct cullinfo_s { + int type; + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + cplane_t plane; +} cullinfo_t; + +typedef struct msurface_s { + //int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + int cubemapIndex; + cullinfo_t cullinfo; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + +#define CONTENTS_NODE -1 +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visCounts[MAX_VISCOUNTS]; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + int firstmarksurface; + int nummarksurfaces; +} mnode_t; + +typedef struct { + vec3_t bounds[2]; // for culling + int firstSurface; + int numSurfaces; +} bmodel_t; + +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int dataSize; + + int numShaders; + dshader_t *shaders; + + int numBModels; + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + int numWorldSurfaces; + + int numsurfaces; + msurface_t *surfaces; + int *surfacesViewCount; + int *surfacesDlightBits; + int *surfacesPshadowBits; + + int nummarksurfaces; + int *marksurfaces; + + int numfogs; + fog_t *fogs; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + byte *lightGridData; + uint16_t *lightGrid16; + + + int numClusters; + int clusterBytes; + const byte *vis; // may be passed in by CM_LoadMap to save space + + char *entityString; + char *entityParsePoint; +} world_t; + + +/* +============================================================================== +MDV MODELS - meta format for vertex animation models like .md2, .md3, .mdc +============================================================================== +*/ +typedef struct +{ + float bounds[2][3]; + float localOrigin[3]; + float radius; +} mdvFrame_t; + +typedef struct +{ + float origin[3]; + float axis[3][3]; +} mdvTag_t; + +typedef struct +{ + char name[MAX_QPATH]; // tag name +} mdvTagName_t; + +typedef struct +{ + vec3_t xyz; + int16_t normal[4]; + int16_t tangent[4]; +} mdvVertex_t; + +typedef struct +{ + float st[2]; +} mdvSt_t; + +typedef struct mdvSurface_s +{ + surfaceType_t surfaceType; + + char name[MAX_QPATH]; // polyset name + + int numShaderIndexes; + int *shaderIndexes; + + int numVerts; + mdvVertex_t *verts; + mdvSt_t *st; + + int numIndexes; + glIndex_t *indexes; + + struct mdvModel_s *model; +} mdvSurface_t; + +typedef struct mdvModel_s +{ + int numFrames; + mdvFrame_t *frames; + + int numTags; + mdvTag_t *tags; + mdvTagName_t *tagNames; + + int numSurfaces; + mdvSurface_t *surfaces; + + int numVaoSurfaces; + srfVaoMdvMesh_t *vaoSurfaces; + + int numSkins; +} mdvModel_t; + + +//====================================================================== + +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, + MOD_MDR, + MOD_IQM +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + mdvModel_t *mdv[MD3_MAX_LODS]; // only if type == MOD_MESH + void *modelData; // only if type == (MOD_MDR | MOD_IQM) + + int numLods; +} model_t; + + +#define MAX_MOD_KNOWN 1024 + +void R_ModelInit (void); +model_t *R_GetModelByHandle( qhandle_t hModel ); +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f (void); + +//==================================================== + +#define MAX_DRAWIMAGES 2048 +#define MAX_SKINS 1024 + + +#define MAX_DRAWSURFS 0x10000 +#define DRAWSURF_MASK (MAX_DRAWSURFS-1) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +0 - 1 : dlightmap index +//2 : used to be clipped flag REMOVED - 03.21.00 rad +2 - 6 : fog index +11 - 20 : entity index +21 - 31 : sorted shader index + + TTimo - 1.32 +0-1 : dlightmap index +2-6 : fog index +7-16 : entity index +17-30 : sorted shader index + + SmileTheory - for pshadows +17-31 : sorted shader index +7-16 : entity index +2-6 : fog index +1 : pshadow flag +0 : dlight flag +*/ +#define QSORT_FOGNUM_SHIFT 2 +#define QSORT_REFENTITYNUM_SHIFT 7 +#define QSORT_SHADERNUM_SHIFT (QSORT_REFENTITYNUM_SHIFT+REFENTITYNUM_BITS) +#if (QSORT_SHADERNUM_SHIFT+SHADERNUM_BITS) > 32 + #error "Need to update sorting, too many bits." +#endif +#define QSORT_PSHADOW_SHIFT 1 + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) + + +// the renderer front end should never modify glstate_t +typedef struct { + bool finishCalled; + int texEnv[2]; + int faceCulling; + int faceCullFront; + uint32_t glStateBits; + uint32_t storedGlState; + float vertexAttribsInterpolation; + bool vertexAnimation; + uint32_t vertexAttribsEnabled; // global if no VAOs, tess only otherwise + FBO_t *currentFBO; + vao_t *currentVao; + mat4_t modelview; + mat4_t projection; + mat4_t modelviewProjection; +} glstate_t; + +typedef enum { + MI_NONE, + MI_NVX, + MI_ATI +} memInfo_t; + +typedef enum { + TCR_NONE = 0x0000, + TCR_RGTC = 0x0001, + TCR_BPTC = 0x0002, +} textureCompressionRef_t; + +// We can't change glConfig_t without breaking DLL/vms compatibility, so +// store extensions we have here. +typedef struct { + int openglMajorVersion; + int openglMinorVersion; + + bool intelGraphics; + + bool occlusionQuery; + + int glslMajorVersion; + int glslMinorVersion; + + memInfo_t memInfo; + + bool framebufferObject; + int maxRenderbufferSize; + int maxColorAttachments; + + bool textureFloat; + int /*textureCompressionRef_t*/ textureCompression; + bool swizzleNormalmap; + + bool framebufferMultisample; + bool framebufferBlit; + + bool depthClamp; + bool seamlessCubeMap; + + bool vertexArrayObject; + bool directStateAccess; +} glRefConfig_t; + + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + int c_surfBatches; + float c_overDraw; + + int c_vaoBinds; + int c_vaoVertexes; + int c_vaoIndexes; + + int c_staticVaoDraws; + int c_dynamicVaoDraws; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int c_glslShaderBinds; + int c_genericDraws; + int c_lightallDraws; + int c_fogDraws; + int c_dlightDraws; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t orientation; + backEndCounters_t pc; + bool isHyperspace; + trRefEntity_t *currentEntity; + bool skyRenderedThisView; // flag for drawing sun + + bool projection2D; // if true, drawstretchpic doesn't need to change modes + byte color2D[4]; + bool vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering + + FBO_t *last2DFBO; + bool colorMask[4]; + bool framePostProcessed; + bool depthFill; +} backEndState_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +typedef struct { + bool registered; // cleared at shutdown, set at beginRegistration + + int visIndex; + int visClusters[MAX_VISCOUNTS]; + int visCounts[MAX_VISCOUNTS]; // incremented every time a new vis cluster is entered + + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int frameSceneNum; // zeroed at RE_BeginFrame + + bool worldMapLoaded; + bool worldDeluxeMapping; + vec2_t autoExposureMinMax; + vec3_t toneMinAvgMaxLevel; + world_t *world; + + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load + + image_t *defaultImage; + image_t *scratchImage[32]; + image_t *fogImage; + image_t *dlightImage; // inverse-quare highlight for projective adding + image_t *flareImage; + image_t *whiteImage; // full of 0xff + image_t *identityLightImage; // full of tr.identityLightByte + + image_t *shadowCubemaps[MAX_DLIGHTS]; + + + image_t *renderImage; + image_t *sunRaysImage; + image_t *renderDepthImage; + image_t *pshadowMaps[MAX_DRAWN_PSHADOWS]; + image_t *screenScratchImage; + image_t *textureScratchImage[2]; + image_t *quarterImage[2]; + image_t *calcLevelsImage; + image_t *targetLevelsImage; + image_t *fixedLevelsImage; + image_t *sunShadowDepthImage[4]; + image_t *screenShadowImage; + image_t *screenSsaoImage; + image_t *hdrDepthImage; + image_t *renderCubeImage; + + image_t *textureDepthImage; + + FBO_t *renderFbo; + FBO_t *msaaResolveFbo; + FBO_t *sunRaysFbo; + FBO_t *depthFbo; + FBO_t *pshadowFbos[MAX_DRAWN_PSHADOWS]; + FBO_t *screenScratchFbo; + FBO_t *textureScratchFbo[2]; + FBO_t *quarterFbo[2]; + FBO_t *calcLevelsFbo; + FBO_t *targetLevelsFbo; + FBO_t *sunShadowFbo[4]; + FBO_t *screenShadowFbo; + FBO_t *screenSsaoFbo; + FBO_t *hdrDepthFbo; + FBO_t *renderCubeFbo; + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *projectionShadowShader; + + shader_t *flareShader; + shader_t *sunShader; + shader_t *sunFlareShader; + + int numLightmaps; + int lightmapSize; + image_t **lightmaps; + image_t **deluxemaps; + + int fatLightmapCols; + int fatLightmapRows; + + int numCubemaps; + cubemap_t *cubemaps; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + int shiftedEntityNum; // currentEntityNum << QSORT_REFENTITYNUM_SHIFT + model_t *currentModel; + + // + // GPU shader programs + // + shaderProgram_t genericShader[GENERICDEF_COUNT]; + shaderProgram_t textureColorShader; + shaderProgram_t fogShader[FOGDEF_COUNT]; + shaderProgram_t dlightShader[DLIGHTDEF_COUNT]; + shaderProgram_t lightallShader[LIGHTDEF_COUNT]; + shaderProgram_t shadowmapShader; + shaderProgram_t pshadowShader; + shaderProgram_t down4xShader; + shaderProgram_t bokehShader; + shaderProgram_t tonemapShader; + shaderProgram_t calclevels4xShader[2]; + shaderProgram_t shadowmaskShader; + shaderProgram_t ssaoShader; + shaderProgram_t depthBlurShader[4]; + shaderProgram_t testcubeShader; + + + // ----------------------------------------- + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t orientation; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + float sunShadowScale; + + bool sunShadows; + vec3_t sunLight; // from the sky shader for this level + vec3_t sunDirection; + vec3_t lastCascadeSunDirection; + float lastCascadeSunMvp[16]; + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + vec4_t clipRegion; // 2D clipping region + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + + int numImages; + image_t *images[MAX_DRAWIMAGES]; + + int numFBOs; + FBO_t *fbos[MAX_FBOS]; + + int numVaos; + vao_t *vaos[MAX_VAOS]; + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + + int numSkins; + skin_t *skins[MAX_SKINS]; + + GLuint sunFlareQuery[2]; + int sunFlareQueryIndex; + bool sunFlareQueryActive[2]; + + float sinTable[FUNCTABLE_SIZE]; + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; +} trGlobals_t; + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init +extern glRefConfig_t glRefConfig; + +// +// cvars +// +extern cvar_t *r_flareSize; +extern cvar_t *r_flareFade; +// coefficient for the flare intensity falloff function. +#define FLARE_STDCOEFF "150" +extern cvar_t *r_flareCoeff; + +extern cvar_t *r_railWidth; +extern cvar_t *r_railCoreWidth; +extern cvar_t *r_railSegmentLength; + +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew + +extern cvar_t *r_znear; // near Z clip plane +extern cvar_t *r_zproj; // z distance of projection plane +extern cvar_t *r_stereoSeparation; // separation of cameras for stereo rendering + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; + +extern cvar_t *r_inGameVideo; // controls whether in game video should be draw +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_gamma; + +extern cvar_t *r_ext_framebuffer_object; +extern cvar_t *r_ext_texture_float; +extern cvar_t *r_ext_framebuffer_multisample; +extern cvar_t *r_arb_seamless_cube_map; +extern cvar_t *r_arb_vertex_array_object; +extern cvar_t *r_ext_direct_state_access; + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_roundImagesDown; +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +extern cvar_t *r_fullbright; // avoid lightmap pass +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance +extern cvar_t *r_uiFullScreen; // ui is running fullscreen + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_anaglyphMode; + +extern cvar_t *r_externalGLSL; + +extern cvar_t *r_hdr; +extern cvar_t *r_floatLightmap; +extern cvar_t *r_postProcess; + +extern cvar_t *r_toneMap; +extern cvar_t *r_forceToneMap; +extern cvar_t *r_forceToneMapMin; +extern cvar_t *r_forceToneMapAvg; +extern cvar_t *r_forceToneMapMax; + +extern cvar_t *r_autoExposure; +extern cvar_t *r_forceAutoExposure; +extern cvar_t *r_forceAutoExposureMin; +extern cvar_t *r_forceAutoExposureMax; + +extern cvar_t *r_cameraExposure; + +extern cvar_t *r_depthPrepass; +extern cvar_t *r_ssao; + +extern cvar_t *r_normalMapping; +extern cvar_t *r_specularMapping; +extern cvar_t *r_deluxeMapping; +extern cvar_t *r_parallaxMapping; +extern cvar_t *r_cubeMapping; +extern cvar_t *r_cubemapSize; +extern cvar_t *r_pbr; +extern cvar_t *r_baseNormalX; +extern cvar_t *r_baseNormalY; +extern cvar_t *r_baseParallax; +extern cvar_t *r_baseSpecular; +extern cvar_t *r_baseGloss; +extern cvar_t *r_glossType; +extern cvar_t *r_dlightMode; +extern cvar_t *r_pshadowDist; +extern cvar_t *r_mergeLightmaps; +extern cvar_t *r_imageUpsample; +extern cvar_t *r_imageUpsampleMaxSize; +extern cvar_t *r_imageUpsampleType; +extern cvar_t *r_genNormalMaps; +extern cvar_t *r_forceSun; +extern cvar_t *r_forceSunLightScale; +extern cvar_t *r_forceSunAmbientScale; +extern cvar_t *r_sunlightMode; +extern cvar_t *r_drawSunRays; +extern cvar_t *r_sunShadows; +extern cvar_t *r_shadowFilter; +extern cvar_t *r_shadowBlur; +extern cvar_t *r_shadowMapSize; +extern cvar_t *r_shadowCascadeZNear; +extern cvar_t *r_shadowCascadeZFar; +extern cvar_t *r_shadowCascadeZBias; +extern cvar_t *r_ignoreDstAlpha; + +extern cvar_t *r_greyscale; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; +extern cvar_t *r_mapOverBrightBits; + +extern cvar_t *r_mapLightmapMin; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; + +extern cvar_t *r_printShaders; + +extern cvar_t *r_marksOnTriangleMeshes; + +//==================================================================== + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); +void R_RenderDlightCubemaps(const refdef_t *fd); +void R_RenderPshadowMaps(const refdef_t *fd); +void R_RenderSunShadowMaps(const refdef_t *fd, int level); +void R_RenderCubemapSide( int cubemapIndex, int cubemapSide, bool subscene ); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, bool isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap, int *pshadowMap ); + +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap, int pshadowMap, int cubemap ); + +void R_CalcTexDirs(vec3_t sdir, vec3_t tdir, const vec3_t v1, const vec3_t v2, + const vec3_t v3, const vec2_t w1, const vec2_t w2, const vec2_t w3); +vec_t R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, const vec3_t normal, const vec3_t sdir, const vec3_t tdir); +bool R_CalcTangentVectors(srfVert_t * dv[3]); + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld (const vec3_t local, vec3_t world); +void R_LocalPointToWorld (const vec3_t local, vec3_t world); +int R_CullBox (vec3_t bounds[2]); +int R_CullLocalBox (vec3_t bounds[2]); +int R_CullPointAndRadiusEx( const vec3_t origin, float radius, const cplane_t* frustum, int numPlanes ); +int R_CullPointAndRadius( const vec3_t origin, float radius ); +int R_CullLocalPointAndRadius( const vec3_t origin, float radius ); + +void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, bool computeFrustum); +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *orientation ); + +/* +** GL wrapper/helper functions +*/ +void GL_BindToTMU( image_t *image, int tmu ); +void GL_SetDefaultState (void); +void GL_TextureMode( const char *string ); +void GL_CheckErrs( const char *file, int line ); +#define GL_CheckErrors(...) GL_CheckErrs(__FILE__, __LINE__) +void GL_State( unsigned long stateVector ); +void GL_SetProjectionMatrix(mat4_t matrix); +void GL_SetModelviewMatrix(mat4_t matrix); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 +#define GLS_DEPTHFUNC_GREATER 0x00040000 +#define GLS_DEPTHFUNC_BITS 0x00060000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_BITS 0x70000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE + +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, bool dirty); +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, bool dirty); + +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_BeginRegistration( glconfig_t *glconfig ); +void RE_LoadWorldMap( const char *mapname ); +void RE_SetWorldVisData( const byte *vis ); +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +void RE_Shutdown( bool destroyWindow ); + +bool R_GetEntityToken( char *buffer, int size ); + +model_t *R_AllocModel( void ); + +void R_Init( void ); +void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 +const void *RB_TakeScreenshotCmd( const void *data ); +void R_ScreenShot_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +int R_SumOfUsedImages( void ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + +int R_ComputeLOD( trRefEntity_t *ent ); + +const void *RB_TakeVideoFrameCmd( const void *data ); + +// +// tr_shader.c +// +shader_t *R_FindShader( const char *name, int lightmapIndex, bool mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +shader_t *R_GetShaderByState( int index, long *cycleTime ); +shader_t *R_FindShaderByName( const char *name ); +void R_InitShaders( void ); +void R_ShaderList_f( void ); +void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset); + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_InitExtraExtensions( void ); + +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ + +typedef struct stageVars +{ + color4ub_t colors[SHADER_MAX_VERTEXES]; + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + +typedef struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16); + vec4_t xyz[SHADER_MAX_VERTEXES] QALIGN(16); + int16_t normal[SHADER_MAX_VERTEXES][4] QALIGN(16); + int16_t tangent[SHADER_MAX_VERTEXES][4] QALIGN(16); + vec2_t texCoords[SHADER_MAX_VERTEXES] QALIGN(16); + vec2_t lightCoords[SHADER_MAX_VERTEXES] QALIGN(16); + uint16_t color[SHADER_MAX_VERTEXES][4] QALIGN(16); + int16_t lightdir[SHADER_MAX_VERTEXES][4] QALIGN(16); + //int vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16); + + void *attribPointers[ATTR_INDEX_COUNT]; + vao_t *vao; + bool useInternalVao; + bool useCacheVao; + + stageVars_t svars QALIGN(16); + + //color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16); + + shader_t *shader; + double shaderTime; + int fogNum; + int cubemapIndex; + + int dlightBits; // or together of all vertexDlightBits + int pshadowBits; + + int firstIndex; + int numIndexes; + int numVertexes; + + // info extracted from current shader + int numPasses; + void (*currentStageIteratorFunc)( void ); + shaderStage_t **xstages; +} shaderCommands_t; + +extern shaderCommands_t tess; + +void RB_BeginSurface(shader_t *shader, int fogNum, int cubemapIndex ); +void RB_EndSurface(void); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} + +void R_DrawElements( int numIndexes, glIndex_t firstIndex ); +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); +void RB_StageIteratorVertexLitTexture( void ); +void RB_StageIteratorLightmappedMultitexture( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, float color[4] ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4], float s1, float t1, float s2, float t2 ); +void RB_InstantQuad( vec4_t quadVerts[4] ); +//void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4], vec4_t color, shaderProgram_t *sp, vec2_t invTexRes); +void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]); + +void RB_ShowImages( void ); + + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); +bool R_inPVS( const vec3_t p1, const vec3_t p2 ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ); +void RB_AddDlightFlares( void ); +void RB_RenderFlares (void); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *orientation ); +bool R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +bool R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world ); +int R_CubemapForPoint( vec3_t point ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ + +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( float scale, shader_t *shader ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); + +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ + +#define PATCH_STITCHING + +void R_SubdividePatchToGrid( srfBspSurface_t *grid, int width, int height, + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ); +void R_GridInsertColumn( srfBspSurface_t *grid, int column, int row, vec3_t point, float loderror ); +void R_GridInsertRow( srfBspSurface_t *grid, int row, int column, vec3_t point, float loderror ); + +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + +/* +============================================================ + +VERTEX BUFFER OBJECTS + +============================================================ +*/ + +void R_VaoPackTangent(int16_t *out, vec4_t v); +void R_VaoPackNormal(int16_t *out, vec3_t v); +void R_VaoPackColor(uint16_t *out, vec4_t c); +void R_VaoUnpackTangent(vec4_t v, int16_t *pack); +void R_VaoUnpackNormal(vec3_t v, int16_t *pack); + +vao_t *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage); +vao_t *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *inIndexes); + +void R_BindVao(vao_t *vao); +void R_BindNullVao(void); + +void Vao_SetVertexPointers(vao_t *vao); + +void R_InitVaos(void); +void R_ShutdownVaos(void); +void R_VaoList_f(void); + +void RB_UpdateTessVao(unsigned int attribBits); + +void VaoCache_Commit(void); +void VaoCache_Init(void); +void VaoCache_BindVao(void); +void VaoCache_CheckAdd(bool *endSurface, bool *recycleVertexBuffer, bool *recycleIndexBuffer, int numVerts, int numIndexes); +void VaoCache_RecycleVertexBuffer(void); +void VaoCache_RecycleIndexBuffer(void); +void VaoCache_InitNewSurfaceSet(void); +void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int numIndexes); + +/* +============================================================ + +GLSL + +============================================================ +*/ + +void GLSL_InitGPUShaders(void); +void GLSL_ShutdownGPUShaders(void); +void GLSL_VertexAttribPointers(uint32_t attribBits); +void GLSL_BindProgram(shaderProgram_t * program); + +void GLSL_SetUniformInt(shaderProgram_t *program, int uniformNum, GLint value); +void GLSL_SetUniformFloat(shaderProgram_t *program, int uniformNum, GLfloat value); +void GLSL_SetUniformFloat5(shaderProgram_t *program, int uniformNum, const vec5_t v); +void GLSL_SetUniformVec2(shaderProgram_t *program, int uniformNum, const vec2_t v); +void GLSL_SetUniformVec3(shaderProgram_t *program, int uniformNum, const vec3_t v); +void GLSL_SetUniformVec4(shaderProgram_t *program, int uniformNum, const vec4_t v); +void GLSL_SetUniformMat4(shaderProgram_t *program, int uniformNum, const mat4_t matrix); + +shaderProgram_t *GLSL_GetGenericShaderProgram(int stage); + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_InitNextFrame( void ); + +void RE_ClearScene( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_BeginScene( const refdef_t *fd ); +void RE_RenderScene( const refdef_t *fd ); +void RE_EndScene( void ); + +/* +============================================================= + +UNCOMPRESSING BONES + +============================================================= +*/ + +#define MC_BITS_X (16) +#define MC_BITS_Y (16) +#define MC_BITS_Z (16) +#define MC_BITS_VECT (16) + +#define MC_SCALE_X (1.0f/64) +#define MC_SCALE_Y (1.0f/64) +#define MC_SCALE_Z (1.0f/64) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp); + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ); +void RB_MDRSurfaceAnim( mdrSurface_t *surface ); +bool R_LoadIQM (model_t *mod, void *buffer, int filesize, const char *name ); +void R_AddIQMSurfaces( trRefEntity_t *ent ); +void RB_IQMSurfaceAnim( surfaceType_t *surface ); +int R_IQMLerpTag( orientation_t *tag, iqmData_t *data, + int startFrame, int endFrame, + float frac, const char *tagName ); + +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcFogTexCoords( float *dstTexCoords ); + +void RB_CalcScaleTexMatrix( const float scale[2], float *matrix ); +void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix ); +void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix ); +void RB_CalcTurbulentFactors( const waveForm_t *wf, float *amplitude, float *now ); +void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix ); +void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix ); + +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +float RB_CalcWaveAlphaSingle( const waveForm_t *wf ); +float RB_CalcWaveColorSingle( const waveForm_t *wf ); + +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#define MAX_RENDER_COMMANDS 0x40000 + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; +} stretchPicCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +} drawSurfsCommand_t; + +typedef struct { + int commandId; + int x; + int y; + int width; + int height; + char *fileName; + bool jpeg; +} screenshotCommand_t; + +typedef struct { + int commandId; + int width; + int height; + byte *captureBuffer; + byte *encodeBuffer; + bool motionJpeg; +} videoFrameCommand_t; + +typedef struct +{ + int commandId; + + GLboolean rgba[4]; +} colorMaskCommand_t; + +typedef struct +{ + int commandId; +} clearDepthCommand_t; + +typedef struct { + int commandId; + int map; + int cubeSide; +} capShadowmapCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; +} postProcessCommand_t; + +typedef struct { + int commandId; +} exportCubemapsCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS, + RC_SCREENSHOT, + RC_VIDEOFRAME, + RC_COLORMASK, + RC_CLEARDEPTH, + RC_CAPSHADOWMAP, + RC_POSTPROCESS, + RC_EXPORT_CUBEMAPS +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc +#define MAX_POLYS 600 +#define MAX_POLYVERTS 3000 + +// all of the information needed by the back end must be +// contained in a backEndData_t +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; + dlight_t dlights[MAX_DLIGHTS]; + trRefEntity_t entities[MAX_REFENTITIES]; + srfPoly_t *polys;//[MAX_POLYS]; + polyVert_t *polyVerts;//[MAX_POLYVERTS]; + pshadow_t pshadows[MAX_CALC_PSHADOWS]; + renderCommandList_t commands; +} backEndData_t; + +extern int max_polys; +extern int max_polyverts; + +extern backEndData_t *backEndData; // the second one may not be allocated + + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_IssuePendingRenderCommands( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); +void R_AddCapShadowmapCmd( int dlight, int cubeSide ); +void R_AddPostProcessCmd (void); + +void RE_SetColor( const float *rgba ); +void RE_SetClipRegion( const float *region ); +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, + unsigned char *image_buffer, int padding); +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding); +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, bool motionJpeg ); + + +#endif //TR_LOCAL_H diff --git a/src/renderergl2/tr_main.cpp b/src/renderergl2/tr_main.cpp new file mode 100644 index 0000000..d9eb3ee --- /dev/null +++ b/src/renderergl2/tr_main.cpp @@ -0,0 +1,2669 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_main.c -- main control flow for each frame + +#include "tr_local.h" + +#include // memcpy + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +refimport_t ri; + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +/* +================ +R_CompareVert +================ +*/ +bool R_CompareVert(srfVert_t * v1, srfVert_t * v2, bool checkST) +{ + int i; + + for(i = 0; i < 3; i++) + { + if(floor(v1->xyz[i] + 0.1) != floor(v2->xyz[i] + 0.1)) + { + return false; + } + + if(checkST && ((v1->st[0] != v2->st[0]) || (v1->st[1] != v2->st[1]))) + { + return false; + } + } + + return true; +} + +/* +http://www.terathon.com/code/tangent.html +*/ +void R_CalcTexDirs(vec3_t sdir, vec3_t tdir, const vec3_t v1, const vec3_t v2, + const vec3_t v3, const vec2_t w1, const vec2_t w2, const vec2_t w3) +{ + float x1, x2, y1, y2, z1, z2; + float s1, s2, t1, t2, r; + + x1 = v2[0] - v1[0]; + x2 = v3[0] - v1[0]; + y1 = v2[1] - v1[1]; + y2 = v3[1] - v1[1]; + z1 = v2[2] - v1[2]; + z2 = v3[2] - v1[2]; + + s1 = w2[0] - w1[0]; + s2 = w3[0] - w1[0]; + t1 = w2[1] - w1[1]; + t2 = w3[1] - w1[1]; + + r = s1 * t2 - s2 * t1; + if (r) r = 1.0f / r; + + VectorSet(sdir, (t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); + VectorSet(tdir, (s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); +} + +/* +============= +R_CalcTangentSpace + +Lengyel, Eric. �Computing Tangent Space Basis Vectors for an Arbitrary Mesh�. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/src/tangent.html +============= +*/ +vec_t R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, const vec3_t normal, const vec3_t sdir, const vec3_t tdir) +{ + vec3_t n_cross_t; + vec_t n_dot_t, handedness; + + // Gram-Schmidt orthogonalize + n_dot_t = DotProduct(normal, sdir); + VectorMA(sdir, -n_dot_t, normal, tangent); + VectorNormalize(tangent); + + // Calculate handedness + CrossProduct(normal, sdir, n_cross_t); + handedness = (DotProduct(n_cross_t, tdir) < 0.0f) ? -1.0f : 1.0f; + + // Calculate orthogonal bitangent, if necessary + if (bitangent) + CrossProduct(normal, tangent, bitangent); + + return handedness; +} + +bool R_CalcTangentVectors(srfVert_t * dv[3]) +{ + int i; + float bb, s, t; + vec3_t bary; + + + /* calculate barycentric basis for the triangle */ + bb = (dv[1]->st[0] - dv[0]->st[0]) * (dv[2]->st[1] - dv[0]->st[1]) - (dv[2]->st[0] - dv[0]->st[0]) * (dv[1]->st[1] - dv[0]->st[1]); + if(fabs(bb) < 0.00000001f) + return false; + + /* do each vertex */ + for(i = 0; i < 3; i++) + { + vec4_t tangent; + vec3_t normal, bitangent, nxt; + + // calculate s tangent vector + s = dv[i]->st[0] + 10.0f; + t = dv[i]->st[1]; + bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb; + bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb; + bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb; + + tangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0]; + tangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1]; + tangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2]; + + VectorSubtract(tangent, dv[i]->xyz, tangent); + VectorNormalize(tangent); + + // calculate t tangent vector + s = dv[i]->st[0]; + t = dv[i]->st[1] + 10.0f; + bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb; + bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb; + bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb; + + bitangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0]; + bitangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1]; + bitangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2]; + + VectorSubtract(bitangent, dv[i]->xyz, bitangent); + VectorNormalize(bitangent); + + // store bitangent handedness + R_VaoUnpackNormal(normal, dv[i]->normal); + CrossProduct(normal, tangent, nxt); + tangent[3] = (DotProduct(nxt, bitangent) < 0.0f) ? -1.0f : 1.0f; + + R_VaoPackTangent(dv[i]->tangent, tangent); + + // debug code + //% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i, + //% stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] ); + } + + return true; +} + + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox(vec3_t localBounds[2]) { +#if 0 + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // transform into world space + for (i = 0 ; i < 8 ; i++) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + VectorCopy( tr.orientation.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.orientation.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.orientation.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.orientation.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for (i = 0 ; i < 4 ; i++) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for (j = 0 ; j < 8 ; j++) { + dists[j] = DotProduct(transformed[j], frust->normal); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +#else + int j; + vec3_t transformed; + vec3_t v; + vec3_t worldBounds[2]; + + if(r_nocull->integer) + { + return CULL_CLIP; + } + + // transform into world space + ClearBounds(worldBounds[0], worldBounds[1]); + + for(j = 0; j < 8; j++) + { + v[0] = localBounds[j & 1][0]; + v[1] = localBounds[(j >> 1) & 1][1]; + v[2] = localBounds[(j >> 2) & 1][2]; + + R_LocalPointToWorld(v, transformed); + + AddPointToBounds(transformed, worldBounds[0], worldBounds[1]); + } + + return R_CullBox(worldBounds); +#endif +} + +/* +================= +R_CullBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullBox(vec3_t worldBounds[2]) { + int i; + cplane_t *frust; + bool anyClip; + int r, numPlanes; + + numPlanes = (tr.viewParms.flags & VPF_FARPLANEFRUSTUM) ? 5 : 4; + + // check against frustum planes + anyClip = false; + for(i = 0; i < numPlanes; i++) + { + frust = &tr.viewParms.frustum[i]; + + r = BoxOnPlaneSide(worldBounds[0], worldBounds[1], frust); + + if(r == 2) + { + // completely outside frustum + return CULL_OUT; + } + if(r == 3) + { + anyClip = true; + } + } + + if(!anyClip) + { + // completely inside frustum + return CULL_IN; + } + + // partially clipped + return CULL_CLIP; +} + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( const vec3_t pt, float radius ) +{ + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadiusEx( const vec3_t pt, float radius, const cplane_t* frustum, int numPlanes ) +{ + int i; + float dist; + const cplane_t *frust; + bool mightBeClipped = false; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // check against frustum planes + for (i = 0 ; i < numPlanes ; i++) + { + frust = &frustum[i]; + + dist = DotProduct( pt, frust->normal) - frust->dist; + if ( dist < -radius ) + { + return CULL_OUT; + } + else if ( dist <= radius ) + { + mightBeClipped = true; + } + } + + if ( mightBeClipped ) + { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( const vec3_t pt, float radius ) +{ + return R_CullPointAndRadiusEx(pt, radius, tr.viewParms.frustum, (tr.viewParms.flags & VPF_FARPLANEFRUSTUM) ? 5 : 4); +} + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.orientation.axis[0][0] + local[1] * tr.orientation.axis[1][0] + local[2] * tr.orientation.axis[2][0]; + world[1] = local[0] * tr.orientation.axis[0][1] + local[1] * tr.orientation.axis[1][1] + local[2] * tr.orientation.axis[2][1]; + world[2] = local[0] * tr.orientation.axis[0][2] + local[1] * tr.orientation.axis[1][2] + local[2] * tr.orientation.axis[2][2]; +} + +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.orientation.axis[0][0] + local[1] * tr.orientation.axis[1][0] + local[2] * tr.orientation.axis[2][0] + tr.orientation.origin[0]; + world[1] = local[0] * tr.orientation.axis[0][1] + local[1] * tr.orientation.axis[1][1] + local[2] * tr.orientation.axis[2][1] + tr.orientation.origin[1]; + world[2] = local[0] * tr.orientation.axis[0][2] + local[1] * tr.orientation.axis[1][2] + local[2] * tr.orientation.axis[2][2] + tr.orientation.origin[2]; +} + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal (const vec3_t world, vec3_t local) { + local[0] = DotProduct(world, tr.orientation.axis[0]); + local[1] = DotProduct(world, tr.orientation.axis[1]); + local[2] = DotProduct(world, tr.orientation.axis[2]); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth; + window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *orientation ) { + float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *orientation = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, orientation->origin ); + + VectorCopy( ent->e.axis[0], orientation->axis[0] ); + VectorCopy( ent->e.axis[1], orientation->axis[1] ); + VectorCopy( ent->e.axis[2], orientation->axis[2] ); + + glMatrix[0] = orientation->axis[0][0]; + glMatrix[4] = orientation->axis[1][0]; + glMatrix[8] = orientation->axis[2][0]; + glMatrix[12] = orientation->origin[0]; + + glMatrix[1] = orientation->axis[0][1]; + glMatrix[5] = orientation->axis[1][1]; + glMatrix[9] = orientation->axis[2][1]; + glMatrix[13] = orientation->origin[1]; + + glMatrix[2] = orientation->axis[0][2]; + glMatrix[6] = orientation->axis[1][2]; + glMatrix[10] = orientation->axis[2][2]; + glMatrix[14] = orientation->origin[2]; + + glMatrix[3] = 0; + glMatrix[7] = 0; + glMatrix[11] = 0; + glMatrix[15] = 1; + + Mat4Copy(glMatrix, orientation->transformMatrix); + myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, orientation->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->orientation.origin, orientation->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + } else { + axisLength = 1.0f; + } + + orientation->viewOrigin[0] = DotProduct( delta, orientation->axis[0] ) * axisLength; + orientation->viewOrigin[1] = DotProduct( delta, orientation->axis[1] ) * axisLength; + orientation->viewOrigin[2] = DotProduct( delta, orientation->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer (void) +{ + float viewerMatrix[16]; + vec3_t origin; + + Com_Memset (&tr.orientation, 0, sizeof(tr.orientation)); + tr.orientation.axis[0][0] = 1; + tr.orientation.axis[1][1] = 1; + tr.orientation.axis[2][2] = 1; + VectorCopy (tr.viewParms.orientation.origin, tr.orientation.viewOrigin); + + // transform by the camera placement + VectorCopy( tr.viewParms.orientation.origin, origin ); + + viewerMatrix[0] = tr.viewParms.orientation.axis[0][0]; + viewerMatrix[4] = tr.viewParms.orientation.axis[0][1]; + viewerMatrix[8] = tr.viewParms.orientation.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.orientation.axis[1][0]; + viewerMatrix[5] = tr.viewParms.orientation.axis[1][1]; + viewerMatrix[9] = tr.viewParms.orientation.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.orientation.axis[2][0]; + viewerMatrix[6] = tr.viewParms.orientation.axis[2][1]; + viewerMatrix[10] = tr.viewParms.orientation.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.orientation.modelMatrix ); + + tr.viewParms.world = tr.orientation; + +} + +/* +** SetFarClip +*/ +static void R_SetFarClip( void ) +{ + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + tr.viewParms.zFar = 2048; + return; + } + + // + // set far clipping planes dynamically + // + farthestCornerDistance = 0; + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + vec3_t vecTo; + float distance; + + if ( i & 1 ) + { + v[0] = tr.viewParms.visBounds[0][0]; + } + else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) + { + v[1] = tr.viewParms.visBounds[0][1]; + } + else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) + { + v[2] = tr.viewParms.visBounds[0][2]; + } + else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + VectorSubtract( v, tr.viewParms.orientation.origin, vecTo ); + + distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2]; + + if ( distance > farthestCornerDistance ) + { + farthestCornerDistance = distance; + } + } + tr.viewParms.zFar = sqrt( farthestCornerDistance ); +} + +/* +================= +R_SetupFrustum + +Set up the culling frustum planes for the current view using the results we got from computing the first two rows of +the projection matrix. +================= +*/ +void R_SetupFrustum (viewParms_t *dest, float xmin, float xmax, float ymax, float zProj, float zFar, float stereoSep) +{ + vec3_t ofsorigin; + float oppleg, adjleg, length; + int i; + + if(stereoSep == 0 && xmin == -xmax) + { + // symmetric case can be simplified + VectorCopy(dest->orientation.origin, ofsorigin); + + length = sqrt(xmax * xmax + zProj * zProj); + oppleg = xmax / length; + adjleg = zProj / length; + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, adjleg, dest->orientation.axis[1], dest->frustum[0].normal); + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -adjleg, dest->orientation.axis[1], dest->frustum[1].normal); + } + else + { + // In stereo rendering, due to the modification of the projection matrix, dest->orientation.origin is not the + // actual origin that we're rendering so offset the tip of the view pyramid. + VectorMA(dest->orientation.origin, stereoSep, dest->orientation.axis[1], ofsorigin); + + oppleg = xmax + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->orientation.axis[0], oppleg / length, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, zProj / length, dest->orientation.axis[1], dest->frustum[0].normal); + + oppleg = xmin + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->orientation.axis[0], -oppleg / length, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -zProj / length, dest->orientation.axis[1], dest->frustum[1].normal); + } + + length = sqrt(ymax * ymax + zProj * zProj); + oppleg = ymax / length; + adjleg = zProj / length; + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[2].normal); + VectorMA(dest->frustum[2].normal, adjleg, dest->orientation.axis[2], dest->frustum[2].normal); + + VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[3].normal); + VectorMA(dest->frustum[3].normal, -adjleg, dest->orientation.axis[2], dest->frustum[3].normal); + + for (i=0 ; i<4 ; i++) { + dest->frustum[i].type = PLANE_NON_AXIAL; + dest->frustum[i].dist = DotProduct (ofsorigin, dest->frustum[i].normal); + SetPlaneSignbits( &dest->frustum[i] ); + } + + if (zFar != 0.0f) + { + vec3_t farpoint; + + VectorMA(ofsorigin, zFar, dest->orientation.axis[0], farpoint); + VectorScale(dest->orientation.axis[0], -1.0f, dest->frustum[4].normal); + + dest->frustum[4].type = PLANE_NON_AXIAL; + dest->frustum[4].dist = DotProduct (farpoint, dest->frustum[4].normal); + SetPlaneSignbits( &dest->frustum[4] ); + dest->flags |= VPF_FARPLANEFRUSTUM; + } +} + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, bool computeFrustum) +{ + float xmin, xmax, ymin, ymax; + float width, height, stereoSep = r_stereoSeparation->value; + + /* + * offset the view origin of the viewer for stereo rendering + * by setting the projection matrix appropriately. + */ + + if(stereoSep != 0) + { + if(dest->stereoFrame == STEREO_LEFT) + stereoSep = zProj / stereoSep; + else if(dest->stereoFrame == STEREO_RIGHT) + stereoSep = zProj / -stereoSep; + else + stereoSep = 0; + } + + ymax = zProj * tan(dest->fovY * M_PI / 360.0f); + ymin = -ymax; + + xmax = zProj * tan(dest->fovX * M_PI / 360.0f); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + + dest->projectionMatrix[0] = 2 * zProj / width; + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = (xmax + xmin + 2 * stereoSep) / width; + dest->projectionMatrix[12] = 2 * zProj * stereoSep / width; + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 * zProj / height; + dest->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + dest->projectionMatrix[13] = 0; + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = -1; + dest->projectionMatrix[15] = 0; + + // Now that we have all the data for the projection matrix we can also setup the view frustum. + if(computeFrustum) + R_SetupFrustum(dest, xmin, xmax, ymax, zProj, zFar, stereoSep); +} + +/* +=============== +R_SetupProjectionZ + +Sets the z-component transformation part in the projection matrix +=============== +*/ +void R_SetupProjectionZ(viewParms_t *dest) +{ + float zNear, zFar, depth; + + zNear = r_znear->value; + zFar = dest->zFar; + + depth = zFar - zNear; + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = -( zFar + zNear ) / depth; + dest->projectionMatrix[14] = -2 * zFar * zNear / depth; + + if (dest->isPortal) + { + float plane[4]; + float plane2[4]; + vec4_t q, c; + + // transform portal plane into camera space + plane[0] = dest->portalPlane.normal[0]; + plane[1] = dest->portalPlane.normal[1]; + plane[2] = dest->portalPlane.normal[2]; + plane[3] = dest->portalPlane.dist; + + plane2[0] = -DotProduct (dest->orientation.axis[1], plane); + plane2[1] = DotProduct (dest->orientation.axis[2], plane); + plane2[2] = -DotProduct (dest->orientation.axis[0], plane); + plane2[3] = DotProduct (plane, dest->orientation.origin) - plane[3]; + + // Lengyel, Eric. "Modifying the Projection Matrix to Perform Oblique Near-plane Clipping". + // Terathon Software 3D Graphics Library, 2004. http://www.terathon.com/code/oblique.html + q[0] = (SGN(plane2[0]) + dest->projectionMatrix[8]) / dest->projectionMatrix[0]; + q[1] = (SGN(plane2[1]) + dest->projectionMatrix[9]) / dest->projectionMatrix[5]; + q[2] = -1.0f; + q[3] = (1.0f + dest->projectionMatrix[10]) / dest->projectionMatrix[14]; + + VectorScale4(plane2, 2.0f / DotProduct4(plane2, q), c); + + dest->projectionMatrix[2] = c[0]; + dest->projectionMatrix[6] = c[1]; + dest->projectionMatrix[10] = c[2] + 1.0f; + dest->projectionMatrix[14] = c[3]; + + } + +} + +/* +=============== +R_SetupProjectionOrtho +=============== +*/ +void R_SetupProjectionOrtho(viewParms_t *dest, vec3_t viewBounds[2]) +{ + float xmin, xmax, ymin, ymax, znear, zfar; + //viewParms_t *dest = &tr.viewParms; + int i; + vec3_t pop; + + // Quake3: Projection: + // + // Z X Y Z + // | / | / + // |/ |/ + // Y--+ +--X + + xmin = viewBounds[0][1]; + xmax = viewBounds[1][1]; + ymin = -viewBounds[1][2]; + ymax = -viewBounds[0][2]; + znear = viewBounds[0][0]; + zfar = viewBounds[1][0]; + + dest->projectionMatrix[0] = 2 / (xmax - xmin); + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = 0; + dest->projectionMatrix[12] = (xmax + xmin) / (xmax - xmin); + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 / (ymax - ymin); + dest->projectionMatrix[9] = 0; + dest->projectionMatrix[13] = (ymax + ymin) / (ymax - ymin); + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = -2 / (zfar - znear); + dest->projectionMatrix[14] = -(zfar + znear) / (zfar - znear); + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = 0; + dest->projectionMatrix[15] = 1; + + VectorScale(dest->orientation.axis[1], 1.0f, dest->frustum[0].normal); + VectorMA(dest->orientation.origin, viewBounds[0][1], dest->frustum[0].normal, pop); + dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal); + + VectorScale(dest->orientation.axis[1], -1.0f, dest->frustum[1].normal); + VectorMA(dest->orientation.origin, -viewBounds[1][1], dest->frustum[1].normal, pop); + dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal); + + VectorScale(dest->orientation.axis[2], 1.0f, dest->frustum[2].normal); + VectorMA(dest->orientation.origin, viewBounds[0][2], dest->frustum[2].normal, pop); + dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal); + + VectorScale(dest->orientation.axis[2], -1.0f, dest->frustum[3].normal); + VectorMA(dest->orientation.origin, -viewBounds[1][2], dest->frustum[3].normal, pop); + dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal); + + VectorScale(dest->orientation.axis[0], -1.0f, dest->frustum[4].normal); + VectorMA(dest->orientation.origin, -viewBounds[1][0], dest->frustum[4].normal, pop); + dest->frustum[4].dist = DotProduct(pop, dest->frustum[4].normal); + + for (i = 0; i < 5; i++) + { + dest->frustum[i].type = PLANE_NON_AXIAL; + SetPlaneSignbits (&dest->frustum[i]); + } + + dest->flags |= VPF_FARPLANEFRUSTUM; +} + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(local, surface->axis[i]); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(in, surface->axis[i]); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) { + srfBspSurface_t *tri; + srfPoly_t *poly; + srfVert_t *v1, *v2, *v3; + vec4_t plane4; + + if (!surfType) { + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } + switch (*surfType) { + case SF_FACE: + *plane = ((srfBspSurface_t *)surfType)->cullPlane; + return; + case SF_TRIANGLES: + tri = (srfBspSurface_t *)surfType; + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns true if it should be mirrored +================= +*/ +bool R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, bool *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != REFENTITYNUM_WORLD ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.orientation ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.orientation.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orientation.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = true; + return true; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.oldframe ) { + // if a speed is specified + if ( e->e.frame ) { + // continuous rotate + d = (tr.refdef.time/1000.0f) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else { + // bobbing rotate, with skinNum being the rotation offset + d = sin( tr.refdef.time * 0.003f ); + d = e->e.skinNum + d * 4; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + } + else if ( e->e.skinNum ) { + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = false; + return true; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" ); + + return false; +} + +static bool IsMirror( const drawSurf_t *drawSurf, int entityNum ) +{ + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != REFENTITYNUM_WORLD ) + { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.orientation ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.orientation.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orientation.origin ); + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) + { + return true; + } + + return false; + } + return false; +} + +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static bool SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 100000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + int pshadowed; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed ); + RB_BeginSurface( shader, fogNum, drawSurf->cubemapIndex); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + + assert( tess.numVertexes < 128 ); + + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.orientation.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) + { + pointFlags |= (1 << (j*2)); + } + else if ( clip[j] <= -clip[3] ) + { + pointFlags |= ( 1 << (j*2+1)); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) + { + return true; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal, tNormal; + + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.orientation.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) + { + shortest = len; + } + + R_VaoUnpackNormal(tNormal, tess.normal[tess.indexes[i]]); + + if ( DotProduct( normal, tNormal ) >= 0 ) + { + numTriangles--; + } + } + if ( !numTriangles ) + { + return true; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) + { + return false; + } + + if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) ) + { + return true; + } + + return false; +} + +/* +======================== +R_MirrorViewBySurface + +Returns true if another view has been rendered +======================== +*/ +bool R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if (tr.viewParms.isPortal) { + ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" ); + return false; + } + + if ( r_noportals->integer || (r_fastsky->integer == 1) ) { + return false; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return false; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = true; + newParms.zFar = 0.0f; + newParms.flags &= ~VPF_FARPLANEFRUSTUM; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return false; // bad portal, no portalentity + } + + // Never draw viewmodels in portal or mirror views. + newParms.flags |= VPF_NOVIEWMODEL; + + R_MirrorPoint (oldParms.orientation.origin, &surface, &camera, newParms.orientation.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector (oldParms.orientation.axis[0], &surface, &camera, newParms.orientation.axis[0]); + R_MirrorVector (oldParms.orientation.axis[1], &surface, &camera, newParms.orientation.axis[1]); + R_MirrorVector (oldParms.orientation.axis[2], &surface, &camera, newParms.orientation.axis[2]); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView (&newParms); + + tr.viewParms = oldParms; + + return true; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + if ( ent->e.renderfx & RF_CROSSHAIR ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +=============== +R_Radix +=============== +*/ +static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest ) +{ + int count[ 256 ] = { 0 }; + int index[ 256 ]; + int i; + unsigned char *sortKey = NULL; + unsigned char *end = NULL; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + end = sortKey + ( size * sizeof( drawSurf_t ) ); + for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) ) + ++count[ *sortKey ]; + + index[ 0 ] = 0; + + for( i = 1; i < 256; ++i ) + index[ i ] = index[ i - 1 ] + count[ i - 1 ]; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) ) + dest[ index[ *sortKey ]++ ] = source[ i ]; +} + +/* +=============== +R_RadixSort + +Radix sort with 4 byte size buckets +=============== +*/ +static void R_RadixSort( drawSurf_t *source, int size ) +{ + static drawSurf_t scratch[ MAX_DRAWSURFS ]; +#ifdef Q3_LITTLE_ENDIAN + R_Radix( 0, size, source, scratch ); + R_Radix( 1, size, scratch, source ); + R_Radix( 2, size, source, scratch ); + R_Radix( 3, size, scratch, source ); +#else + R_Radix( 3, size, source, scratch ); + R_Radix( 2, size, scratch, source ); + R_Radix( 1, size, source, scratch ); + R_Radix( 0, size, scratch, source ); +#endif //Q3_LITTLE_ENDIAN +} + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap, int pshadowMap, int cubemap ) { + int index; + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) + | ((int)pshadowMap << QSORT_PSHADOW_SHIFT) | (int)dlightMap; + tr.refdef.drawSurfs[index].cubemapIndex = cubemap; + tr.refdef.drawSurfs[index].surface = surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap, int *pshadowMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ]; + *entityNum = ( sort >> QSORT_REFENTITYNUM_SHIFT ) & REFENTITYNUM_MASK; + *pshadowMap = (sort >> QSORT_PSHADOW_SHIFT ) & 1; + *dlightMap = sort & 1; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + int pshadowed; + int i; + + //ri.Printf(PRINT_ALL, "firstDrawSurf %d numDrawSurfs %d\n", (int)(drawSurfs - tr.refdef.drawSurfs), numDrawSurfs); + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // sort the drawsurfs by sort type, then orientation, then shader + R_RadixSort( drawSurfs, numDrawSurfs ); + + // skip pass through drawing if rendering a shadow map + if (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW)) + { + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +static void R_AddEntitySurface (int entityNum) +{ + trRefEntity_t *ent; + shader_t *shader; + + tr.currentEntityNum = entityNum; + + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + + ent->needDlights = false; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( (ent->e.renderfx & RF_FIRST_PERSON) && (tr.viewParms.flags & VPF_NOVIEWMODEL)) { + return; + } + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_BEAM: + case RT_LIGHTNING: + case RT_RAIL_CORE: + case RT_RAIL_RINGS: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + return; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0, 0, 0 /*cubeMap*/ ); + break; + + case RT_MODEL: + // we must set up parts of tr.or for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.orientation ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if (!tr.currentModel) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0, 0 /*cubeMap*/ ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + case MOD_MDR: + R_MDRAddAnimSurfaces( ent ); + break; + case MOD_IQM: + R_AddIQMSurfaces( ent ); + break; + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; + case MOD_BAD: // null model axis + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + break; + } + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0, 0 ); + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces (void) { + int i; + + if ( !r_drawentities->integer ) { + return; + } + + for ( i = 0; i < tr.refdef.num_entities; i++) + R_AddEntitySurface(i); +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + + // dynamically compute far clip plane distance + if (!(tr.viewParms.flags & VPF_SHADOWMAP)) + { + R_SetFarClip(); + } + + // we know the size of the clipping volume. Now set the rest of the projection matrix. + R_SetupProjectionZ (&tr.viewParms); + + R_AddEntitySurfaces (); +} + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + // FIXME: implement this +#if 0 + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color&1, (color>>1)&1, (color>>2)&1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // draw wireframe outline + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglDepthRange( 0, 0 ); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + qglDepthRange( 0, 1 ); +#endif +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + R_IssuePendingRenderCommands(); + + GL_BindToTMU(tr.whiteImage, TB_COLORMAP); + GL_Cull( CT_FRONT_SIDED ); + ri.CM_DrawDebugSurface( R_DebugPolygon ); +} + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView (viewParms_t *parms) { + int firstDrawSurf; + int numDrawSurfs; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupProjection(&tr.viewParms, r_zproj->value, tr.viewParms.zFar, true); + + R_GenerateDrawSurfs(); + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + numDrawSurfs = tr.refdef.numDrawSurfs; + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; + } + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_DebugGraphics(); +} + + +void R_RenderDlightCubemaps(const refdef_t *fd) +{ + int i; + + for (i = 0; i < tr.refdef.num_dlights; i++) + { + viewParms_t shadowParms; + int j; + + // use previous frame to determine visible dlights + if ((1 << i) & tr.refdef.dlightMask) + continue; + + Com_Memset( &shadowParms, 0, sizeof( shadowParms ) ); + + shadowParms.viewportX = tr.refdef.x; + shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + PSHADOW_MAP_SIZE ); + shadowParms.viewportWidth = PSHADOW_MAP_SIZE; + shadowParms.viewportHeight = PSHADOW_MAP_SIZE; + shadowParms.isPortal = false; + shadowParms.isMirror = true; // because it is + + shadowParms.fovX = 90; + shadowParms.fovY = 90; + + shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW | VPF_NOVIEWMODEL; + shadowParms.zFar = tr.refdef.dlights[i].radius; + + VectorCopy( tr.refdef.dlights[i].origin, shadowParms.orientation.origin ); + + for (j = 0; j < 6; j++) + { + switch(j) + { + case 0: + // -X + VectorSet( shadowParms.orientation.axis[0], -1, 0, 0); + VectorSet( shadowParms.orientation.axis[1], 0, 0, -1); + VectorSet( shadowParms.orientation.axis[2], 0, 1, 0); + break; + case 1: + // +X + VectorSet( shadowParms.orientation.axis[0], 1, 0, 0); + VectorSet( shadowParms.orientation.axis[1], 0, 0, 1); + VectorSet( shadowParms.orientation.axis[2], 0, 1, 0); + break; + case 2: + // -Y + VectorSet( shadowParms.orientation.axis[0], 0, -1, 0); + VectorSet( shadowParms.orientation.axis[1], 1, 0, 0); + VectorSet( shadowParms.orientation.axis[2], 0, 0, -1); + break; + case 3: + // +Y + VectorSet( shadowParms.orientation.axis[0], 0, 1, 0); + VectorSet( shadowParms.orientation.axis[1], 1, 0, 0); + VectorSet( shadowParms.orientation.axis[2], 0, 0, 1); + break; + case 4: + // -Z + VectorSet( shadowParms.orientation.axis[0], 0, 0, -1); + VectorSet( shadowParms.orientation.axis[1], 1, 0, 0); + VectorSet( shadowParms.orientation.axis[2], 0, 1, 0); + break; + case 5: + // +Z + VectorSet( shadowParms.orientation.axis[0], 0, 0, 1); + VectorSet( shadowParms.orientation.axis[1], -1, 0, 0); + VectorSet( shadowParms.orientation.axis[2], 0, 1, 0); + break; + } + + R_RenderView(&shadowParms); + R_AddCapShadowmapCmd( i, j ); + } + } +} + + +void R_RenderPshadowMaps(const refdef_t *fd) +{ + viewParms_t shadowParms; + int i; + + // first, make a list of shadows + for ( i = 0; i < tr.refdef.num_entities; i++) + { + trRefEntity_t *ent = &tr.refdef.entities[i]; + + if((ent->e.renderfx & (RF_FIRST_PERSON | RF_NOSHADOW))) + continue; + + //if((ent->e.renderfx & RF_THIRD_PERSON)) + //continue; + + if (ent->e.reType == RT_MODEL) + { + model_t *model = R_GetModelByHandle( ent->e.hModel ); + pshadow_t shadow; + float radius = 0.0f; + float scale = 1.0f; + vec3_t diff; + int j; + + if (!model) + continue; + + if (ent->e.nonNormalizedAxes) + { + scale = VectorLength( ent->e.axis[0] ); + } + + switch (model->type) + { + case MOD_MESH: + { + mdvFrame_t *frame = &model->mdv[0]->frames[ent->e.frame]; + + radius = frame->radius * scale; + } + break; + + case MOD_MDR: + { + // FIXME: never actually tested this + mdrHeader_t *header = (mdrHeader_t*)model->modelData; + int frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + mdrFrame_t *frame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + + radius = frame->radius; + } + break; + case MOD_IQM: + { + // FIXME: never actually tested this + iqmData_t *data = (iqmData_t*)model->modelData; + vec3_t diag; + float *framebounds; + + framebounds = data->bounds + 6*ent->e.frame; + VectorSubtract( framebounds+3, framebounds, diag ); + radius = 0.5f * VectorLength( diag ); + } + break; + + default: + break; + } + + if (!radius) + continue; + + // Cull entities that are behind the viewer by more than lightRadius + VectorSubtract(ent->e.origin, fd->vieworg, diff); + if (DotProduct(diff, fd->viewaxis[0]) < -r_pshadowDist->value) + continue; + + memset(&shadow, 0, sizeof(shadow)); + + shadow.numEntities = 1; + shadow.entityNums[0] = i; + shadow.viewRadius = radius; + shadow.lightRadius = r_pshadowDist->value; + VectorCopy(ent->e.origin, shadow.viewOrigin); + shadow.sort = DotProduct(diff, diff) / (radius * radius); + VectorCopy(ent->e.origin, shadow.entityOrigins[0]); + shadow.entityRadiuses[0] = radius; + + for (j = 0; j < MAX_CALC_PSHADOWS; j++) + { + pshadow_t swap; + + if (j + 1 > tr.refdef.num_pshadows) + { + tr.refdef.num_pshadows = j + 1; + tr.refdef.pshadows[j] = shadow; + break; + } + + // sort shadows by distance from camera divided by radius + // FIXME: sort better + if (tr.refdef.pshadows[j].sort <= shadow.sort) + continue; + + swap = tr.refdef.pshadows[j]; + tr.refdef.pshadows[j] = shadow; + shadow = swap; + } + } + } + + // next, merge touching pshadows + for ( i = 0; i < tr.refdef.num_pshadows; i++) + { + pshadow_t *ps1 = &tr.refdef.pshadows[i]; + int j; + + for (j = i + 1; j < tr.refdef.num_pshadows; j++) + { + pshadow_t *ps2 = &tr.refdef.pshadows[j]; + int k; + bool touch; + + if (ps1->numEntities == 8) + break; + + touch = false; + if (SpheresIntersect(ps1->viewOrigin, ps1->viewRadius, ps2->viewOrigin, ps2->viewRadius)) + { + for (k = 0; k < ps1->numEntities; k++) + { + if (SpheresIntersect(ps1->entityOrigins[k], ps1->entityRadiuses[k], ps2->viewOrigin, ps2->viewRadius)) + { + touch = true; + break; + } + } + } + + if (touch) + { + vec3_t newOrigin; + float newRadius; + + BoundingSphereOfSpheres(ps1->viewOrigin, ps1->viewRadius, ps2->viewOrigin, ps2->viewRadius, newOrigin, &newRadius); + VectorCopy(newOrigin, ps1->viewOrigin); + ps1->viewRadius = newRadius; + + ps1->entityNums[ps1->numEntities] = ps2->entityNums[0]; + VectorCopy(ps2->viewOrigin, ps1->entityOrigins[ps1->numEntities]); + ps1->entityRadiuses[ps1->numEntities] = ps2->viewRadius; + + ps1->numEntities++; + + for (k = j; k < tr.refdef.num_pshadows - 1; k++) + { + tr.refdef.pshadows[k] = tr.refdef.pshadows[k + 1]; + } + + j--; + tr.refdef.num_pshadows--; + } + } + } + + // cap number of drawn pshadows + if (tr.refdef.num_pshadows > MAX_DRAWN_PSHADOWS) + { + tr.refdef.num_pshadows = MAX_DRAWN_PSHADOWS; + } + + // next, fill up the rest of the shadow info + for ( i = 0; i < tr.refdef.num_pshadows; i++) + { + pshadow_t *shadow = &tr.refdef.pshadows[i]; + vec3_t up; + vec3_t ambientLight, directedLight, lightDir; + + VectorSet(lightDir, 0.57735f, 0.57735f, 0.57735f); +#if 1 + R_LightForPoint(shadow->viewOrigin, ambientLight, directedLight, lightDir); + + // sometimes there's no light + if (DotProduct(lightDir, lightDir) < 0.9f) + VectorSet(lightDir, 0.0f, 0.0f, 1.0f); +#endif + + if (shadow->viewRadius * 3.0f > shadow->lightRadius) + { + shadow->lightRadius = shadow->viewRadius * 3.0f; + } + + VectorMA(shadow->viewOrigin, shadow->viewRadius, lightDir, shadow->lightOrigin); + + // make up a projection, up doesn't matter + VectorScale(lightDir, -1.0f, shadow->lightViewAxis[0]); + VectorSet(up, 0, 0, -1); + + if ( fabsf(DotProduct(up, shadow->lightViewAxis[0])) > 0.9f ) + { + VectorSet(up, -1, 0, 0); + } + + CrossProduct(shadow->lightViewAxis[0], up, shadow->lightViewAxis[1]); + VectorNormalize(shadow->lightViewAxis[1]); + CrossProduct(shadow->lightViewAxis[0], shadow->lightViewAxis[1], shadow->lightViewAxis[2]); + + VectorCopy(shadow->lightViewAxis[0], shadow->cullPlane.normal); + shadow->cullPlane.dist = DotProduct(shadow->cullPlane.normal, shadow->lightOrigin); + shadow->cullPlane.type = PLANE_NON_AXIAL; + SetPlaneSignbits(&shadow->cullPlane); + } + + // next, render shadowmaps + for ( i = 0; i < tr.refdef.num_pshadows; i++) + { + int firstDrawSurf; + pshadow_t *shadow = &tr.refdef.pshadows[i]; + int j; + + Com_Memset( &shadowParms, 0, sizeof( shadowParms ) ); + + if (glRefConfig.framebufferObject) + { + shadowParms.viewportX = 0; + shadowParms.viewportY = 0; + } + else + { + shadowParms.viewportX = tr.refdef.x; + shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + PSHADOW_MAP_SIZE ); + } + shadowParms.viewportWidth = PSHADOW_MAP_SIZE; + shadowParms.viewportHeight = PSHADOW_MAP_SIZE; + shadowParms.isPortal = false; + shadowParms.isMirror = false; + + shadowParms.fovX = 90; + shadowParms.fovY = 90; + + if (glRefConfig.framebufferObject) + shadowParms.targetFbo = tr.pshadowFbos[i]; + + shadowParms.flags = VPF_DEPTHSHADOW | VPF_NOVIEWMODEL; + shadowParms.zFar = shadow->lightRadius; + + VectorCopy(shadow->lightOrigin, shadowParms.orientation.origin); + + VectorCopy(shadow->lightViewAxis[0], shadowParms.orientation.axis[0]); + VectorCopy(shadow->lightViewAxis[1], shadowParms.orientation.axis[1]); + VectorCopy(shadow->lightViewAxis[2], shadowParms.orientation.axis[2]); + + { + tr.viewCount++; + + tr.viewParms = shadowParms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + { + float xmin, xmax, ymin, ymax, znear, zfar; + viewParms_t *dest = &tr.viewParms; + vec3_t pop; + + xmin = ymin = -shadow->viewRadius; + xmax = ymax = shadow->viewRadius; + znear = 0; + zfar = shadow->lightRadius; + + dest->projectionMatrix[0] = 2 / (xmax - xmin); + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = (xmax + xmin) / (xmax - xmin); + dest->projectionMatrix[12] =0; + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 / (ymax - ymin); + dest->projectionMatrix[9] = ( ymax + ymin ) / (ymax - ymin); // normally 0 + dest->projectionMatrix[13] = 0; + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = 2 / (zfar - znear); + dest->projectionMatrix[14] = 0; + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = 0; + dest->projectionMatrix[15] = 1; + + VectorScale(dest->orientation.axis[1], 1.0f, dest->frustum[0].normal); + VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[0].normal, pop); + dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal); + + VectorScale(dest->orientation.axis[1], -1.0f, dest->frustum[1].normal); + VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[1].normal, pop); + dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal); + + VectorScale(dest->orientation.axis[2], 1.0f, dest->frustum[2].normal); + VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[2].normal, pop); + dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal); + + VectorScale(dest->orientation.axis[2], -1.0f, dest->frustum[3].normal); + VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[3].normal, pop); + dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal); + + VectorScale(dest->orientation.axis[0], -1.0f, dest->frustum[4].normal); + VectorMA(dest->orientation.origin, -shadow->lightRadius, dest->frustum[4].normal, pop); + dest->frustum[4].dist = DotProduct(pop, dest->frustum[4].normal); + + for (j = 0; j < 5; j++) + { + dest->frustum[j].type = PLANE_NON_AXIAL; + SetPlaneSignbits (&dest->frustum[j]); + } + + dest->flags |= VPF_FARPLANEFRUSTUM; + } + + for (j = 0; j < shadow->numEntities; j++) + { + R_AddEntitySurface(shadow->entityNums[j]); + } + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + if (!glRefConfig.framebufferObject) + R_AddCapShadowmapCmd( i, -1 ); + } + } +} + +static float CalcSplit(float n, float f, float i, float m) +{ + return (n * pow(f / n, i / m) + (f - n) * i / m) / 2.0f; +} + + +void R_RenderSunShadowMaps(const refdef_t *fd, int level) +{ + viewParms_t shadowParms; + vec4_t lightDir, lightCol; + vec3_t lightViewAxis[3]; + vec3_t lightOrigin; + float splitZNear, splitZFar, splitBias; + float viewZNear, viewZFar; + vec3_t lightviewBounds[2]; + bool lightViewIndependentOfCameraView = false; + + if (r_forceSun->integer == 2) + { + int scale = 32768; + float angle = (fd->time % scale) / (float)scale * M_PI; + lightDir[0] = cos(angle); + lightDir[1] = sin(35.0f * M_PI / 180.0f); + lightDir[2] = sin(angle) * cos(35.0f * M_PI / 180.0f); + lightDir[3] = 0.0f; + + if (1) //((fd->time % (scale * 2)) < scale) + { + lightCol[0] = + lightCol[1] = + lightCol[2] = CLAMP(sin(angle) * 2.0f, 0.0f, 1.0f) * 2.0f; + lightCol[3] = 1.0f; + } + else + { + lightCol[0] = + lightCol[1] = + lightCol[2] = CLAMP(sin(angle) * 2.0f * 0.1f, 0.0f, 0.1f); + lightCol[3] = 1.0f; + } + + VectorCopy4(lightDir, tr.refdef.sunDir); + VectorCopy4(lightCol, tr.refdef.sunCol); + VectorScale4(lightCol, 0.2f, tr.refdef.sunAmbCol); + } + else + { + VectorCopy4(tr.refdef.sunDir, lightDir); + } + + viewZNear = r_shadowCascadeZNear->value; + viewZFar = r_shadowCascadeZFar->value; + splitBias = r_shadowCascadeZBias->value; + + switch(level) + { + case 0: + default: + //splitZNear = r_znear->value; + //splitZFar = 256; + splitZNear = viewZNear; + splitZFar = CalcSplit(viewZNear, viewZFar, 1, 3) + splitBias; + break; + case 1: + splitZNear = CalcSplit(viewZNear, viewZFar, 1, 3) + splitBias; + splitZFar = CalcSplit(viewZNear, viewZFar, 2, 3) + splitBias; + //splitZNear = 256; + //splitZFar = 896; + break; + case 2: + splitZNear = CalcSplit(viewZNear, viewZFar, 2, 3) + splitBias; + splitZFar = viewZFar; + //splitZNear = 896; + //splitZFar = 3072; + break; + } + + if (level != 3) + VectorCopy(fd->vieworg, lightOrigin); + else + VectorCopy(tr.world->lightGridOrigin, lightOrigin); + + // Make up a projection + VectorScale(lightDir, -1.0f, lightViewAxis[0]); + + if (level == 3 || lightViewIndependentOfCameraView) + { + // Use world up as light view up + VectorSet(lightViewAxis[2], 0, 0, 1); + } + else if (level == 0) + { + // Level 0 tries to use a diamond texture orientation relative to camera view + // Use halfway between camera view forward and left for light view up + VectorAdd(fd->viewaxis[0], fd->viewaxis[1], lightViewAxis[2]); + } + else + { + // Use camera view up as light view up + VectorCopy(fd->viewaxis[2], lightViewAxis[2]); + } + + // Check if too close to parallel to light direction + if (fabsf(DotProduct(lightViewAxis[2], lightViewAxis[0])) > 0.9f) + { + if (level == 3 || lightViewIndependentOfCameraView) + { + // Use world left as light view up + VectorSet(lightViewAxis[2], 0, 1, 0); + } + else if (level == 0) + { + // Level 0 tries to use a diamond texture orientation relative to camera view + // Use halfway between camera view forward and up for light view up + VectorAdd(fd->viewaxis[0], fd->viewaxis[2], lightViewAxis[2]); + } + else + { + // Use camera view left as light view up + VectorCopy(fd->viewaxis[1], lightViewAxis[2]); + } + } + + // clean axes + CrossProduct(lightViewAxis[2], lightViewAxis[0], lightViewAxis[1]); + VectorNormalize(lightViewAxis[1]); + CrossProduct(lightViewAxis[0], lightViewAxis[1], lightViewAxis[2]); + + // Create bounds for light projection using slice of view projection + { + mat4_t lightViewMatrix; + vec4_t point, base, lightViewPoint; + float lx, ly; + + base[3] = 1; + point[3] = 1; + lightViewPoint[3] = 1; + + Mat4View(lightViewAxis, lightOrigin, lightViewMatrix); + + ClearBounds(lightviewBounds[0], lightviewBounds[1]); + + if (level != 3) + { + // add view near plane + lx = splitZNear * tan(fd->fov_x * M_PI / 360.0f); + ly = splitZNear * tan(fd->fov_y * M_PI / 360.0f); + VectorMA(fd->vieworg, splitZNear, fd->viewaxis[0], base); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + + // add view far plane + lx = splitZFar * tan(fd->fov_x * M_PI / 360.0f); + ly = splitZFar * tan(fd->fov_y * M_PI / 360.0f); + VectorMA(fd->vieworg, splitZFar, fd->viewaxis[0], base); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + } + else + { + // use light grid size as level size + // FIXME: could be tighter + vec3_t bounds; + + bounds[0] = tr.world->lightGridSize[0] * tr.world->lightGridBounds[0]; + bounds[1] = tr.world->lightGridSize[1] * tr.world->lightGridBounds[1]; + bounds[2] = tr.world->lightGridSize[2] * tr.world->lightGridBounds[2]; + + point[0] = tr.world->lightGridOrigin[0]; + point[1] = tr.world->lightGridOrigin[1]; + point[2] = tr.world->lightGridOrigin[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0] + bounds[0]; + point[1] = tr.world->lightGridOrigin[1]; + point[2] = tr.world->lightGridOrigin[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0]; + point[1] = tr.world->lightGridOrigin[1] + bounds[1]; + point[2] = tr.world->lightGridOrigin[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0] + bounds[0]; + point[1] = tr.world->lightGridOrigin[1] + bounds[1]; + point[2] = tr.world->lightGridOrigin[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0]; + point[1] = tr.world->lightGridOrigin[1]; + point[2] = tr.world->lightGridOrigin[2] + bounds[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0] + bounds[0]; + point[1] = tr.world->lightGridOrigin[1]; + point[2] = tr.world->lightGridOrigin[2] + bounds[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0]; + point[1] = tr.world->lightGridOrigin[1] + bounds[1]; + point[2] = tr.world->lightGridOrigin[2] + bounds[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + point[0] = tr.world->lightGridOrigin[0] + bounds[0]; + point[1] = tr.world->lightGridOrigin[1] + bounds[1]; + point[2] = tr.world->lightGridOrigin[2] + bounds[2]; + Mat4Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + } + + if (!glRefConfig.depthClamp) + lightviewBounds[0][0] = lightviewBounds[1][0] - 8192; + + // Moving the Light in Texel-Sized Increments + // from http://msdn.microsoft.com/en-us/library/windows/desktop/ee416324%28v=vs.85%29.aspx + // + if (lightViewIndependentOfCameraView) + { + float cascadeBound, worldUnitsPerTexel, invWorldUnitsPerTexel; + + cascadeBound = MAX(lightviewBounds[1][0] - lightviewBounds[0][0], lightviewBounds[1][1] - lightviewBounds[0][1]); + cascadeBound = MAX(cascadeBound, lightviewBounds[1][2] - lightviewBounds[0][2]); + worldUnitsPerTexel = cascadeBound / tr.sunShadowFbo[level]->width; + invWorldUnitsPerTexel = 1.0f / worldUnitsPerTexel; + + VectorScale(lightviewBounds[0], invWorldUnitsPerTexel, lightviewBounds[0]); + lightviewBounds[0][0] = floor(lightviewBounds[0][0]); + lightviewBounds[0][1] = floor(lightviewBounds[0][1]); + lightviewBounds[0][2] = floor(lightviewBounds[0][2]); + VectorScale(lightviewBounds[0], worldUnitsPerTexel, lightviewBounds[0]); + + VectorScale(lightviewBounds[1], invWorldUnitsPerTexel, lightviewBounds[1]); + lightviewBounds[1][0] = floor(lightviewBounds[1][0]); + lightviewBounds[1][1] = floor(lightviewBounds[1][1]); + lightviewBounds[1][2] = floor(lightviewBounds[1][2]); + VectorScale(lightviewBounds[1], worldUnitsPerTexel, lightviewBounds[1]); + } + + //ri.Printf(PRINT_ALL, "level %d znear %f zfar %f\n", level, lightviewBounds[0][0], lightviewBounds[1][0]); + //ri.Printf(PRINT_ALL, "xmin %f xmax %f ymin %f ymax %f\n", lightviewBounds[0][1], lightviewBounds[1][1], -lightviewBounds[1][2], -lightviewBounds[0][2]); + } + + { + int firstDrawSurf; + + Com_Memset( &shadowParms, 0, sizeof( shadowParms ) ); + + if (glRefConfig.framebufferObject) + { + shadowParms.viewportX = 0; + shadowParms.viewportY = 0; + } + else + { + shadowParms.viewportX = tr.refdef.x; + shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.sunShadowFbo[level]->height ); + } + shadowParms.viewportWidth = tr.sunShadowFbo[level]->width; + shadowParms.viewportHeight = tr.sunShadowFbo[level]->height; + shadowParms.isPortal = false; + shadowParms.isMirror = false; + + shadowParms.fovX = 90; + shadowParms.fovY = 90; + + if (glRefConfig.framebufferObject) + shadowParms.targetFbo = tr.sunShadowFbo[level]; + + shadowParms.flags = VPF_DEPTHSHADOW | VPF_DEPTHCLAMP | VPF_ORTHOGRAPHIC | VPF_NOVIEWMODEL; + shadowParms.zFar = lightviewBounds[1][0]; + + VectorCopy(lightOrigin, shadowParms.orientation.origin); + + VectorCopy(lightViewAxis[0], shadowParms.orientation.axis[0]); + VectorCopy(lightViewAxis[1], shadowParms.orientation.axis[1]); + VectorCopy(lightViewAxis[2], shadowParms.orientation.axis[2]); + + VectorCopy(lightOrigin, shadowParms.pvsOrigin ); + + { + tr.viewCount++; + + tr.viewParms = shadowParms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupProjectionOrtho(&tr.viewParms, lightviewBounds); + + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + R_AddEntitySurfaces (); + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + } + + Mat4Multiply(tr.viewParms.projectionMatrix, tr.viewParms.world.modelMatrix, tr.refdef.sunShadowMvp[level]); + } +} + +void R_RenderCubemapSide( int cubemapIndex, int cubemapSide, bool subscene ) +{ + refdef_t refdef; + viewParms_t parms; + + memset( &refdef, 0, sizeof( refdef ) ); + refdef.rdflags = 0; + VectorCopy(tr.cubemaps[cubemapIndex].origin, refdef.vieworg); + + switch(cubemapSide) + { + case 0: + // -X + VectorSet( refdef.viewaxis[0], -1, 0, 0); + VectorSet( refdef.viewaxis[1], 0, 0, -1); + VectorSet( refdef.viewaxis[2], 0, 1, 0); + break; + case 1: + // +X + VectorSet( refdef.viewaxis[0], 1, 0, 0); + VectorSet( refdef.viewaxis[1], 0, 0, 1); + VectorSet( refdef.viewaxis[2], 0, 1, 0); + break; + case 2: + // -Y + VectorSet( refdef.viewaxis[0], 0, -1, 0); + VectorSet( refdef.viewaxis[1], 1, 0, 0); + VectorSet( refdef.viewaxis[2], 0, 0, -1); + break; + case 3: + // +Y + VectorSet( refdef.viewaxis[0], 0, 1, 0); + VectorSet( refdef.viewaxis[1], 1, 0, 0); + VectorSet( refdef.viewaxis[2], 0, 0, 1); + break; + case 4: + // -Z + VectorSet( refdef.viewaxis[0], 0, 0, -1); + VectorSet( refdef.viewaxis[1], 1, 0, 0); + VectorSet( refdef.viewaxis[2], 0, 1, 0); + break; + case 5: + // +Z + VectorSet( refdef.viewaxis[0], 0, 0, 1); + VectorSet( refdef.viewaxis[1], -1, 0, 0); + VectorSet( refdef.viewaxis[2], 0, 1, 0); + break; + } + + refdef.fov_x = 90; + refdef.fov_y = 90; + + refdef.x = 0; + refdef.y = 0; + refdef.width = tr.renderCubeFbo->width; + refdef.height = tr.renderCubeFbo->height; + + refdef.time = 0; + + if (!subscene) + { + RE_BeginScene(&refdef); + + // FIXME: sun shadows aren't rendered correctly in cubemaps + // fix involves changing r_FBufScale to fit smaller cubemap image size, or rendering cubemap to framebuffer first + if(0) //(glRefConfig.framebufferObject && r_sunlightMode->integer && (r_forceSun->integer || tr.sunShadows)) + { + R_RenderSunShadowMaps(&refdef, 0); + R_RenderSunShadowMaps(&refdef, 1); + R_RenderSunShadowMaps(&refdef, 2); + R_RenderSunShadowMaps(&refdef, 3); + } + } + + { + vec3_t ambient, directed, lightDir; + float scale; + + R_LightForPoint(tr.refdef.vieworg, ambient, directed, lightDir); + scale = directed[0] + directed[1] + directed[2] + ambient[0] + ambient[1] + ambient[2] + 1.0f; + + // only print message for first side + if (scale < 1.0001f && cubemapSide == 0) + { + ri.Printf(PRINT_ALL, "cubemap %d %s (%f, %f, %f) is outside the lightgrid or inside a wall!\n", cubemapIndex, tr.cubemaps[cubemapIndex].name, tr.refdef.vieworg[0], tr.refdef.vieworg[1], tr.refdef.vieworg[2]); + } + } + + Com_Memset( &parms, 0, sizeof( parms ) ); + + parms.viewportX = 0; + parms.viewportY = 0; + parms.viewportWidth = tr.renderCubeFbo->width; + parms.viewportHeight = tr.renderCubeFbo->height; + parms.isPortal = false; + parms.isMirror = true; + parms.flags = VPF_NOVIEWMODEL | VPF_NOCUBEMAPS; + + parms.fovX = 90; + parms.fovY = 90; + + VectorCopy( refdef.vieworg, parms.orientation.origin ); + VectorCopy( refdef.viewaxis[0], parms.orientation.axis[0] ); + VectorCopy( refdef.viewaxis[1], parms.orientation.axis[1] ); + VectorCopy( refdef.viewaxis[2], parms.orientation.axis[2] ); + + VectorCopy( refdef.vieworg, parms.pvsOrigin ); + + // FIXME: sun shadows aren't rendered correctly in cubemaps + // fix involves changing r_FBufScale to fit smaller cubemap image size, or rendering cubemap to framebuffer first + if (0) //(r_depthPrepass->value && ((r_forceSun->integer) || tr.sunShadows)) + { + parms.flags = VPF_USESUNLIGHT; + } + + parms.targetFbo = tr.renderCubeFbo; + parms.targetFboLayer = cubemapSide; + parms.targetFboCubemapIndex = cubemapIndex; + + R_RenderView(&parms); + + if (!subscene) + RE_EndScene(); +} diff --git a/src/renderergl2/tr_marks.cpp b/src/renderergl2/tr_marks.cpp new file mode 100644 index 0000000..b56005a --- /dev/null +++ b/src/renderergl2/tr_marks.cpp @@ -0,0 +1,472 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_marks.c -- polygon projection on the world polygons + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon) { + float dists[MAX_VERTS_ON_POLY+4] = { 0 }; + int sides[MAX_VERTS_ON_POLY+4] = { 0 }; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ (i+1) % numInPoints ]; + + d = dists[i] - dists[i+1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for (j=0 ; j<3 ; j++) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + (*numOutPoints)++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) { + + int s, c; + msurface_t *surf; + int *mark; + + // do the tail recursion in a loop + while ( node->contents == -1 ) { + s = BoxOnPlaneSide( mins, maxs, node->plane ); + if (s == 1) { + node = node->children[0]; + } else if (s == 2) { + node = node->children[1]; + } else { + R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir); + node = node->children[1]; + } + } + + // add the individual surfaces + mark = tr.world->marksurfaces + node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + int *surfViewCount; + // + if (*listlength >= listsize) break; + // + surfViewCount = &tr.world->surfacesViewCount[*mark]; + surf = tr.world->surfaces + *mark; + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + *surfViewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if (*(surf->data) == SF_FACE) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &surf->cullinfo.plane ); + if (s == 1 || s == 2) { + *surfViewCount = tr.viewCount; + } else if (DotProduct(surf->cullinfo.plane.normal, dir) > -0.5) { + // don't add faces that make sharp angles with the projection direction + *surfViewCount = tr.viewCount; + } + } + else if (*(surf->data) != SF_GRID && + *(surf->data) != SF_TRIANGLES) + *surfViewCount = tr.viewCount; + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if (*surfViewCount != tr.viewCount) { + *surfViewCount = tr.viewCount; + list[*listlength] = surf->data; + (*listlength)++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + (*returnedPoints) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + (*returnedFragments); + mf->firstPoint = (*returnedPoints); + mf->numPoints = numClipPoints; + Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + + (*returnedPoints) += numClipPoints; + (*returnedFragments)++; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY+2]; + float dists[MAX_VERTS_ON_POLY+2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfBspSurface_t *cv; + glIndex_t *tri; + srfVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + + if (numPoints <= 0) { + return 0; + } + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract(points[(i+1)%numPoints], points[i], v1); + VectorAdd(points[i], projection, v2); + VectorSubtract(points[i], v2, v2); + CrossProduct(v1, v2, normals[i]); + VectorNormalizeFast(normals[i]); + dists[i] = DotProduct(normals[i], points[i]); + } + // add near and far clipping planes for projection + VectorCopy(projectionDir, normals[numPoints]); + dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; + VectorCopy(projectionDir, normals[numPoints+1]); + VectorInverse(normals[numPoints+1]); + dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if (*surfaces[i] == SF_GRID) { + + cv = (srfBspSurface_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + vec3_t fNormal; + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy(dv[0].xyz, clipPoints[0][0]); + R_VaoUnpackNormal(fNormal, dv[0].normal); + VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + R_VaoUnpackNormal(fNormal, dv[cv->width].normal); + VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]); + VectorCopy(dv[1].xyz, clipPoints[0][2]); + R_VaoUnpackNormal(fNormal, dv[1].normal); + VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.1) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy(dv[1].xyz, clipPoints[0][0]); + R_VaoUnpackNormal(fNormal, dv[1].normal); + VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + R_VaoUnpackNormal(fNormal, dv[cv->width].normal); + VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]); + VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); + R_VaoUnpackNormal(fNormal, dv[cv->width + 1].normal); + VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.05) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } + else if (*surfaces[i] == SF_FACE) { + + srfBspSurface_t *surf = ( srfBspSurface_t * ) surfaces[i]; + + // check the normal of this face + if (DotProduct(surf->cullPlane.normal, projectionDir) > -0.5) { + continue; + } + + for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3) + { + for(j = 0; j < 3; j++) + { + v = surf->verts[tri[j]].xyz; + VectorMA(v, MARKER_OFFSET, surf->cullPlane.normal, clipPoints[0][j]); + } + + // add the fragments of this face + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) { + + srfBspSurface_t *surf = (srfBspSurface_t *) surfaces[i]; + + for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3) + { + for(j = 0; j < 3; j++) + { + vec3_t fNormal; + v = surf->verts[tri[j]].xyz; + R_VaoUnpackNormal(fNormal, surf->verts[tri[j]].normal); + VectorMA(v, MARKER_OFFSET, fNormal, clipPoints[0][j]); + } + + // add the fragments of this face + R_AddMarkFragments(3, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); + if(returnedFragments == maxFragments) + { + return returnedFragments; // not enough space for more fragments + } + } + } + } + return returnedFragments; +} diff --git a/src/renderergl2/tr_mesh.cpp b/src/renderergl2/tr_mesh.cpp new file mode 100644 index 0000000..2761f83 --- /dev/null +++ b/src/renderergl2/tr_mesh.cpp @@ -0,0 +1,418 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_mesh.c: triangle model functions + +#include "tr_local.h" + +static float ProjectRadius( float r, vec3_t location ) +{ + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.orientation.axis[0], tr.viewParms.orientation.origin ); + dist = DotProduct( tr.viewParms.orientation.axis[0], location ) - c; + + if ( dist <= 0 ) + return 0; + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) + pr = 1.0f; + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( mdvModel_t *model, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdvFrame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = model->frames + ent->e.frame; + oldFrame = model->frames + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_ComputeLOD + +================= +*/ +int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + mdvFrame_t *frame; + mdrHeader_t *mdr; + mdrFrame_t *mdrframe; + int lod; + + if ( tr.currentModel->numLods < 2 ) + { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } + else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + + if(tr.currentModel->type == MOD_MDR) + { + int frameSize; + mdr = (mdrHeader_t *) tr.currentModel->modelData; + frameSize = (size_t) (&((mdrFrame_t *)0)->bones[mdr->numBones]); + + mdrframe = (mdrFrame_t *) ((byte *) mdr + mdr->ofsFrames + frameSize * ent->e.frame); + + radius = RadiusFromBounds(mdrframe->bounds[0], mdrframe->bounds[1]); + } + else + { + //frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + frame = tr.currentModel->mdv[0]->frames; + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + } + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) + { + lodscale = r_lodscale->value; + if (lodscale > 20) lodscale = 20; + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = static_cast(flod); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= tr.currentModel->numLods ) + { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) + lod = tr.currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +int R_ComputeFogNum( mdvModel_t *model, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdvFrame_t *mdvFrame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + mdvFrame = model->frames + ent->e.frame; + VectorAdd( ent->e.origin, mdvFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdvFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdvFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + mdvModel_t *model = NULL; + mdvSurface_t *surface = NULL; + shader_t *shader = NULL; + int cull; + int lod; + int fogNum; + int cubemapIndex; + bool personalModel; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal + || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))); + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->mdv[0]->numFrames; + ent->e.oldframe %= tr.currentModel->mdv[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->mdv[0]->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->mdv[0]->numFrames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + model = tr.currentModel->mdv[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel ( model, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( model, ent ); + + cubemapIndex = R_CubemapForPoint(ent->e.origin); + + // + // draw all surfaces + // + surface = model->surfaces; + for ( i = 0 ; i < model->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j].name, surface->name ) ) { + shader = skin->surfaces[j].shader; + break; + } + } + if (shader == tr.defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name); + } + else if (shader->defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name); + } + //} else if ( surface->numShaders <= 0 ) { + //shader = tr.defaultShader; + } else { + //md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); + //md3Shader += ent->e.skinNum % surface->numShaders; + //shader = tr.shaders[ md3Shader->shaderIndex ]; + shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ]; + } + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & (RF_NOSHADOW|RF_DEPTHHACK)) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (surfaceType_t*)&model->vaoSurfaces[i], tr.shadowShader, 0, false, false, 0); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (surfaceType_t*)&model->vaoSurfaces[i], tr.projectionShadowShader, 0, false, false, 0 ); + } + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) + R_AddDrawSurf((surfaceType_t*)&model->vaoSurfaces[i], shader, fogNum, false, false, cubemapIndex ); + + surface++; + } + +} diff --git a/src/renderergl2/tr_model.cpp b/src/renderergl2/tr_model.cpp new file mode 100644 index 0000000..6e3b984 --- /dev/null +++ b/src/renderergl2/tr_model.cpp @@ -0,0 +1,1419 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_models.c -- model loading and caching + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +static bool R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *modName); +static bool R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name ); + +/* +==================== +R_RegisterMD3 +==================== +*/ +qhandle_t R_RegisterMD3(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + int size; + int lod; + int ident; + bool loaded = false; + int numLoaded; + char filename[MAX_QPATH], namebuf[MAX_QPATH+20]; + char *fext, defex[] = "md3"; + + numLoaded = 0; + + strcpy(filename, name); + + fext = strchr(filename, '.'); + if(!fext) + fext = defex; + else + { + *fext = '\0'; + fext++; + } + + for (lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod--) + { + if(lod) + Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext); + else + Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext); + + size = ri.FS_ReadFile( namebuf, &buf.v ); + if(!buf.u) + continue; + + ident = LittleLong(* (unsigned *) buf.u); + if (ident == MD3_IDENT) + loaded = R_LoadMD3(mod, lod, buf.u, size, name); + else + ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name); + + ri.FS_FreeFile(buf.v); + + if(loaded) + { + mod->numLods++; + numLoaded++; + } + else + break; + } + + if(numLoaded) + { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for(lod--; lod >= 0; lod--) + { + mod->numLods++; + mod->mdv[lod] = mod->mdv[lod + 1]; + } + + return mod->index; + } + + ri.Printf(PRINT_DEVELOPER, "R_RegisterMD3: couldn't load %s\n", name); + + mod->type = MOD_BAD; + return 0; +} + +/* +==================== +R_RegisterMDR +==================== +*/ +qhandle_t R_RegisterMDR(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + int ident; + bool loaded = false; + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + mod->type = MOD_BAD; + return 0; + } + + ident = LittleLong(*(unsigned *)buf.u); + if(ident == MDR_IDENT) + loaded = R_LoadMDR(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"R_RegisterMDR: couldn't load mdr file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; +} + +/* +==================== +R_RegisterIQM +==================== +*/ +qhandle_t R_RegisterIQM(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + bool loaded = false; + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + mod->type = MOD_BAD; + return 0; + } + + loaded = R_LoadIQM(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"R_RegisterIQM: couldn't load iqm file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; +} + + +struct modelExtToLoaderMap_t +{ + const char *ext; + qhandle_t (*ModelLoader)( const char *, model_t * ); +}; + +// Note that the ordering indicates the order of preference used +// when there are multiple models of different formats available +static modelExtToLoaderMap_t modelLoaders[ ] = +{ + { "iqm", R_RegisterIQM }, + { "mdr", R_RegisterMDR }, + { "md3", R_RegisterMD3 } +}; + +static int numModelLoaders = ARRAY_LEN(modelLoaders); + +//=============================================================================== + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) { + model_t *mod; + + // out of range gets the defualt model + if ( index < 1 || index >= tr.numModels ) { + return tr.models[0]; + } + + mod = tr.models[index]; + + return mod; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = (model_t*)ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low ); + mod->index = tr.numModels; + tr.models[tr.numModels] = mod; + tr.numModels++; + + return mod; +} + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +qhandle_t RE_RegisterModel( const char *name ) { + model_t *mod; + qhandle_t hModel; + bool orgNameFailed = false; + int orgLoader = -1; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + char altName[ MAX_QPATH ]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Model name exceeds MAX_QPATH\n" ); + return 0; + } + + // + // search the currently loaded models + // + for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { + mod = tr.models[hModel]; + if ( !strcmp( mod->name, name ) ) { + if( mod->type == MOD_BAD ) { + return 0; + } + return hModel; + } + } + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + + + R_IssuePendingRenderCommands(); + + mod->type = MOD_BAD; + mod->numLods = 0; + + // + // load the files + // + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numModelLoaders; i++ ) + { + if( !Q_stricmp( ext, modelLoaders[ i ].ext ) ) + { + // Load + hModel = modelLoaders[ i ].ModelLoader( localName, mod ); + break; + } + } + + // A loader was found + if( i < numModelLoaders ) + { + if( !hModel ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = true; + orgLoader = i; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return mod->index; + } + } + } + + // Try and find a suitable match using all + // the model formats supported + for( i = 0; i < numModelLoaders; i++ ) + { + if (i == orgLoader) + continue; + + Com_sprintf( altName, sizeof (altName), "%s.%s", localName, modelLoaders[ i ].ext ); + + // Load + hModel = modelLoaders[ i ].ModelLoader( altName, mod ); + + if( hModel ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } + + return hModel; +} + +/* +================= +R_LoadMD3 +================= +*/ +static bool R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName) +{ + int f, i, j; + + md3Header_t *md3Model; + md3Frame_t *md3Frame; + md3Surface_t *md3Surf; + md3Shader_t *md3Shader; + md3Triangle_t *md3Tri; + md3St_t *md3st; + md3XyzNormal_t *md3xyz; + md3Tag_t *md3Tag; + + mdvModel_t *mdvModel; + mdvFrame_t *frame; + mdvSurface_t *surf;//, *surface; + int *shaderIndex; + glIndex_t *tri; + mdvVertex_t *v; + mdvSt_t *st; + mdvTag_t *tag; + mdvTagName_t *tagName; + + int version; + int size; + + md3Model = (md3Header_t *) buffer; + + version = LittleLong(md3Model->version); + if(version != MD3_VERSION) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", modName, version, MD3_VERSION); + return false; + } + + mod->type = MOD_MESH; + size = LittleLong(md3Model->ofsEnd); + mod->dataSize += size; + mdvModel = mod->mdv[lod] = (mdvModel_t*)ri.Hunk_Alloc(sizeof(mdvModel_t), h_low); + +// Com_Memcpy(mod->md3[lod], buffer, LittleLong(md3Model->ofsEnd)); + + LL(md3Model->ident); + LL(md3Model->version); + LL(md3Model->numFrames); + LL(md3Model->numTags); + LL(md3Model->numSurfaces); + LL(md3Model->ofsFrames); + LL(md3Model->ofsTags); + LL(md3Model->ofsSurfaces); + LL(md3Model->ofsEnd); + + if(md3Model->numFrames < 1) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has no frames\n", modName); + return false; + } + + // swap all the frames + mdvModel->numFrames = md3Model->numFrames; + mdvModel->frames = frame = (mdvFrame_t*)ri.Hunk_Alloc(sizeof(*frame) * md3Model->numFrames, h_low); + + md3Frame = (md3Frame_t *) ((byte *) md3Model + md3Model->ofsFrames); + for(i = 0; i < md3Model->numFrames; i++, frame++, md3Frame++) + { + frame->radius = LittleFloat(md3Frame->radius); + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(md3Frame->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(md3Frame->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(md3Frame->localOrigin[j]); + } + } + + // swap all the tags + mdvModel->numTags = md3Model->numTags; + mdvModel->tags = tag = (mdvTag_t*)ri.Hunk_Alloc(sizeof(*tag) * (md3Model->numTags * md3Model->numFrames), h_low); + + md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags); + for(i = 0; i < md3Model->numTags * md3Model->numFrames; i++, tag++, md3Tag++) + { + for(j = 0; j < 3; j++) + { + tag->origin[j] = LittleFloat(md3Tag->origin[j]); + tag->axis[0][j] = LittleFloat(md3Tag->axis[0][j]); + tag->axis[1][j] = LittleFloat(md3Tag->axis[1][j]); + tag->axis[2][j] = LittleFloat(md3Tag->axis[2][j]); + } + } + + + mdvModel->tagNames = tagName = (mdvTagName_t*)ri.Hunk_Alloc(sizeof(*tagName) * (md3Model->numTags), h_low); + + md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags); + for(i = 0; i < md3Model->numTags; i++, tagName++, md3Tag++) + { + Q_strncpyz(tagName->name, md3Tag->name, sizeof(tagName->name)); + } + + // swap all the surfaces + mdvModel->numSurfaces = md3Model->numSurfaces; + mdvModel->surfaces = surf = (mdvSurface_t*)ri.Hunk_Alloc(sizeof(*surf) * md3Model->numSurfaces, h_low); + + md3Surf = (md3Surface_t *) ((byte *) md3Model + md3Model->ofsSurfaces); + for(i = 0; i < md3Model->numSurfaces; i++) + { + LL(md3Surf->ident); + LL(md3Surf->flags); + LL(md3Surf->numFrames); + LL(md3Surf->numShaders); + LL(md3Surf->numTriangles); + LL(md3Surf->ofsTriangles); + LL(md3Surf->numVerts); + LL(md3Surf->ofsShaders); + LL(md3Surf->ofsSt); + LL(md3Surf->ofsXyzNormals); + LL(md3Surf->ofsEnd); + + if(md3Surf->numVerts >= SHADER_MAX_VERTEXES) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i verts on %s (%i).\n", + modName, SHADER_MAX_VERTEXES - 1, md3Surf->name[0] ? md3Surf->name : "a surface", + md3Surf->numVerts ); + return false; + } + if(md3Surf->numTriangles * 3 >= SHADER_MAX_INDEXES) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i triangles on %s (%i).\n", + modName, ( SHADER_MAX_INDEXES / 3 ) - 1, md3Surf->name[0] ? md3Surf->name : "a surface", + md3Surf->numTriangles ); + return false; + } + + // change to surface identifier + surf->surfaceType = SF_MDV; + + // give pointer to model for Tess_SurfaceMDX + surf->model = mdvModel; + + // copy surface name + Q_strncpyz(surf->name, md3Surf->name, sizeof(surf->name)); + + // lowercase the surface name so skin compares are faster + Q_strlwr(surf->name); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen(surf->name); + if(j > 2 && surf->name[j - 2] == '_') + { + surf->name[j - 2] = 0; + } + + // register the shaders + surf->numShaderIndexes = md3Surf->numShaders; + surf->shaderIndexes = shaderIndex = (int*)ri.Hunk_Alloc(sizeof(*shaderIndex) * md3Surf->numShaders, h_low); + + md3Shader = (md3Shader_t *) ((byte *) md3Surf + md3Surf->ofsShaders); + for(j = 0; j < md3Surf->numShaders; j++, shaderIndex++, md3Shader++) + { + shader_t *sh; + + sh = R_FindShader(md3Shader->name, LIGHTMAP_NONE, true); + if(sh->defaultShader) + { + *shaderIndex = 0; + } + else + { + *shaderIndex = sh->index; + } + } + + // swap all the triangles + surf->numIndexes = md3Surf->numTriangles * 3; + surf->indexes = tri = (glIndex_t*)ri.Hunk_Alloc(sizeof(*tri) * 3 * md3Surf->numTriangles, h_low); + + md3Tri = (md3Triangle_t *) ((byte *) md3Surf + md3Surf->ofsTriangles); + for(j = 0; j < md3Surf->numTriangles; j++, tri += 3, md3Tri++) + { + tri[0] = LittleLong(md3Tri->indexes[0]); + tri[1] = LittleLong(md3Tri->indexes[1]); + tri[2] = LittleLong(md3Tri->indexes[2]); + } + + // swap all the XyzNormals + surf->numVerts = md3Surf->numVerts; + surf->verts = v = (mdvVertex_t*)ri.Hunk_Alloc(sizeof(*v) * (md3Surf->numVerts * md3Surf->numFrames), h_low); + + md3xyz = (md3XyzNormal_t *) ((byte *) md3Surf + md3Surf->ofsXyzNormals); + for(j = 0; j < md3Surf->numVerts * md3Surf->numFrames; j++, md3xyz++, v++) + { + unsigned lat, lng; + unsigned short normal; + vec3_t fNormal; + + v->xyz[0] = LittleShort(md3xyz->xyz[0]) * MD3_XYZ_SCALE; + v->xyz[1] = LittleShort(md3xyz->xyz[1]) * MD3_XYZ_SCALE; + v->xyz[2] = LittleShort(md3xyz->xyz[2]) * MD3_XYZ_SCALE; + + normal = LittleShort(md3xyz->normal); + + lat = ( normal >> 8 ) & 0xff; + lng = ( normal & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + fNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + fNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + fNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + R_VaoPackNormal(v->normal, fNormal); + } + + // swap all the ST + surf->st = st = (mdvSt_t*)ri.Hunk_Alloc(sizeof(*st) * md3Surf->numVerts, h_low); + + md3st = (md3St_t *) ((byte *) md3Surf + md3Surf->ofsSt); + for(j = 0; j < md3Surf->numVerts; j++, md3st++, st++) + { + st->st[0] = LittleFloat(md3st->st[0]); + st->st[1] = LittleFloat(md3st->st[1]); + } + + // calc tangent spaces + { + vec3_t *sdirs = (vec3_t*)ri.Malloc(sizeof(*sdirs) * surf->numVerts * mdvModel->numFrames); + vec3_t *tdirs = (vec3_t*)ri.Malloc(sizeof(*tdirs) * surf->numVerts * mdvModel->numFrames); + + for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++) + { + VectorClear(sdirs[j]); + VectorClear(tdirs[j]); + } + + for(f = 0; f < mdvModel->numFrames; f++) + { + for(j = 0, tri = surf->indexes; j < surf->numIndexes; j += 3, tri += 3) + { + vec3_t sdir, tdir; + const float *v0, *v1, *v2, *t0, *t1, *t2; + glIndex_t index0, index1, index2; + + index0 = surf->numVerts * f + tri[0]; + index1 = surf->numVerts * f + tri[1]; + index2 = surf->numVerts * f + tri[2]; + + v0 = surf->verts[index0].xyz; + v1 = surf->verts[index1].xyz; + v2 = surf->verts[index2].xyz; + + t0 = surf->st[tri[0]].st; + t1 = surf->st[tri[1]].st; + t2 = surf->st[tri[2]].st; + + R_CalcTexDirs(sdir, tdir, v0, v1, v2, t0, t1, t2); + + VectorAdd(sdir, sdirs[index0], sdirs[index0]); + VectorAdd(sdir, sdirs[index1], sdirs[index1]); + VectorAdd(sdir, sdirs[index2], sdirs[index2]); + VectorAdd(tdir, tdirs[index0], tdirs[index0]); + VectorAdd(tdir, tdirs[index1], tdirs[index1]); + VectorAdd(tdir, tdirs[index2], tdirs[index2]); + } + } + + for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++) + { + vec3_t normal; + vec4_t tangent; + + VectorNormalize(sdirs[j]); + VectorNormalize(tdirs[j]); + + R_VaoUnpackNormal(normal, v->normal); + + tangent[3] = R_CalcTangentSpace(tangent, NULL, normal, sdirs[j], tdirs[j]); + + R_VaoPackTangent(v->tangent, tangent); + } + + ri.Free(sdirs); + ri.Free(tdirs); + } + + // find the next surface + md3Surf = (md3Surface_t *) ((byte *) md3Surf + md3Surf->ofsEnd); + surf++; + } + + { + srfVaoMdvMesh_t *vaoSurf; + + mdvModel->numVaoSurfaces = mdvModel->numSurfaces; + mdvModel->vaoSurfaces = (srfVaoMdvMesh_t*)ri.Hunk_Alloc(sizeof(*mdvModel->vaoSurfaces) * mdvModel->numSurfaces, h_low); + + vaoSurf = mdvModel->vaoSurfaces; + surf = mdvModel->surfaces; + for (i = 0; i < mdvModel->numSurfaces; i++, vaoSurf++, surf++) + { + uint32_t offset_xyz, offset_st, offset_normal, offset_tangent; + uint32_t stride_xyz, stride_st, stride_normal, stride_tangent; + uint32_t dataSize, dataOfs; + uint8_t *data; + + if (mdvModel->numFrames > 1) + { + // vertex animation, store texcoords first, then position/normal/tangents + offset_st = 0; + offset_xyz = surf->numVerts * sizeof(vec2_t); + offset_normal = offset_xyz + sizeof(vec3_t); + offset_tangent = offset_normal + sizeof(int16_t) * 4; + stride_st = sizeof(vec2_t); + stride_xyz = sizeof(vec3_t) + sizeof(int16_t) * 4; + stride_xyz += sizeof(int16_t) * 4; + stride_normal = stride_tangent = stride_xyz; + + dataSize = offset_xyz + surf->numVerts * mdvModel->numFrames * stride_xyz; + } + else + { + // no animation, interleave everything + offset_xyz = 0; + offset_st = offset_xyz + sizeof(vec3_t); + offset_normal = offset_st + sizeof(vec2_t); + offset_tangent = offset_normal + sizeof(int16_t) * 4; + stride_xyz = offset_tangent + sizeof(int16_t) * 4; + stride_st = stride_normal = stride_tangent = stride_xyz; + + dataSize = surf->numVerts * stride_xyz; + } + + + data = (uint8_t*)ri.Malloc(dataSize); + dataOfs = 0; + + if (mdvModel->numFrames > 1) + { + st = surf->st; + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + memcpy(data + dataOfs, &st->st, sizeof(vec2_t)); + dataOfs += sizeof(st->st); + } + + v = surf->verts; + for ( j = 0; j < surf->numVerts * mdvModel->numFrames ; j++, v++ ) + { + // xyz + memcpy(data + dataOfs, &v->xyz, sizeof(vec3_t)); + dataOfs += sizeof(vec3_t); + + // normal + memcpy(data + dataOfs, &v->normal, sizeof(int16_t) * 4); + dataOfs += sizeof(int16_t) * 4; + + // tangent + memcpy(data + dataOfs, &v->tangent, sizeof(int16_t) * 4); + dataOfs += sizeof(int16_t) * 4; + } + } + else + { + v = surf->verts; + st = surf->st; + for ( j = 0; j < surf->numVerts; j++, v++, st++ ) + { + // xyz + memcpy(data + dataOfs, &v->xyz, sizeof(vec3_t)); + dataOfs += sizeof(v->xyz); + + // st + memcpy(data + dataOfs, &st->st, sizeof(vec2_t)); + dataOfs += sizeof(st->st); + + // normal + memcpy(data + dataOfs, &v->normal, sizeof(int16_t) * 4); + dataOfs += sizeof(int16_t) * 4; + + // tangent + memcpy(data + dataOfs, &v->tangent, sizeof(int16_t) * 4); + dataOfs += sizeof(int16_t) * 4; + } + } + + vaoSurf->surfaceType = SF_VAO_MDVMESH; + vaoSurf->mdvModel = mdvModel; + vaoSurf->mdvSurface = surf; + vaoSurf->numIndexes = surf->numIndexes; + vaoSurf->numVerts = surf->numVerts; + + vaoSurf->minIndex = 0; + vaoSurf->maxIndex = surf->numVerts - 1; + + vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC); + + vaoSurf->vao->attribs[ATTR_INDEX_POSITION].enabled = 1; + vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1; + + vaoSurf->vao->attribs[ATTR_INDEX_POSITION].count = 3; + vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].count = 2; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].count = 4; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].count = 4; + + vaoSurf->vao->attribs[ATTR_INDEX_POSITION].type = GL_FLOAT; + vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].type = GL_FLOAT; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT; + + vaoSurf->vao->attribs[ATTR_INDEX_POSITION].normalized = GL_FALSE; + vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].normalized = GL_FALSE; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE; + + vaoSurf->vao->attribs[ATTR_INDEX_POSITION].offset = offset_xyz; + vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].offset = offset_st; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].offset = offset_normal; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].offset = offset_tangent; + + vaoSurf->vao->attribs[ATTR_INDEX_POSITION].stride = stride_xyz; + vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].stride = stride_st; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].stride = stride_normal; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].stride = stride_tangent; + + if (mdvModel->numFrames > 1) + { + vaoSurf->vao->attribs[ATTR_INDEX_POSITION2] = vaoSurf->vao->attribs[ATTR_INDEX_POSITION]; + vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2 ] = vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ]; + vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2 ] = vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ]; + + vaoSurf->vao->frameSize = stride_xyz * surf->numVerts; + } + + Vao_SetVertexPointers(vaoSurf->vao); + + ri.Free(data); + } + } + + return true; +} + + + +/* +================= +R_LoadMDR +================= +*/ +static bool R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) +{ + int i, j, k, l; + mdrHeader_t *pinmodel, *mdr; + mdrFrame_t *frame; + mdrLOD_t *lod, *curlod; + mdrSurface_t *surf, *cursurf; + mdrTriangle_t *tri, *curtri; + mdrVertex_t *v, *curv; + mdrWeight_t *weight, *curweight; + mdrTag_t *tag, *curtag; + int size; + shader_t *sh; + + pinmodel = (mdrHeader_t *)buffer; + + pinmodel->version = LittleLong(pinmodel->version); + if (pinmodel->version != MDR_VERSION) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION); + return false; + } + + size = LittleLong(pinmodel->ofsEnd); + + if(size > filesize) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name); + return false; + } + + mod->type = MOD_MDR; + + LL(pinmodel->numFrames); + LL(pinmodel->numBones); + LL(pinmodel->ofsFrames); + + // This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame + // over and over again, we'll uncompress it in this function already, so we must adjust the size of the target mdr. + if(pinmodel->ofsFrames < 0) + { + // mdrFrame_t is larger than mdrCompFrame_t: + size += pinmodel->numFrames * sizeof(frame->name); + // now add enough space for the uncompressed bones. + size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t))); + } + + // simple bounds check + if(pinmodel->numBones < 0 || + sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + mod->dataSize += size; + mod->modelData = mdr = (mdrHeader_t*)ri.Hunk_Alloc( size, h_low ); + + // Copy all the values over from the file and fix endian issues in the process, if necessary. + + mdr->ident = LittleLong(pinmodel->ident); + mdr->version = pinmodel->version; // Don't need to swap byte order on this one, we already did above. + Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name)); + mdr->numFrames = pinmodel->numFrames; + mdr->numBones = pinmodel->numBones; + mdr->numLODs = LittleLong(pinmodel->numLODs); + mdr->numTags = LittleLong(pinmodel->numTags); + // We don't care about the other offset values, we'll generate them ourselves while loading. + + mod->numLods = mdr->numLODs; + + if ( mdr->numFrames < 1 ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name); + return false; + } + + /* The first frame will be put into the first free space after the header */ + frame = (mdrFrame_t *)(mdr + 1); + mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr); + + if (pinmodel->ofsFrames < 0) + { + mdrCompFrame_t *cframe; + + // compressed model... + cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames); + + for(i = 0; i < mdr->numFrames; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]); + } + + frame->radius = LittleFloat(cframe->radius); + frame->name[0] = '\0'; // No name supplied in the compressed version. + + for(j = 0; j < mdr->numBones; j++) + { + for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++) + { + // Do swapping for the uncompressing functions. They seem to use shorts + // values only, so I assume this will work. Never tested it on other + // platforms, though. + + ((unsigned short *)(cframe->bones[j].Comp))[k] = + LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] ); + } + + /* Now do the actual uncompressing */ + MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp); + } + + // Next Frame... + cframe = (mdrCompFrame_t *) &cframe->bones[j]; + frame = (mdrFrame_t *) &frame->bones[j]; + } + } + else + { + mdrFrame_t *curframe; + + // uncompressed model... + // + + curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames); + + // swap all the frames + for ( i = 0 ; i < mdr->numFrames ; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]); + } + + frame->radius = LittleFloat(curframe->radius); + Q_strncpyz(frame->name, curframe->name, sizeof(frame->name)); + + for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++) + { + ((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] ); + } + + curframe = (mdrFrame_t *) &curframe->bones[mdr->numBones]; + frame = (mdrFrame_t *) &frame->bones[mdr->numBones]; + } + } + + // frame should now point to the first free address after all frames. + lod = (mdrLOD_t *) frame; + mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr); + + curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs)); + + // swap all the LOD's + for ( l = 0 ; l < mdr->numLODs ; l++) + { + // simple bounds check + if((byte *) (lod + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + lod->numSurfaces = LittleLong(curlod->numSurfaces); + + // swap all the surfaces + surf = (mdrSurface_t *) (lod + 1); + lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod); + cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces)); + + for ( i = 0 ; i < lod->numSurfaces ; i++) + { + // simple bounds check + if((byte *) (surf + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + // first do some copying stuff + + surf->ident = SF_MDR; + Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name)); + Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader)); + + surf->ofsHeader = (byte *) mdr - (byte *) surf; + + surf->numVerts = LittleLong(cursurf->numVerts); + surf->numTriangles = LittleLong(cursurf->numTriangles); + // numBoneReferences and BoneReferences generally seem to be unused + + // now do the checks that may fail. + if ( surf->numVerts >= SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on %s (%i).\n", + mod_name, SHADER_MAX_VERTEXES - 1, surf->name[0] ? surf->name : "a surface", + surf->numVerts ); + return false; + } + if ( surf->numTriangles*3 >= SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on %s (%i).\n", + mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, surf->name[0] ? surf->name : "a surface", + surf->numTriangles ); + return false; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // register the shaders + sh = R_FindShader(surf->shader, LIGHTMAP_NONE, true); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + + // now copy the vertexes. + v = (mdrVertex_t *) (surf + 1); + surf->ofsVerts = (int)((byte *) v - (byte *) surf); + curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts)); + + for(j = 0; j < surf->numVerts; j++) + { + LL(curv->numWeights); + + // simple bounds check + if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + v->normal[0] = LittleFloat(curv->normal[0]); + v->normal[1] = LittleFloat(curv->normal[1]); + v->normal[2] = LittleFloat(curv->normal[2]); + + v->texCoords[0] = LittleFloat(curv->texCoords[0]); + v->texCoords[1] = LittleFloat(curv->texCoords[1]); + + v->numWeights = curv->numWeights; + weight = &v->weights[0]; + curweight = &curv->weights[0]; + + // Now copy all the weights + for(k = 0; k < v->numWeights; k++) + { + weight->boneIndex = LittleLong(curweight->boneIndex); + weight->boneWeight = LittleFloat(curweight->boneWeight); + + weight->offset[0] = LittleFloat(curweight->offset[0]); + weight->offset[1] = LittleFloat(curweight->offset[1]); + weight->offset[2] = LittleFloat(curweight->offset[2]); + + weight++; + curweight++; + } + + v = (mdrVertex_t *) weight; + curv = (mdrVertex_t *) curweight; + } + + // we know the offset to the triangles now: + tri = (mdrTriangle_t *) v; + surf->ofsTriangles = (int)((byte *) tri - (byte *) surf); + curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles)); + + // simple bounds check + if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + for(j = 0; j < surf->numTriangles; j++) + { + tri->indexes[0] = LittleLong(curtri->indexes[0]); + tri->indexes[1] = LittleLong(curtri->indexes[1]); + tri->indexes[2] = LittleLong(curtri->indexes[2]); + + tri++; + curtri++; + } + + // tri now points to the end of the surface. + surf->ofsEnd = (byte *) tri - (byte *) surf; + surf = (mdrSurface_t *) tri; + + // find the next surface. + cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd)); + } + + // surf points to the next lod now. + lod->ofsEnd = (int)((byte *) surf - (byte *) lod); + lod = (mdrLOD_t *) surf; + + // find the next LOD. + curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd)); + } + + // lod points to the first tag now, so update the offset too. + tag = (mdrTag_t *) lod; + mdr->ofsTags = (int)((byte *) tag - (byte *) mdr); + curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags)); + + // simple bounds check + if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return false; + } + + for (i = 0 ; i < mdr->numTags ; i++) + { + tag->boneIndex = LittleLong(curtag->boneIndex); + Q_strncpyz(tag->name, curtag->name, sizeof(tag->name)); + + tag++; + curtag++; + } + + // And finally we know the real offset to the end. + mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr); + + // phew! we're done. + + return true; +} + + + +//============================================================================= + +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { + int i; + + R_Init(); + + *glconfigOut = glConfig; + + R_IssuePendingRenderCommands(); + + tr.visIndex = 0; + // force markleafs to regenerate + for(i = 0; i < MAX_VISCOUNTS; i++) { + tr.visClusters[i] = -2; + } + + R_ClearFlares(); + RE_ClearScene(); + + tr.registered = true; +} + +//============================================================================= + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) { + model_t *mod; + + // leave a space for NULL model + tr.numModels = 0; + + mod = R_AllocModel(); + mod->type = MOD_BAD; +} + + +/* +================ +R_Modellist_f +================ +*/ +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < tr.numModels; i++ ) { + mod = tr.models[i]; + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->mdv[j] && mod->mdv[j] != mod->mdv[j-1] ) { + lods++; + } + } + ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + total += mod->dataSize; + } + ri.Printf( PRINT_ALL, "%8i : Total models\n", total ); + +#if 0 // not working right with new hunk + if ( tr.world ) { + ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name ); + } +#endif +} + + +//============================================================================= + + +/* +================ +R_GetTag +================ +*/ +static mdvTag_t *R_GetTag( mdvModel_t *mod, int frame, const char *_tagName ) { + int i; + mdvTag_t *tag; + mdvTagName_t *tagName; + + if ( frame >= mod->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mod->numFrames - 1; + } + + tag = mod->tags + frame * mod->numTags; + tagName = mod->tagNames; + for(i = 0; i < mod->numTags; i++, tag++, tagName++) + { + if(!strcmp(tagName->name, _tagName)) + { + return tag; + } + } + + return NULL; +} + +mdvTag_t *R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, mdvTag_t * dest) +{ + int i, j, k; + int frameSize; + mdrFrame_t *frame; + mdrTag_t *tag; + + if ( framenum >= mod->numFrames ) + { + // it is possible to have a bad frame while changing models, so don't error + framenum = mod->numFrames - 1; + } + + tag = (mdrTag_t *)((byte *)mod + mod->ofsTags); + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) + { + if ( !strcmp( tag->name, tagName ) ) + { + // uncompressed model... + // + frameSize = (intptr_t)( &((mdrFrame_t *)0)->bones[ mod->numBones ] ); + frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize ); + + for (j = 0; j < 3; j++) + { + for (k = 0; k < 3; k++) + dest->axis[j][k] = frame->bones[tag->boneIndex].matrix[k][j]; + } + + dest->origin[0] = frame->bones[tag->boneIndex].matrix[0][3]; + dest->origin[1] = frame->bones[tag->boneIndex].matrix[1][3]; + dest->origin[2] = frame->bones[tag->boneIndex].matrix[2][3]; + + return dest; + } + } + + return NULL; +} + +/* +================ +R_LerpTag +================ +*/ +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ) { + mdvTag_t *start, *end; + mdvTag_t start_space, end_space; + int i; + float frontLerp, backLerp; + model_t *model; + + model = R_GetModelByHandle( handle ); + if ( !model->mdv[0] ) + { + if(model->type == MOD_MDR) + { + start = R_GetAnimTag((mdrHeader_t *) model->modelData, startFrame, tagName, &start_space); + end = R_GetAnimTag((mdrHeader_t *) model->modelData, endFrame, tagName, &end_space); + } + else if( model->type == MOD_IQM ) { + return R_IQMLerpTag( tag, (iqmData_t*)model->modelData, startFrame, endFrame, frac, tagName ); + } else { + start = end = NULL; + } + } + else + { + start = R_GetTag( model->mdv[0], startFrame, tagName ); + end = R_GetTag( model->mdv[0], endFrame, tagName ); + } + + if ( !start || !end ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return false; + } + + frontLerp = frac; + backLerp = 1.0f - frac; + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp; + } + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); + return true; +} + + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + + model = R_GetModelByHandle( handle ); + + if(model->type == MOD_BRUSH) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + + return; + } else if (model->type == MOD_MESH) { + mdvModel_t *header; + mdvFrame_t *frame; + + header = model->mdv[0]; + frame = header->frames; + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; + } else if (model->type == MOD_MDR) { + mdrHeader_t *header; + mdrFrame_t *frame; + + header = (mdrHeader_t *)model->modelData; + frame = (mdrFrame_t *) ((byte *)header + header->ofsFrames); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; + } else if(model->type == MOD_IQM) { + iqmData_t *iqmData; + + iqmData = (iqmData_t*)model->modelData; + + if(iqmData->bounds) + { + VectorCopy(iqmData->bounds, mins); + VectorCopy(iqmData->bounds + 3, maxs); + return; + } + } + + VectorClear( mins ); + VectorClear( maxs ); +} diff --git a/src/renderergl2/tr_model_iqm.cpp b/src/renderergl2/tr_model_iqm.cpp new file mode 100644 index 0000000..40ce9da --- /dev/null +++ b/src/renderergl2/tr_model_iqm.cpp @@ -0,0 +1,1196 @@ +/* +=========================================================================== +Copyright (C) 2011 Thilo Schulz +Copyright (C) 2011 Matthias Bentrup +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +// 3x4 identity matrix +static float identityMatrix[12] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0 +}; + +static bool IQM_CheckRange( iqmHeader_t *header, int offset, + int count,int size ) { + // return true if the range specified by offset, count and size + // doesn't fit into the file + return ( count <= 0 || + offset < 0 || + offset > header->filesize || + offset + count * size < 0 || + offset + count * size > header->filesize ); +} +// "multiply" 3x4 matrices, these are assumed to be the top 3 rows +// of a 4x4 matrix with the last row = (0 0 0 1) +static void Matrix34Multiply( float *a, float *b, float *out ) { + out[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8]; + out[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9]; + out[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10]; + out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3]; + out[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8]; + out[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9]; + out[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10]; + out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7]; + out[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; + out[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; + out[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; + out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; +} +static void Matrix34Multiply_OnlySetOrigin( float *a, float *b, float *out ) { + out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3]; + out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7]; + out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; +} +static void InterpolateMatrix( float *a, float *b, float lerp, float *mat ) { + float unLerp = 1.0f - lerp; + + mat[ 0] = a[ 0] * unLerp + b[ 0] * lerp; + mat[ 1] = a[ 1] * unLerp + b[ 1] * lerp; + mat[ 2] = a[ 2] * unLerp + b[ 2] * lerp; + mat[ 3] = a[ 3] * unLerp + b[ 3] * lerp; + mat[ 4] = a[ 4] * unLerp + b[ 4] * lerp; + mat[ 5] = a[ 5] * unLerp + b[ 5] * lerp; + mat[ 6] = a[ 6] * unLerp + b[ 6] * lerp; + mat[ 7] = a[ 7] * unLerp + b[ 7] * lerp; + mat[ 8] = a[ 8] * unLerp + b[ 8] * lerp; + mat[ 9] = a[ 9] * unLerp + b[ 9] * lerp; + mat[10] = a[10] * unLerp + b[10] * lerp; + mat[11] = a[11] * unLerp + b[11] * lerp; +} +static void JointToMatrix( vec4_t rot, vec3_t scale, vec3_t trans, + float *mat ) { + float xx = 2.0f * rot[0] * rot[0]; + float yy = 2.0f * rot[1] * rot[1]; + float zz = 2.0f * rot[2] * rot[2]; + float xy = 2.0f * rot[0] * rot[1]; + float xz = 2.0f * rot[0] * rot[2]; + float yz = 2.0f * rot[1] * rot[2]; + float wx = 2.0f * rot[3] * rot[0]; + float wy = 2.0f * rot[3] * rot[1]; + float wz = 2.0f * rot[3] * rot[2]; + + mat[ 0] = scale[0] * (1.0f - (yy + zz)); + mat[ 1] = scale[0] * (xy - wz); + mat[ 2] = scale[0] * (xz + wy); + mat[ 3] = trans[0]; + mat[ 4] = scale[1] * (xy + wz); + mat[ 5] = scale[1] * (1.0f - (xx + zz)); + mat[ 6] = scale[1] * (yz - wx); + mat[ 7] = trans[1]; + mat[ 8] = scale[2] * (xz - wy); + mat[ 9] = scale[2] * (yz + wx); + mat[10] = scale[2] * (1.0f - (xx + yy)); + mat[11] = trans[2]; +} +static void Matrix34Invert( float *inMat, float *outMat ) +{ + vec3_t trans; + float invSqrLen, *v; + + outMat[ 0] = inMat[ 0]; outMat[ 1] = inMat[ 4]; outMat[ 2] = inMat[ 8]; + outMat[ 4] = inMat[ 1]; outMat[ 5] = inMat[ 5]; outMat[ 6] = inMat[ 9]; + outMat[ 8] = inMat[ 2]; outMat[ 9] = inMat[ 6]; outMat[10] = inMat[10]; + + v = outMat + 0; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + v = outMat + 4; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + v = outMat + 8; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + + trans[0] = inMat[ 3]; + trans[1] = inMat[ 7]; + trans[2] = inMat[11]; + + outMat[ 3] = -DotProduct(outMat + 0, trans); + outMat[ 7] = -DotProduct(outMat + 4, trans); + outMat[11] = -DotProduct(outMat + 8, trans); +} + +/* +================= +R_LoadIQM + +Load an IQM model and compute the joint matrices for every frame. +================= +*/ +bool R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_name ) { + iqmHeader_t *header; + iqmVertexArray_t *vertexarray; + iqmTriangle_t *triangle; + iqmMesh_t *mesh; + iqmJoint_t *joint; + iqmPose_t *pose; + iqmBounds_t *bounds; + unsigned short *framedata; + char *str; + int i, j; + float jointInvMats[IQM_MAX_JOINTS * 12] = {0.0f}; + float *mat, *matInv; + size_t size, joint_names; + iqmData_t *iqmData; + srfIQModel_t *surface; + char meshName[MAX_QPATH]; + byte blendIndexesType, blendWeightsType; + + if( filesize < sizeof(iqmHeader_t) ) { + return false; + } + + header = (iqmHeader_t *)buffer; + if( Q_strncmp( header->magic, IQM_MAGIC, sizeof(header->magic) ) ) { + return false; + } + + LL( header->version ); + if( header->version != IQM_VERSION ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s is a unsupported IQM version (%d), only version %d is supported.\n", + mod_name, header->version, IQM_VERSION); + return false; + } + + LL( header->filesize ); + if( header->filesize > filesize || header->filesize > 16<<20 ) { + return false; + } + + LL( header->flags ); + LL( header->num_text ); + LL( header->ofs_text ); + LL( header->num_meshes ); + LL( header->ofs_meshes ); + LL( header->num_vertexarrays ); + LL( header->num_vertexes ); + LL( header->ofs_vertexarrays ); + LL( header->num_triangles ); + LL( header->ofs_triangles ); + LL( header->ofs_adjacency ); + LL( header->num_joints ); + LL( header->ofs_joints ); + LL( header->num_poses ); + LL( header->ofs_poses ); + LL( header->num_anims ); + LL( header->ofs_anims ); + LL( header->num_frames ); + LL( header->num_framechannels ); + LL( header->ofs_frames ); + LL( header->ofs_bounds ); + LL( header->num_comment ); + LL( header->ofs_comment ); + LL( header->num_extensions ); + LL( header->ofs_extensions ); + + // check ioq3 joint limit + if ( header->num_joints > IQM_MAX_JOINTS ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %d joints (%d).\n", + mod_name, IQM_MAX_JOINTS, header->num_joints); + return false; + } + + blendIndexesType = blendWeightsType = IQM_UBYTE; + + // check and swap vertex arrays + if( IQM_CheckRange( header, header->ofs_vertexarrays, + header->num_vertexarrays, + sizeof(iqmVertexArray_t) ) ) { + return false; + } + vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays); + for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) { + int n, *intPtr; + + if( vertexarray->size <= 0 || vertexarray->size > 4 ) { + return false; + } + + // total number of values + n = header->num_vertexes * vertexarray->size; + + switch( vertexarray->format ) { + case IQM_BYTE: + case IQM_UBYTE: + // 1 byte, no swapping necessary + if( IQM_CheckRange( header, vertexarray->offset, + n, sizeof(byte) ) ) { + return false; + } + break; + case IQM_INT: + case IQM_UINT: + case IQM_FLOAT: + // 4-byte swap + if( IQM_CheckRange( header, vertexarray->offset, + n, sizeof(float) ) ) { + return false; + } + intPtr = (int *)((byte *)header + vertexarray->offset); + for( j = 0; j < n; j++, intPtr++ ) { + LL( *intPtr ); + } + break; + default: + // not supported + return false; + break; + } + + switch( vertexarray->type ) { + case IQM_POSITION: + case IQM_NORMAL: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 3 ) { + return false; + } + break; + case IQM_TANGENT: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 4 ) { + return false; + } + break; + case IQM_TEXCOORD: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 2 ) { + return false; + } + break; + case IQM_BLENDINDEXES: + if( (vertexarray->format != IQM_INT && + vertexarray->format != IQM_UBYTE) || + vertexarray->size != 4 ) { + return false; + } + blendIndexesType = vertexarray->format; + break; + case IQM_BLENDWEIGHTS: + if( (vertexarray->format != IQM_FLOAT && + vertexarray->format != IQM_UBYTE) || + vertexarray->size != 4 ) { + return false; + } + blendWeightsType = vertexarray->format; + break; + case IQM_COLOR: + if( vertexarray->format != IQM_UBYTE || + vertexarray->size != 4 ) { + return false; + } + break; + } + } + + // check and swap triangles + if( IQM_CheckRange( header, header->ofs_triangles, + header->num_triangles, sizeof(iqmTriangle_t) ) ) { + return false; + } + triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles); + for( i = 0; i < header->num_triangles; i++, triangle++ ) { + LL( triangle->vertex[0] ); + LL( triangle->vertex[1] ); + LL( triangle->vertex[2] ); + + if( triangle->vertex[0] > header->num_vertexes || + triangle->vertex[1] > header->num_vertexes || + triangle->vertex[2] > header->num_vertexes ) { + return false; + } + } + + // check and swap meshes + if( IQM_CheckRange( header, header->ofs_meshes, + header->num_meshes, sizeof(iqmMesh_t) ) ) { + return false; + } + mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes); + for( i = 0; i < header->num_meshes; i++, mesh++) { + LL( mesh->name ); + LL( mesh->material ); + LL( mesh->first_vertex ); + LL( mesh->num_vertexes ); + LL( mesh->first_triangle ); + LL( mesh->num_triangles ); + + if ( mesh->name < header->num_text ) { + Q_strncpyz( meshName, (char*)header + header->ofs_text + mesh->name, sizeof (meshName) ); + } else { + meshName[0] = '\0'; + } + + // check ioq3 limits + if ( mesh->num_vertexes >= SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i verts on %s (%i).\n", + mod_name, SHADER_MAX_VERTEXES - 1, meshName[0] ? meshName : "a surface", + mesh->num_vertexes ); + return false; + } + if ( mesh->num_triangles*3 >= SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i triangles on %s (%i).\n", + mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, meshName[0] ? meshName : "a surface", + mesh->num_triangles ); + return false; + } + + if( mesh->first_vertex >= header->num_vertexes || + mesh->first_vertex + mesh->num_vertexes > header->num_vertexes || + mesh->first_triangle >= header->num_triangles || + mesh->first_triangle + mesh->num_triangles > header->num_triangles || + mesh->name >= header->num_text || + mesh->material >= header->num_text ) { + return false; + } + } + + if( header->num_poses != header->num_joints && header->num_poses != 0 ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has %d poses and %d joints, must have the same number or 0 poses\n", + mod_name, header->num_poses, header->num_joints ); + return false; + } + + joint_names = 0; + + if ( header->num_joints ) + { + // check and swap joints + if( IQM_CheckRange( header, header->ofs_joints, + header->num_joints, sizeof(iqmJoint_t) ) ) { + return false; + } + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + LL( joint->name ); + LL( joint->parent ); + LL( joint->translate[0] ); + LL( joint->translate[1] ); + LL( joint->translate[2] ); + LL( joint->rotate[0] ); + LL( joint->rotate[1] ); + LL( joint->rotate[2] ); + LL( joint->rotate[3] ); + LL( joint->scale[0] ); + LL( joint->scale[1] ); + LL( joint->scale[2] ); + + if( joint->parent < -1 || + joint->parent >= (int)header->num_joints || + joint->name >= (int)header->num_text ) { + return false; + } + joint_names += strlen( (char *)header + header->ofs_text + + joint->name ) + 1; + } + } + + if ( header->num_poses ) + { + // check and swap poses + if( IQM_CheckRange( header, header->ofs_poses, + header->num_poses, sizeof(iqmPose_t) ) ) { + return false; + } + pose = (iqmPose_t *)((byte *)header + header->ofs_poses); + for( i = 0; i < header->num_poses; i++, pose++ ) { + LL( pose->parent ); + LL( pose->mask ); + LL( pose->channeloffset[0] ); + LL( pose->channeloffset[1] ); + LL( pose->channeloffset[2] ); + LL( pose->channeloffset[3] ); + LL( pose->channeloffset[4] ); + LL( pose->channeloffset[5] ); + LL( pose->channeloffset[6] ); + LL( pose->channeloffset[7] ); + LL( pose->channeloffset[8] ); + LL( pose->channeloffset[9] ); + LL( pose->channelscale[0] ); + LL( pose->channelscale[1] ); + LL( pose->channelscale[2] ); + LL( pose->channelscale[3] ); + LL( pose->channelscale[4] ); + LL( pose->channelscale[5] ); + LL( pose->channelscale[6] ); + LL( pose->channelscale[7] ); + LL( pose->channelscale[8] ); + LL( pose->channelscale[9] ); + } + } + + if (header->ofs_bounds) + { + // check and swap model bounds + if(IQM_CheckRange(header, header->ofs_bounds, + header->num_frames, sizeof(*bounds))) + { + return false; + } + bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds); + for(i = 0; i < header->num_frames; i++) + { + LL(bounds->bbmin[0]); + LL(bounds->bbmin[1]); + LL(bounds->bbmin[2]); + LL(bounds->bbmax[0]); + LL(bounds->bbmax[1]); + LL(bounds->bbmax[2]); + + bounds++; + } + } + + // allocate the model and copy the data + size = sizeof(iqmData_t); + size += header->num_meshes * sizeof( srfIQModel_t ); + size += header->num_joints * 12 * sizeof( float ); // joint mats + size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats + if(header->ofs_bounds) + size += header->num_frames * 6 * sizeof(float); // model bounds + size += header->num_vertexes * 3 * sizeof(float); // positions + size += header->num_vertexes * 2 * sizeof(float); // texcoords + size += header->num_vertexes * 3 * sizeof(float); // normals + size += header->num_vertexes * 4 * sizeof(float); // tangents + size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes + size += header->num_vertexes * 4 * sizeof(byte); // colors + size += header->num_joints * sizeof(int); // parents + size += header->num_triangles * 3 * sizeof(int); // triangles + size += joint_names; // joint names + + // blendWeights + if (blendWeightsType == IQM_FLOAT) { + size += header->num_vertexes * 4 * sizeof(float); + } else { + size += header->num_vertexes * 4 * sizeof(byte); + } + + mod->type = MOD_IQM; + iqmData = (iqmData_t *)ri.Hunk_Alloc( size, h_low ); + mod->modelData = iqmData; + + // fill header + iqmData->num_vertexes = header->num_vertexes; + iqmData->num_triangles = header->num_triangles; + iqmData->num_frames = header->num_frames; + iqmData->num_surfaces = header->num_meshes; + iqmData->num_joints = header->num_joints; + iqmData->num_poses = header->num_poses; + iqmData->blendWeightsType = blendWeightsType; + iqmData->surfaces = (srfIQModel_t *)(iqmData + 1); + iqmData->jointMats = (float *) (iqmData->surfaces + iqmData->num_surfaces); + iqmData->poseMats = iqmData->jointMats + 12 * header->num_joints; + if(header->ofs_bounds) + { + iqmData->bounds = iqmData->poseMats + 12 * header->num_poses * header->num_frames; + iqmData->positions = iqmData->bounds + 6 * header->num_frames; + } + else + iqmData->positions = iqmData->poseMats + 12 * header->num_poses * header->num_frames; + iqmData->texcoords = iqmData->positions + 3 * header->num_vertexes; + iqmData->normals = iqmData->texcoords + 2 * header->num_vertexes; + iqmData->tangents = iqmData->normals + 3 * header->num_vertexes; + iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes); + + if(blendWeightsType == IQM_FLOAT) { + iqmData->blendWeights.f = (float *)(iqmData->blendIndexes + 4 * header->num_vertexes); + iqmData->colors = (byte *)(iqmData->blendWeights.f + 4 * header->num_vertexes); + } else { + iqmData->blendWeights.b = iqmData->blendIndexes + 4 * header->num_vertexes; + iqmData->colors = iqmData->blendWeights.b + 4 * header->num_vertexes; + } + + iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes); + iqmData->triangles = iqmData->jointParents + header->num_joints; + iqmData->names = (char *)(iqmData->triangles + 3 * header->num_triangles); + + if ( header->num_joints == 0 ) + iqmData->jointMats = NULL; + + if ( header->num_poses == 0 ) + iqmData->poseMats = NULL; + + // calculate joint matrices and their inverses + // joint inverses are needed only until the pose matrices are calculated + mat = iqmData->jointMats; + matInv = jointInvMats; + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + float baseFrame[12], invBaseFrame[12]; + + JointToMatrix( joint->rotate, joint->scale, joint->translate, baseFrame ); + Matrix34Invert( baseFrame, invBaseFrame ); + + if ( joint->parent >= 0 ) + { + Matrix34Multiply( iqmData->jointMats + 12 * joint->parent, baseFrame, mat ); + mat += 12; + Matrix34Multiply( invBaseFrame, jointInvMats + 12 * joint->parent, matInv ); + matInv += 12; + } + else + { + Com_Memcpy( mat, baseFrame, sizeof(baseFrame) ); + mat += 12; + Com_Memcpy( matInv, invBaseFrame, sizeof(invBaseFrame) ); + matInv += 12; + } + } + + // calculate pose matrices + framedata = (unsigned short *)((byte *)header + header->ofs_frames); + mat = iqmData->poseMats; + for( i = 0; i < header->num_frames; i++ ) { + pose = (iqmPose_t *)((byte *)header + header->ofs_poses); + for( j = 0; j < header->num_poses; j++, pose++ ) { + vec3_t translate; + vec4_t rotate; + vec3_t scale; + float mat1[12], mat2[12]; + + translate[0] = pose->channeloffset[0]; + if( pose->mask & 0x001) + translate[0] += *framedata++ * pose->channelscale[0]; + translate[1] = pose->channeloffset[1]; + if( pose->mask & 0x002) + translate[1] += *framedata++ * pose->channelscale[1]; + translate[2] = pose->channeloffset[2]; + if( pose->mask & 0x004) + translate[2] += *framedata++ * pose->channelscale[2]; + + rotate[0] = pose->channeloffset[3]; + if( pose->mask & 0x008) + rotate[0] += *framedata++ * pose->channelscale[3]; + rotate[1] = pose->channeloffset[4]; + if( pose->mask & 0x010) + rotate[1] += *framedata++ * pose->channelscale[4]; + rotate[2] = pose->channeloffset[5]; + if( pose->mask & 0x020) + rotate[2] += *framedata++ * pose->channelscale[5]; + rotate[3] = pose->channeloffset[6]; + if( pose->mask & 0x040) + rotate[3] += *framedata++ * pose->channelscale[6]; + + scale[0] = pose->channeloffset[7]; + if( pose->mask & 0x080) + scale[0] += *framedata++ * pose->channelscale[7]; + scale[1] = pose->channeloffset[8]; + if( pose->mask & 0x100) + scale[1] += *framedata++ * pose->channelscale[8]; + scale[2] = pose->channeloffset[9]; + if( pose->mask & 0x200) + scale[2] += *framedata++ * pose->channelscale[9]; + + // construct transformation matrix + JointToMatrix( rotate, scale, translate, mat1 ); + + if( pose->parent >= 0 ) { + Matrix34Multiply( iqmData->jointMats + 12 * pose->parent, + mat1, mat2 ); + } else { + Com_Memcpy( mat2, mat1, sizeof(mat1) ); + } + + Matrix34Multiply( mat2, jointInvMats + 12 * j, mat ); + mat += 12; + } + } + + // register shaders + // overwrite the material offset with the shader index + mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes); + surface = iqmData->surfaces; + str = (char *)header + header->ofs_text; + for( i = 0; i < header->num_meshes; i++, mesh++, surface++ ) { + surface->surfaceType = SF_IQM; + Q_strncpyz(surface->name, str + mesh->name, sizeof (surface->name)); + Q_strlwr(surface->name); // lowercase the surface name so skin compares are faster + surface->shader = R_FindShader( str + mesh->material, LIGHTMAP_NONE, true ); + if( surface->shader->defaultShader ) + surface->shader = tr.defaultShader; + surface->data = iqmData; + surface->first_vertex = mesh->first_vertex; + surface->num_vertexes = mesh->num_vertexes; + surface->first_triangle = mesh->first_triangle; + surface->num_triangles = mesh->num_triangles; + } + + // copy vertexarrays and indexes + vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays); + for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) { + int n; + + // total number of values + n = header->num_vertexes * vertexarray->size; + + switch( vertexarray->type ) { + case IQM_POSITION: + Com_Memcpy( iqmData->positions, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_NORMAL: + Com_Memcpy( iqmData->normals, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_TANGENT: + Com_Memcpy( iqmData->tangents, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_TEXCOORD: + Com_Memcpy( iqmData->texcoords, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_BLENDINDEXES: + if( blendIndexesType == IQM_INT ) { + int *data = (int*)((byte*)header + vertexarray->offset); + for ( j = 0; j < n; j++ ) { + iqmData->blendIndexes[j] = (byte)data[j]; + } + } else { + Com_Memcpy( iqmData->blendIndexes, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + } + break; + case IQM_BLENDWEIGHTS: + if( blendWeightsType == IQM_FLOAT ) { + Com_Memcpy( iqmData->blendWeights.f, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + } else { + Com_Memcpy( iqmData->blendWeights.b, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + } + break; + case IQM_COLOR: + Com_Memcpy( iqmData->colors, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + break; + } + } + + // copy joint parents + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + iqmData->jointParents[i] = joint->parent; + } + + // copy triangles + triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles); + for( i = 0; i < header->num_triangles; i++, triangle++ ) { + iqmData->triangles[3*i+0] = triangle->vertex[0]; + iqmData->triangles[3*i+1] = triangle->vertex[1]; + iqmData->triangles[3*i+2] = triangle->vertex[2]; + } + + // copy joint names + str = iqmData->names; + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + char *name = (char *)header + header->ofs_text + + joint->name; + int len = strlen( name ) + 1; + Com_Memcpy( str, name, len ); + str += len; + } + + // copy model bounds + if(header->ofs_bounds) + { + mat = iqmData->bounds; + bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds); + for(i = 0; i < header->num_frames; i++) + { + mat[0] = bounds->bbmin[0]; + mat[1] = bounds->bbmin[1]; + mat[2] = bounds->bbmin[2]; + mat[3] = bounds->bbmax[0]; + mat[4] = bounds->bbmax[1]; + mat[5] = bounds->bbmax[2]; + + mat += 6; + bounds++; + } + } + + return true; +} + +/* +============= +R_CullIQM +============= +*/ +static int R_CullIQM( iqmData_t *data, trRefEntity_t *ent ) { + vec3_t bounds[2]; + vec_t *oldBounds, *newBounds; + int i; + + if (!data->bounds) { + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + } + + // compute bounds pointers + oldBounds = data->bounds + 6*ent->e.oldframe; + newBounds = data->bounds + 6*ent->e.frame; + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldBounds[i] < newBounds[i] ? oldBounds[i] : newBounds[i]; + bounds[1][i] = oldBounds[i+3] > newBounds[i+3] ? oldBounds[i+3] : newBounds[i+3]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_ComputeIQMFogNum + +================= +*/ +int R_ComputeIQMFogNum( iqmData_t *data, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + const vec_t *bounds; + const vec_t defaultBounds[6] = { -8, -8, -8, 8, 8, 8 }; + vec3_t diag, center; + vec3_t localOrigin; + vec_t radius; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + if (data->bounds) { + bounds = data->bounds + 6*ent->e.frame; + } else { + bounds = defaultBounds; + } + VectorSubtract( bounds+3, bounds, diag ); + VectorMA( bounds, 0.5f, diag, center ); + VectorAdd( ent->e.origin, center, localOrigin ); + radius = 0.5f * VectorLength( diag ); + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddIQMSurfaces + +Add all surfaces of this model +================= +*/ +void R_AddIQMSurfaces( trRefEntity_t *ent ) { + iqmData_t *data; + srfIQModel_t *surface; + int i, j; + bool personalModel; + int cull; + int fogNum; + int cubemapIndex; + shader_t *shader; + skin_t *skin; + + data = (iqmData_t*)tr.currentModel->modelData; + surface = data->surfaces; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal + || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))); + + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= data->num_frames; + ent->e.oldframe %= data->num_frames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= data->num_frames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= data->num_frames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddIQMSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullIQM ( data, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeIQMFogNum( data, ent ); + + cubemapIndex = R_CubemapForPoint(ent->e.origin); + + for ( i = 0 ; i < data->num_surfaces ; i++ ) { + if(ent->e.customShader) + shader = R_GetShaderByHandle( ent->e.customShader ); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j].name, surface->name)) + { + shader = skin->surfaces[j].shader; + break; + } + } + } else { + shader = surface->shader; + } + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t*)surface, tr.shadowShader, 0, 0, 0, 0 ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t*)surface, tr.projectionShadowShader, 0, 0, 0, 0 ); + } + + if( !personalModel ) { + R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, 0, 0, cubemapIndex ); + } + + surface++; + } +} + + +static void ComputePoseMats( iqmData_t *data, int frame, int oldframe, + float backlerp, float *mat ) { + float *mat1, *mat2; + int *joint = data->jointParents; + int i; + + if ( data->num_poses == 0 ) { + for( i = 0; i < data->num_joints; i++, joint++ ) { + if( *joint >= 0 ) { + Matrix34Multiply( mat + 12 * *joint, + identityMatrix, mat + 12*i ); + } else { + Com_Memcpy( mat + 12*i, identityMatrix, 12 * sizeof(float) ); + } + } + return; + } + + if ( oldframe == frame ) { + mat1 = data->poseMats + 12 * data->num_poses * frame; + for( i = 0; i < data->num_poses; i++, joint++ ) { + if( *joint >= 0 ) { + Matrix34Multiply( mat + 12 * *joint, + mat1 + 12*i, mat + 12*i ); + } else { + Com_Memcpy( mat + 12*i, mat1 + 12*i, 12 * sizeof(float) ); + } + } + } else { + mat1 = data->poseMats + 12 * data->num_poses * frame; + mat2 = data->poseMats + 12 * data->num_poses * oldframe; + + for( i = 0; i < data->num_poses; i++, joint++ ) { + if( *joint >= 0 ) { + float tmpMat[12]; + InterpolateMatrix( mat1 + 12*i, mat2 + 12*i, + backlerp, tmpMat ); + Matrix34Multiply( mat + 12 * *joint, + tmpMat, mat + 12*i ); + + } else { + InterpolateMatrix( mat1 + 12*i, mat2 + 12*i, + backlerp, mat ); + } + } + } +} + +static void ComputeJointMats( iqmData_t *data, int frame, int oldframe, + float backlerp, float *mat ) { + float *mat1; + int i; + + ComputePoseMats( data, frame, oldframe, backlerp, mat ); + + for( i = 0; i < data->num_joints; i++ ) { + float outmat[12]; + mat1 = mat + 12 * i; + + Com_Memcpy(outmat, mat1, sizeof(outmat)); + + Matrix34Multiply_OnlySetOrigin( outmat, data->jointMats + 12 * i, mat1 ); + } +} + + +/* +================= +RB_AddIQMSurfaces + +Compute vertices for this model surface +================= +*/ +void RB_IQMSurfaceAnim( surfaceType_t *surface ) { + srfIQModel_t *surf = (srfIQModel_t *)surface; + iqmData_t *data = surf->data; + float jointMats[IQM_MAX_JOINTS * 12]; + int i; + + vec4_t *outXYZ; + int16_t *outNormal; + int16_t *outTangent; + vec2_t *outTexCoord; + uint16_t *outColor; + + int frame = data->num_frames ? backEnd.currentEntity->e.frame % data->num_frames : 0; + int oldframe = data->num_frames ? backEnd.currentEntity->e.oldframe % data->num_frames : 0; + float backlerp = backEnd.currentEntity->e.backlerp; + + int *tri; + glIndex_t *ptr; + glIndex_t base; + + RB_CHECKOVERFLOW( surf->num_vertexes, surf->num_triangles * 3 ); + + outXYZ = &tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + outTangent = tess.tangent[tess.numVertexes]; + outTexCoord = &tess.texCoords[tess.numVertexes]; + outColor = tess.color[tess.numVertexes]; + + // compute interpolated joint matrices + if ( data->num_poses > 0 ) { + ComputePoseMats( data, frame, oldframe, backlerp, jointMats ); + } + + // transform vertexes and fill other data + for( i = 0; i < surf->num_vertexes; + i++, outXYZ++, outNormal+=4, outTexCoord++, outColor+=4 ) { + int j, k; + float vtxMat[12]; + float nrmMat[9]; + int vtx = i + surf->first_vertex; + float blendWeights[4]; + int numWeights; + + for ( numWeights = 0; numWeights < 4; numWeights++ ) { + if ( data->blendWeightsType == IQM_FLOAT ) + blendWeights[numWeights] = data->blendWeights.f[4*vtx + numWeights]; + else + blendWeights[numWeights] = (float)data->blendWeights.b[4*vtx + numWeights] / 255.0f; + + if ( blendWeights[numWeights] <= 0 ) + break; + } + + if ( data->num_poses == 0 || numWeights == 0 ) { + // no blend joint, use identity matrix. + Com_Memcpy( vtxMat, identityMatrix, 12 * sizeof (float) ); + } else { + // compute the vertex matrix by blending the up to + // four blend weights + Com_Memset( vtxMat, 0, 12 * sizeof (float) ); + for( j = 0; j < numWeights; j++ ) { + for( k = 0; k < 12; k++ ) { + vtxMat[k] += blendWeights[j] * jointMats[12*data->blendIndexes[4*vtx + j] + k]; + } + } + } + + // compute the normal matrix as transpose of the adjoint + // of the vertex matrix + nrmMat[ 0] = vtxMat[ 5]*vtxMat[10] - vtxMat[ 6]*vtxMat[ 9]; + nrmMat[ 1] = vtxMat[ 6]*vtxMat[ 8] - vtxMat[ 4]*vtxMat[10]; + nrmMat[ 2] = vtxMat[ 4]*vtxMat[ 9] - vtxMat[ 5]*vtxMat[ 8]; + nrmMat[ 3] = vtxMat[ 2]*vtxMat[ 9] - vtxMat[ 1]*vtxMat[10]; + nrmMat[ 4] = vtxMat[ 0]*vtxMat[10] - vtxMat[ 2]*vtxMat[ 8]; + nrmMat[ 5] = vtxMat[ 1]*vtxMat[ 8] - vtxMat[ 0]*vtxMat[ 9]; + nrmMat[ 6] = vtxMat[ 1]*vtxMat[ 6] - vtxMat[ 2]*vtxMat[ 5]; + nrmMat[ 7] = vtxMat[ 2]*vtxMat[ 4] - vtxMat[ 0]*vtxMat[ 6]; + nrmMat[ 8] = vtxMat[ 0]*vtxMat[ 5] - vtxMat[ 1]*vtxMat[ 4]; + + (*outTexCoord)[0] = data->texcoords[2*vtx + 0]; + (*outTexCoord)[1] = data->texcoords[2*vtx + 1]; + + (*outXYZ)[0] = + vtxMat[ 0] * data->positions[3*vtx+0] + + vtxMat[ 1] * data->positions[3*vtx+1] + + vtxMat[ 2] * data->positions[3*vtx+2] + + vtxMat[ 3]; + (*outXYZ)[1] = + vtxMat[ 4] * data->positions[3*vtx+0] + + vtxMat[ 5] * data->positions[3*vtx+1] + + vtxMat[ 6] * data->positions[3*vtx+2] + + vtxMat[ 7]; + (*outXYZ)[2] = + vtxMat[ 8] * data->positions[3*vtx+0] + + vtxMat[ 9] * data->positions[3*vtx+1] + + vtxMat[10] * data->positions[3*vtx+2] + + vtxMat[11]; + (*outXYZ)[3] = 1.0f; + + { + vec3_t normal; + vec4_t tangent; + + normal[0] = DotProduct(&nrmMat[0], &data->normals[3*vtx]); + normal[1] = DotProduct(&nrmMat[3], &data->normals[3*vtx]); + normal[2] = DotProduct(&nrmMat[6], &data->normals[3*vtx]); + + R_VaoPackNormal(outNormal, normal); + + tangent[0] = DotProduct(&nrmMat[0], &data->tangents[4*vtx]); + tangent[1] = DotProduct(&nrmMat[3], &data->tangents[4*vtx]); + tangent[2] = DotProduct(&nrmMat[6], &data->tangents[4*vtx]); + tangent[3] = data->tangents[4*vtx+3]; + + R_VaoPackTangent(outTangent, tangent); + outTangent+=4; + } + + outColor[0] = data->colors[4*vtx+0] * 257; + outColor[1] = data->colors[4*vtx+1] * 257; + outColor[2] = data->colors[4*vtx+2] * 257; + outColor[3] = data->colors[4*vtx+3] * 257; + } + + tri = data->triangles + 3 * surf->first_triangle; + ptr = &tess.indexes[tess.numIndexes]; + base = tess.numVertexes; + + for( i = 0; i < surf->num_triangles; i++ ) { + *ptr++ = base + (*tri++ - surf->first_vertex); + *ptr++ = base + (*tri++ - surf->first_vertex); + *ptr++ = base + (*tri++ - surf->first_vertex); + } + + tess.numIndexes += 3 * surf->num_triangles; + tess.numVertexes += surf->num_vertexes; +} + +int R_IQMLerpTag( orientation_t *tag, iqmData_t *data, + int startFrame, int endFrame, + float frac, const char *tagName ) { + float jointMats[IQM_MAX_JOINTS * 12]; + int joint; + char *names = data->names; + + // get joint number by reading the joint names + for( joint = 0; joint < data->num_joints; joint++ ) { + if( !strcmp( tagName, names ) ) + break; + names += strlen( names ) + 1; + } + if( joint >= data->num_joints ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return false; + } + + ComputeJointMats( data, startFrame, endFrame, frac, jointMats ); + + tag->axis[0][0] = jointMats[12 * joint + 0]; + tag->axis[1][0] = jointMats[12 * joint + 1]; + tag->axis[2][0] = jointMats[12 * joint + 2]; + tag->origin[0] = jointMats[12 * joint + 3]; + tag->axis[0][1] = jointMats[12 * joint + 4]; + tag->axis[1][1] = jointMats[12 * joint + 5]; + tag->axis[2][1] = jointMats[12 * joint + 6]; + tag->origin[1] = jointMats[12 * joint + 7]; + tag->axis[0][2] = jointMats[12 * joint + 8]; + tag->axis[1][2] = jointMats[12 * joint + 9]; + tag->axis[2][2] = jointMats[12 * joint + 10]; + tag->origin[2] = jointMats[12 * joint + 11]; + + return true; +} diff --git a/src/renderergl2/tr_postprocess.cpp b/src/renderergl2/tr_postprocess.cpp new file mode 100644 index 0000000..7d00d01 --- /dev/null +++ b/src/renderergl2/tr_postprocess.cpp @@ -0,0 +1,484 @@ +/* +=========================================================================== +Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +void RB_ToneMap(FBO_t *hdrFbo, ivec4_t hdrBox, FBO_t *ldrFbo, ivec4_t ldrBox, int autoExposure) +{ + ivec4_t srcBox, dstBox; + vec4_t color; + static int lastFrameCount = 0; + + if (autoExposure) + { + if (lastFrameCount == 0 || tr.frameCount < lastFrameCount || tr.frameCount - lastFrameCount > 5) + { + // determine average log luminance + FBO_t *srcFbo, *dstFbo, *tmp; + int size = 256; + + lastFrameCount = tr.frameCount; + + VectorSet4(dstBox, 0, 0, size, size); + + FBO_Blit(hdrFbo, hdrBox, NULL, tr.textureScratchFbo[0], dstBox, &tr.calclevels4xShader[0], NULL, 0); + + srcFbo = tr.textureScratchFbo[0]; + dstFbo = tr.textureScratchFbo[1]; + + // downscale to 1x1 texture + while (size > 1) + { + VectorSet4(srcBox, 0, 0, size, size); + //size >>= 2; + size >>= 1; + VectorSet4(dstBox, 0, 0, size, size); + + if (size == 1) + dstFbo = tr.targetLevelsFbo; + + //FBO_Blit(targetFbo, srcBox, NULL, tr.textureScratchFbo[nextScratch], dstBox, &tr.calclevels4xShader[1], NULL, 0); + FBO_FastBlit(srcFbo, srcBox, dstFbo, dstBox, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + tmp = srcFbo; + srcFbo = dstFbo; + dstFbo = tmp; + } + } + + // blend with old log luminance for gradual change + VectorSet4(srcBox, 0, 0, 0, 0); + + color[0] = + color[1] = + color[2] = 1.0f; + if (glRefConfig.textureFloat) + color[3] = 0.03f; + else + color[3] = 0.1f; + + FBO_Blit(tr.targetLevelsFbo, srcBox, NULL, tr.calcLevelsFbo, NULL, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + + // tonemap + color[0] = + color[1] = + color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value); + color[3] = 1.0f; + + if (autoExposure) + GL_BindToTMU(tr.calcLevelsImage, TB_LEVELSMAP); + else + GL_BindToTMU(tr.fixedLevelsImage, TB_LEVELSMAP); + + FBO_Blit(hdrFbo, hdrBox, NULL, ldrFbo, ldrBox, &tr.tonemapShader, color, 0); +} + +/* +============= +RB_BokehBlur + + +Blurs a part of one framebuffer to another. + +Framebuffers can be identical. +============= +*/ +void RB_BokehBlur(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, float blur) +{ +// ivec4_t srcBox, dstBox; + vec4_t color; + + blur *= 10.0f; + + if (blur < 0.004f) + return; + + if (glRefConfig.framebufferObject) + { + // bokeh blur + if (blur > 0.0f) + { + ivec4_t quarterBox; + + quarterBox[0] = 0; + quarterBox[1] = tr.quarterFbo[0]->height; + quarterBox[2] = tr.quarterFbo[0]->width; + quarterBox[3] = -tr.quarterFbo[0]->height; + + // create a quarter texture + //FBO_Blit(NULL, NULL, NULL, tr.quarterFbo[0], NULL, NULL, NULL, 0); + FBO_FastBlit(src, srcBox, tr.quarterFbo[0], quarterBox, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + +#ifndef HQ_BLUR + if (blur > 1.0f) + { + // create a 1/16th texture + //FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.textureScratchFbo[0], NULL, NULL, NULL, 0); + FBO_FastBlit(tr.quarterFbo[0], NULL, tr.textureScratchFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } +#endif + + if (blur > 0.0f && blur <= 1.0f) + { + // Crossfade original with quarter texture + VectorSet4(color, 1, 1, 1, blur); + + FBO_Blit(tr.quarterFbo[0], NULL, NULL, dst, dstBox, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } +#ifndef HQ_BLUR + // ok blur, but can see some pixelization + else if (blur > 1.0f && blur <= 2.0f) + { + // crossfade quarter texture with 1/16th texture + FBO_Blit(tr.quarterFbo[0], NULL, NULL, dst, dstBox, NULL, NULL, 0); + + VectorSet4(color, 1, 1, 1, blur - 1.0f); + + FBO_Blit(tr.textureScratchFbo[0], NULL, NULL, dst, dstBox, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + else if (blur > 2.0f) + { + // blur 1/16th texture then replace + int i; + + for (i = 0; i < 2; i++) + { + vec2_t blurTexScale; + float subblur; + + subblur = ((blur - 2.0f) / 2.0f) / 3.0f * (float)(i + 1); + + blurTexScale[0] = + blurTexScale[1] = subblur; + + color[0] = + color[1] = + color[2] = 0.5f; + color[3] = 1.0f; + + if (i != 0) + FBO_Blit(tr.textureScratchFbo[0], NULL, blurTexScale, tr.textureScratchFbo[1], NULL, &tr.bokehShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + else + FBO_Blit(tr.textureScratchFbo[0], NULL, blurTexScale, tr.textureScratchFbo[1], NULL, &tr.bokehShader, color, 0); + } + + FBO_Blit(tr.textureScratchFbo[1], NULL, NULL, dst, dstBox, NULL, NULL, 0); + } +#else // higher quality blur, but slower + else if (blur > 1.0f) + { + // blur quarter texture then replace + int i; + + src = tr.quarterFbo[0]; + dst = tr.quarterFbo[1]; + + VectorSet4(color, 0.5f, 0.5f, 0.5f, 1); + + for (i = 0; i < 2; i++) + { + vec2_t blurTexScale; + float subblur; + + subblur = (blur - 1.0f) / 2.0f * (float)(i + 1); + + blurTexScale[0] = + blurTexScale[1] = subblur; + + color[0] = + color[1] = + color[2] = 1.0f; + if (i != 0) + color[3] = 1.0f; + else + color[3] = 0.5f; + + FBO_Blit(tr.quarterFbo[0], NULL, blurTexScale, tr.quarterFbo[1], NULL, &tr.bokehShader, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + + FBO_Blit(tr.quarterFbo[1], NULL, NULL, dst, dstBox, NULL, NULL, 0); + } +#endif + } +} + + +static void RB_RadialBlur(FBO_t *srcFbo, FBO_t *dstFbo, int passes, float stretch, float x, float y, float w, float h, float xcenter, float ycenter, float alpha) +{ + ivec4_t srcBox, dstBox; + int srcWidth, srcHeight; + vec4_t color; + const float inc = 1.f / passes; + const float mul = powf(stretch, inc); + float scale; + + alpha *= inc; + VectorSet4(color, alpha, alpha, alpha, 1.0f); + + srcWidth = srcFbo ? srcFbo->width : glConfig.vidWidth; + srcHeight = srcFbo ? srcFbo->height : glConfig.vidHeight; + + VectorSet4(srcBox, 0, 0, srcWidth, srcHeight); + + VectorSet4(dstBox, x, y, w, h); + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, 0); + + --passes; + scale = mul; + while (passes > 0) + { + float iscale = 1.f / scale; + float s0 = xcenter * (1.f - iscale); + float t0 = (1.0f - ycenter) * (1.f - iscale); + + srcBox[0] = s0 * srcWidth; + srcBox[1] = t0 * srcHeight; + srcBox[2] = iscale * srcWidth; + srcBox[3] = iscale * srcHeight; + + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + scale *= mul; + --passes; + } +} + + +static bool RB_UpdateSunFlareVis(void) +{ + GLuint sampleCount = 0; + if (!glRefConfig.occlusionQuery) + return true; + + tr.sunFlareQueryIndex ^= 1; + if (!tr.sunFlareQueryActive[tr.sunFlareQueryIndex]) + return true; + + /* debug code */ + if (0) + { + int iter; + for (iter=0 ; ; ++iter) + { + GLint available = 0; + qglGetQueryObjectiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT_AVAILABLE, &available); + if (available) + break; + } + + ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter); + } + + qglGetQueryObjectuiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT, &sampleCount); + return sampleCount > 0; +} + +void RB_SunRays(FBO_t *srcFbo, ivec4_t srcBox, FBO_t *dstFbo, ivec4_t dstBox) +{ + vec4_t color; + float dot; + const float cutoff = 0.25f; + bool colorize = true; + +// float w, h, w2, h2; + mat4_t mvp; + vec4_t pos, hpos; + + dot = DotProduct(tr.sunDirection, backEnd.viewParms.orientation.axis[0]); + if (dot < cutoff) + return; + + if (!RB_UpdateSunFlareVis()) + return; + + // From RB_DrawSun() + { + float dist; + mat4_t trans, model; + + Mat4Translation( backEnd.viewParms.orientation.origin, trans ); + Mat4Multiply( backEnd.viewParms.world.modelMatrix, trans, model ); + Mat4Multiply(backEnd.viewParms.projectionMatrix, model, mvp); + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + + VectorScale( tr.sunDirection, dist, pos ); + } + + // project sun point + //Mat4Multiply(backEnd.viewParms.projectionMatrix, backEnd.viewParms.world.modelMatrix, mvp); + Mat4Transform(mvp, pos, hpos); + + // transform to UV coords + hpos[3] = 0.5f / hpos[3]; + + pos[0] = 0.5f + hpos[0] * hpos[3]; + pos[1] = 0.5f + hpos[1] * hpos[3]; + + // initialize quarter buffers + { + float mul = 1.f; + ivec4_t rayBox, quarterBox; + int srcWidth = srcFbo ? srcFbo->width : glConfig.vidWidth; + int srcHeight = srcFbo ? srcFbo->height : glConfig.vidHeight; + + VectorSet4(color, mul, mul, mul, 1); + + rayBox[0] = srcBox[0] * tr.sunRaysFbo->width / srcWidth; + rayBox[1] = srcBox[1] * tr.sunRaysFbo->height / srcHeight; + rayBox[2] = srcBox[2] * tr.sunRaysFbo->width / srcWidth; + rayBox[3] = srcBox[3] * tr.sunRaysFbo->height / srcHeight; + + quarterBox[0] = 0; + quarterBox[1] = tr.quarterFbo[0]->height; + quarterBox[2] = tr.quarterFbo[0]->width; + quarterBox[3] = -tr.quarterFbo[0]->height; + + // first, downsample the framebuffer + if (colorize) + { + FBO_FastBlit(srcFbo, srcBox, tr.quarterFbo[0], quarterBox, GL_COLOR_BUFFER_BIT, GL_LINEAR); + FBO_Blit(tr.sunRaysFbo, rayBox, NULL, tr.quarterFbo[0], quarterBox, NULL, color, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO); + } + else + { + FBO_FastBlit(tr.sunRaysFbo, rayBox, tr.quarterFbo[0], quarterBox, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + } + + // radial blur passes, ping-ponging between the two quarter-size buffers + { + const float stretch_add = 2.f/3.f; + float stretch = 1.f + stretch_add; + int i; + for (i=0; i<2; ++i) + { + RB_RadialBlur(tr.quarterFbo[i&1], tr.quarterFbo[(~i) & 1], 5, stretch, 0.f, 0.f, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height, pos[0], pos[1], 1.125f); + stretch += stretch_add; + } + } + + // add result back on top of the main buffer + { + float mul = 1.f; + + VectorSet4(color, mul, mul, mul, 1); + + FBO_Blit(tr.quarterFbo[0], NULL, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + } +} + +static void RB_BlurAxis(FBO_t *srcFbo, FBO_t *dstFbo, float strength, bool horizontal) +{ + float dx, dy; + float xmul, ymul; + float weights[3] = { + 0.227027027f, + 0.316216216f, + 0.070270270f, + }; + float offsets[3] = { + 0.f, + 1.3846153846f, + 3.2307692308f, + }; + + xmul = horizontal; + ymul = 1.f - xmul; + + xmul *= strength; + ymul *= strength; + + { + ivec4_t srcBox, dstBox; + vec4_t color; + + VectorSet4(color, weights[0], weights[0], weights[0], 1.0f); + VectorSet4(srcBox, 0, 0, srcFbo->width, srcFbo->height); + VectorSet4(dstBox, 0, 0, dstFbo->width, dstFbo->height); + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, 0); + + VectorSet4(color, weights[1], weights[1], weights[1], 1.0f); + dx = offsets[1] * xmul; + dy = offsets[1] * ymul; + VectorSet4(srcBox, dx, dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + + VectorSet4(color, weights[2], weights[2], weights[2], 1.0f); + dx = offsets[2] * xmul; + dy = offsets[2] * ymul; + VectorSet4(srcBox, dx, dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + } +} + +static void RB_HBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength) +{ + RB_BlurAxis(srcFbo, dstFbo, strength, true); +} + +static void RB_VBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength) +{ + RB_BlurAxis(srcFbo, dstFbo, strength, false); +} + +void RB_GaussianBlur(float blur) +{ + //float mul = 1.f; + float factor = Com_Clamp(0.f, 1.f, blur); + + if (factor <= 0.f) + return; + + { + ivec4_t srcBox, dstBox; + vec4_t color; + + VectorSet4(color, 1, 1, 1, 1); + + // first, downsample the framebuffer + FBO_FastBlit(NULL, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR); + FBO_FastBlit(tr.quarterFbo[0], NULL, tr.textureScratchFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // set the alpha channel + qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + FBO_BlitFromTexture(tr.whiteImage, NULL, NULL, tr.textureScratchFbo[0], NULL, NULL, color, GLS_DEPTHTEST_DISABLE); + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + // blur the tiny buffer horizontally and vertically + RB_HBlur(tr.textureScratchFbo[0], tr.textureScratchFbo[1], factor); + RB_VBlur(tr.textureScratchFbo[1], tr.textureScratchFbo[0], factor); + + // finally, merge back to framebuffer + VectorSet4(srcBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height); + VectorSet4(dstBox, 0, 0, glConfig.vidWidth, glConfig.vidHeight); + color[3] = factor; + FBO_Blit(tr.textureScratchFbo[0], srcBox, NULL, NULL, dstBox, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } +} diff --git a/src/renderergl2/tr_postprocess.h b/src/renderergl2/tr_postprocess.h new file mode 100644 index 0000000..de00b88 --- /dev/null +++ b/src/renderergl2/tr_postprocess.h @@ -0,0 +1,34 @@ +/* +=========================================================================== +Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef TR_POSTPROCESS_H +#define TR_POSTPROCESS_H + +#include "tr_fbo.h" + +void RB_ToneMap(FBO_t *hdrFbo, ivec4_t hdrBox, FBO_t *ldrFbo, ivec4_t ldrBox, int autoExposure); +void RB_BokehBlur(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, float blur); +void RB_SunRays(FBO_t *srcFbo, ivec4_t srcBox, FBO_t *dstFbo, ivec4_t dstBox); +void RB_GaussianBlur(float blur); + +#endif diff --git a/src/renderergl2/tr_scene.cpp b/src/renderergl2/tr_scene.cpp new file mode 100644 index 0000000..67ab0d3 --- /dev/null +++ b/src/renderergl2/tr_scene.cpp @@ -0,0 +1,575 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "tr_local.h" + +int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +int r_numentities; +int r_firstSceneEntity; + +int r_numpolys; +int r_firstScenePoly; + +int r_numpolyverts; + + +/* +==================== +R_InitNextFrame + +==================== +*/ +void R_InitNextFrame( void ) { + backEndData->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + + r_numentities = 0; + r_firstSceneEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; + int fogMask; + + tr.currentEntityNum = REFENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + fogMask = -((tr.refdef.rdflags & RDF_NOFOG) == 0); + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( (surfaceType_t*)poly, sh, poly->fogIndex & fogMask, false, false, 0 /*cubeMap*/ ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + srfPoly_t *poly; + int i, j; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + // This isn't a useful warning, and an hShader of zero isn't a null shader, it's + // the default shader. + //ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n"); + //return; + } + + for ( j = 0; j < numPolys; j++ ) { + if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) { + /* + NOTE TTimo this was initially a PRINT_WARNING + but it happens a lot with high fighting scenes and particles + since we don't plan on changing the const and making for room for those effects + simply cut this message to developer only + */ + ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n"); + return; + } + + poly = &backEndData->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData->polyVerts[r_numpolyverts]; + + Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) ); + + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + poly->verts->modulate[0] = 255; + poly->verts->modulate[1] = 255; + poly->verts->modulate[2] = 255; + poly->verts->modulate[3] = 255; + } + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // if no world is loaded + if ( tr.world == NULL ) { + fogIndex = 0; + } + // see if it is in a fog volume + else if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; + } +} + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { + vec3_t cross; + + if ( !tr.registered ) { + return; + } + if ( r_numentities >= MAX_REFENTITIES ) { + ri.Printf(PRINT_DEVELOPER, "RE_AddRefEntityToScene: Dropping refEntity, reached MAX_REFENTITIES\n"); + return; + } + if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) { + static bool firstTime = true; + if (firstTime) { + firstTime = false; + ri.Printf( PRINT_WARNING, "RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n"); + } + return; + } + if ( (int)ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData->entities[r_numentities].e = *ent; + backEndData->entities[r_numentities].lightingCalculated = false; + + CrossProduct(ent->axis[0], ent->axis[1], cross); + backEndData->entities[r_numentities].mirrored = (DotProduct(ent->axis[2], cross) < 0.f); + + r_numentities++; +} + + +/* +===================== +RE_AddDynamicLightToScene + +===================== +*/ +void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) { + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + // these cards don't have the correct blend mode + if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + dl = &backEndData->dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; + dl->additive = additive; +} + +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, false ); +} + +/* +===================== +RE_AddAdditiveLightToScene + +===================== +*/ +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, true ); +} + + +void RE_BeginScene(const refdef_t *fd) +{ + Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.rdflags = fd->rdflags; + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = false; + if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) { + areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i]; + ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = true; + } + } + + tr.refdef.sunDir[3] = 0.0f; + tr.refdef.sunCol[3] = 1.0f; + tr.refdef.sunAmbCol[3] = 1.0f; + + VectorCopy(tr.sunDirection, tr.refdef.sunDir); + if ( (tr.refdef.rdflags & RDF_NOWORLDMODEL) || !(r_depthPrepass->value) ){ + VectorSet(tr.refdef.sunCol, 0, 0, 0); + VectorSet(tr.refdef.sunAmbCol, 0, 0, 0); + } + else + { + float scale = (1 << r_mapOverBrightBits->integer) / 255.0f; + + if (r_forceSun->integer) + VectorScale(tr.sunLight, scale * r_forceSunLightScale->value, tr.refdef.sunCol); + else + VectorScale(tr.sunLight, scale, tr.refdef.sunCol); + + if (r_sunlightMode->integer == 1) + { + tr.refdef.sunAmbCol[0] = + tr.refdef.sunAmbCol[1] = + tr.refdef.sunAmbCol[2] = r_forceSun->integer ? r_forceSunAmbientScale->value : tr.sunShadowScale; + } + else + { + if (r_forceSun->integer) + VectorScale(tr.sunLight, scale * r_forceSunAmbientScale->value, tr.refdef.sunAmbCol); + else + VectorScale(tr.sunLight, scale * tr.sunShadowScale, tr.refdef.sunAmbCol); + } + } + + if (r_forceAutoExposure->integer) + { + tr.refdef.autoExposureMinMax[0] = r_forceAutoExposureMin->value; + tr.refdef.autoExposureMinMax[1] = r_forceAutoExposureMax->value; + } + else + { + tr.refdef.autoExposureMinMax[0] = tr.autoExposureMinMax[0]; + tr.refdef.autoExposureMinMax[1] = tr.autoExposureMinMax[1]; + } + + if (r_forceToneMap->integer) + { + tr.refdef.toneMinAvgMaxLinear[0] = pow(2, r_forceToneMapMin->value); + tr.refdef.toneMinAvgMaxLinear[1] = pow(2, r_forceToneMapAvg->value); + tr.refdef.toneMinAvgMaxLinear[2] = pow(2, r_forceToneMapMax->value); + } + else + { + tr.refdef.toneMinAvgMaxLinear[0] = pow(2, tr.toneMinAvgMaxLevel[0]); + tr.refdef.toneMinAvgMaxLinear[1] = pow(2, tr.toneMinAvgMaxLevel[1]); + tr.refdef.toneMinAvgMaxLinear[2] = pow(2, tr.toneMinAvgMaxLevel[2]); + } + + // Makro - copy exta info if present + if (fd->rdflags & RDF_EXTRA) { + const refdefex_t* extra = (const refdefex_t*) (fd+1); + + tr.refdef.blurFactor = extra->blurFactor; + + if (fd->rdflags & RDF_SUNLIGHT) + { + VectorCopy(extra->sunDir, tr.refdef.sunDir); + VectorCopy(extra->sunCol, tr.refdef.sunCol); + VectorCopy(extra->sunAmbCol, tr.refdef.sunAmbCol); + } + } + else + { + tr.refdef.blurFactor = 0.0f; + } + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData->entities[r_firstSceneEntity]; + + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData->dlights[r_firstSceneDlight]; + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData->polys[r_firstScenePoly]; + + tr.refdef.num_pshadows = 0; + tr.refdef.pshadows = &backEndData->pshadows[0]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled + if ( r_dynamiclight->integer == 0 || + r_vertexLight->integer == 1 || + glConfig.hardwareType == GLHW_PERMEDIA2 ) { + tr.refdef.num_dlights = 0; + } + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; +} + + +void RE_EndScene() +{ + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; + r_firstSceneEntity = r_numentities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; +} + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = ri.Milliseconds(); + + if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel"); + } + + RE_BeginScene(fd); + + // SmileTheory: playing with shadow mapping + if (!( fd->rdflags & RDF_NOWORLDMODEL ) && tr.refdef.num_dlights && r_dlightMode->integer >= 2) + { + R_RenderDlightCubemaps(fd); + } + + /* playing with more shadows */ + if(glRefConfig.framebufferObject && !( fd->rdflags & RDF_NOWORLDMODEL ) && r_shadows->integer == 4) + { + R_RenderPshadowMaps(fd); + } + + // playing with even more shadows + if(glRefConfig.framebufferObject && r_sunlightMode->integer && !( fd->rdflags & RDF_NOWORLDMODEL ) && (r_forceSun->integer || tr.sunShadows)) + { + if (r_shadowCascadeZFar->integer != 0) + { + R_RenderSunShadowMaps(fd, 0); + R_RenderSunShadowMaps(fd, 1); + R_RenderSunShadowMaps(fd, 2); + } + else + { + Mat4Zero(tr.refdef.sunShadowMvp[0]); + Mat4Zero(tr.refdef.sunShadowMvp[1]); + Mat4Zero(tr.refdef.sunShadowMvp[2]); + } + + // only rerender last cascade if sun has changed position + if (r_forceSun->integer == 2 || !VectorCompare(tr.refdef.sunDir, tr.lastCascadeSunDirection)) + { + VectorCopy(tr.refdef.sunDir, tr.lastCascadeSunDirection); + R_RenderSunShadowMaps(fd, 3); + Mat4Copy(tr.refdef.sunShadowMvp[3], tr.lastCascadeSunMvp); + } + else + { + Mat4Copy(tr.lastCascadeSunMvp, tr.refdef.sunShadowMvp[3]); + } + } + + // playing with cube maps + // this is where dynamic cubemaps would be rendered + if (0) //(glRefConfig.framebufferObject && !( fd->rdflags & RDF_NOWORLDMODEL )) + { + int i, j; + + for (i = 0; i < tr.numCubemaps; i++) + { + for (j = 0; j < 6; j++) + { + R_RenderCubemapSide(i, j, true); + } + } + } + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + Com_Memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = false; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + parms.stereoFrame = tr.refdef.stereoFrame; + + VectorCopy( fd->vieworg, parms.orientation.origin ); + VectorCopy( fd->viewaxis[0], parms.orientation.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.orientation.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.orientation.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + if(!( fd->rdflags & RDF_NOWORLDMODEL ) && r_depthPrepass->value && ((r_forceSun->integer) || tr.sunShadows)) + { + parms.flags = VPF_USESUNLIGHT; + } + + R_RenderView( &parms ); + + if(!( fd->rdflags & RDF_NOWORLDMODEL )) + R_AddPostProcessCmd(); + + RE_EndScene(); + + tr.frontEndMsec += ri.Milliseconds() - startTime; +} diff --git a/src/renderergl2/tr_shade.cpp b/src/renderergl2/tr_shade.cpp new file mode 100644 index 0000000..8f9e871 --- /dev/null +++ b/src/renderergl2/tr_shade.cpp @@ -0,0 +1,1634 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_shade.c + +#include "tr_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + + +/* +================== +R_DrawElements + +================== +*/ + +void R_DrawElements( int numIndexes, glIndex_t firstIndex) +{ + qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t))); +} + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + +shaderCommands_t tess; + + +/* +================= +R_BindAnimatedImageToTMU + +================= +*/ +static void R_BindAnimatedImageToTMU( textureBundle_t *bundle, int tmu ) { + + if ( bundle->isVideoMap ) { + ri.CIN_RunCinematic(bundle->videoMapHandle); + ri.CIN_UploadCinematic(bundle->videoMapHandle); + GL_BindToTMU(tr.scratchImage[bundle->videoMapHandle], tmu); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + GL_BindToTMU( bundle->image[0], tmu); + return; + } + + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + int i = static_cast(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE) >> FUNCTABLE_SIZE2; + + if ( i < 0 ) + { + i = 0; // may happen with shader time offsets + } + i %= bundle->numImageAnimations; + + GL_BindToTMU( bundle->image[ i ], tmu ); +} + + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris (shaderCommands_t *input) { + GL_BindToTMU( tr.whiteImage, TB_COLORMAP ); + + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + { + shaderProgram_t *sp = &tr.textureColorShader; + vec4_t color; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + VectorSet4(color, 1, 1, 1, 1); + GLSL_SetUniformVec4(sp, UNIFORM_COLOR, color); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + R_DrawElements(input->numIndexes, input->firstIndex); + } + + qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { + //FIXME: implement this +} + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum, int cubemapIndex ) { + + shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader; + + tess.numIndexes = 0; + tess.firstIndex = 0; + tess.numVertexes = 0; + tess.shader = state; + tess.fogNum = fogNum; + tess.cubemapIndex = cubemapIndex; + tess.dlightBits = 0; // will be OR'd in by surface functions + tess.pshadowBits = 0; // will be OR'd in by surface functions + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + tess.currentStageIteratorFunc = state->optimalStageIteratorFunc; + tess.useInternalVao = true; + tess.useCacheVao = false; + + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) { + tess.shaderTime = tess.shader->clampTime; + } + + if (backEnd.viewParms.flags & VPF_SHADOWMAP) + { + tess.currentStageIteratorFunc = RB_StageIteratorGeneric; + } +} + + + +extern float EvalWaveForm( const waveForm_t *wf ); +extern float EvalWaveFormClamped( const waveForm_t *wf ); + + +static void ComputeTexMods( shaderStage_t *pStage, int bundleNum, float *outMatrix, float *outOffTurb) +{ + int tm; + float matrix[6], currentmatrix[6]; + textureBundle_t *bundle = &pStage->bundle[bundleNum]; + + matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = 0.0f; + matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = 0.0f; + + currentmatrix[0] = 1.0f; currentmatrix[2] = 0.0f; currentmatrix[4] = 0.0f; + currentmatrix[1] = 0.0f; currentmatrix[3] = 1.0f; currentmatrix[5] = 0.0f; + + outMatrix[0] = 1.0f; outMatrix[2] = 0.0f; + outMatrix[1] = 0.0f; outMatrix[3] = 1.0f; + + outOffTurb[0] = 0.0f; outOffTurb[1] = 0.0f; outOffTurb[2] = 0.0f; outOffTurb[3] = 0.0f; + + for ( tm = 0; tm < bundle->numTexMods ; tm++ ) { + switch ( bundle->texMods[tm].type ) + { + + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentFactors(&bundle->texMods[tm].wave, &outOffTurb[2], &outOffTurb[3]); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexMatrix( backEnd.currentEntity->e.shaderTexCoord, matrix ); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexMatrix( bundle->texMods[tm].scroll, + matrix ); + break; + + case TMOD_SCALE: + RB_CalcScaleTexMatrix( bundle->texMods[tm].scale, + matrix ); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexMatrix( &bundle->texMods[tm].wave, + matrix ); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexMatrix( &bundle->texMods[tm], + matrix ); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexMatrix( bundle->texMods[tm].rotateSpeed, + matrix ); + break; + + default: + ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'", bundle->texMods[tm].type, tess.shader->name ); + break; + } + + switch ( bundle->texMods[tm].type ) + { + case TMOD_NONE: + case TMOD_TURBULENT: + default: + break; + + case TMOD_ENTITY_TRANSLATE: + case TMOD_SCROLL: + case TMOD_SCALE: + case TMOD_STRETCH: + case TMOD_TRANSFORM: + case TMOD_ROTATE: + outMatrix[0] = matrix[0] * currentmatrix[0] + matrix[2] * currentmatrix[1]; + outMatrix[1] = matrix[1] * currentmatrix[0] + matrix[3] * currentmatrix[1]; + + outMatrix[2] = matrix[0] * currentmatrix[2] + matrix[2] * currentmatrix[3]; + outMatrix[3] = matrix[1] * currentmatrix[2] + matrix[3] * currentmatrix[3]; + + outOffTurb[0] = matrix[0] * currentmatrix[4] + matrix[2] * currentmatrix[5] + matrix[4]; + outOffTurb[1] = matrix[1] * currentmatrix[4] + matrix[3] * currentmatrix[5] + matrix[5]; + + currentmatrix[0] = outMatrix[0]; + currentmatrix[1] = outMatrix[1]; + currentmatrix[2] = outMatrix[2]; + currentmatrix[3] = outMatrix[3]; + currentmatrix[4] = outOffTurb[0]; + currentmatrix[5] = outOffTurb[1]; + break; + } + } +} + + +static void ComputeDeformValues(int *deformGen, vec5_t deformParams) +{ + // u_DeformGen + *deformGen = DGEN_NONE; + if(!ShaderRequiresCPUDeforms(tess.shader)) + { + deformStage_t *ds; + + // only support the first one + ds = &tess.shader->deforms[0]; + + switch (ds->deformation) + { + case DEFORM_WAVE: + *deformGen = ds->deformationWave.func; + + deformParams[0] = ds->deformationWave.base; + deformParams[1] = ds->deformationWave.amplitude; + deformParams[2] = ds->deformationWave.phase; + deformParams[3] = ds->deformationWave.frequency; + deformParams[4] = ds->deformationSpread; + break; + + case DEFORM_BULGE: + *deformGen = DGEN_BULGE; + + deformParams[0] = 0; + deformParams[1] = ds->bulgeHeight; // amplitude + deformParams[2] = ds->bulgeWidth; // phase + deformParams[3] = ds->bulgeSpeed; // frequency + deformParams[4] = 0; + break; + + default: + break; + } + } +} + + +static void ProjectDlightTexture( void ) { + int l; + vec3_t origin; + float scale; + float radius; + int deformGen; + vec5_t deformParams; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + ComputeDeformValues(&deformGen, deformParams); + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + shaderProgram_t *sp; + vec4_t vector; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + + sp = &tr.dlightShader[deformGen == DGEN_NONE ? 0 : 1]; + + backEnd.pc.c_dlightDraws++; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime); + } + + vector[0] = dl->color[0]; + vector[1] = dl->color[1]; + vector[2] = dl->color[2]; + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, UNIFORM_COLOR, vector); + + vector[0] = origin[0]; + vector[1] = origin[1]; + vector[2] = origin[2]; + vector[3] = scale; + GLSL_SetUniformVec4(sp, UNIFORM_DLIGHTINFO, vector); + + GL_BindToTMU( tr.dlightImage, TB_COLORMAP ); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_ATEST_GT_0 | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_ATEST_GT_0 | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 1); + + R_DrawElements(tess.numIndexes, tess.firstIndex); + + backEnd.pc.c_totalIndexes += tess.numIndexes; + backEnd.pc.c_dlightIndexes += tess.numIndexes; + backEnd.pc.c_dlightVertexes += tess.numVertexes; + } +} + + +static void ComputeShaderColors( shaderStage_t *pStage, vec4_t baseColor, vec4_t vertColor, int blend ) +{ + bool isBlend = ((blend & GLS_SRCBLEND_BITS) == GLS_SRCBLEND_DST_COLOR) + || ((blend & GLS_SRCBLEND_BITS) == GLS_SRCBLEND_ONE_MINUS_DST_COLOR) + || ((blend & GLS_DSTBLEND_BITS) == GLS_DSTBLEND_SRC_COLOR) + || ((blend & GLS_DSTBLEND_BITS) == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR); + + bool is2DDraw = backEnd.currentEntity == &backEnd.entity2D; + + float overbright = (isBlend || is2DDraw) ? 1.0f : (float)(1 << tr.overbrightBits); + + fog_t *fog; + + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + + // + // rgbGen + // + switch ( pStage->rgbGen ) + { + case CGEN_EXACT_VERTEX: + case CGEN_EXACT_VERTEX_LIT: + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 0.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = overbright; + vertColor[3] = 1.0f; + break; + case CGEN_CONST: + baseColor[0] = pStage->constantColor[0] / 255.0f; + baseColor[1] = pStage->constantColor[1] / 255.0f; + baseColor[2] = pStage->constantColor[2] / 255.0f; + baseColor[3] = pStage->constantColor[3] / 255.0f; + break; + case CGEN_VERTEX: + case CGEN_VERTEX_LIT: + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 0.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 1.0f; + break; + case CGEN_ONE_MINUS_VERTEX: + baseColor[0] = + baseColor[1] = + baseColor[2] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = -1.0f; + break; + case CGEN_FOG: + fog = tr.world->fogs + tess.fogNum; + + baseColor[0] = ((unsigned char *)(&fog->colorInt))[0] / 255.0f; + baseColor[1] = ((unsigned char *)(&fog->colorInt))[1] / 255.0f; + baseColor[2] = ((unsigned char *)(&fog->colorInt))[2] / 255.0f; + baseColor[3] = ((unsigned char *)(&fog->colorInt))[3] / 255.0f; + break; + case CGEN_WAVEFORM: + baseColor[0] = + baseColor[1] = + baseColor[2] = RB_CalcWaveColorSingle( &pStage->rgbWave ); + break; + case CGEN_ENTITY: + if (backEnd.currentEntity) + { + baseColor[0] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[0] / 255.0f; + baseColor[1] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[1] / 255.0f; + baseColor[2] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[2] / 255.0f; + baseColor[3] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + break; + case CGEN_ONE_MINUS_ENTITY: + if (backEnd.currentEntity) + { + baseColor[0] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[0] / 255.0f; + baseColor[1] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[1] / 255.0f; + baseColor[2] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[2] / 255.0f; + baseColor[3] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + break; + case CGEN_IDENTITY: + case CGEN_LIGHTING_DIFFUSE: + baseColor[0] = + baseColor[1] = + baseColor[2] = overbright; + break; + case CGEN_IDENTITY_LIGHTING: + case CGEN_BAD: + break; + } + + // + // alphaGen + // + switch ( pStage->alphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_CONST: + baseColor[3] = pStage->constantColor[3] / 255.0f; + vertColor[3] = 0.0f; + break; + case AGEN_WAVEFORM: + baseColor[3] = RB_CalcWaveAlphaSingle( &pStage->alphaWave ); + vertColor[3] = 0.0f; + break; + case AGEN_ENTITY: + if (backEnd.currentEntity) + { + baseColor[3] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + vertColor[3] = 0.0f; + break; + case AGEN_ONE_MINUS_ENTITY: + if (backEnd.currentEntity) + { + baseColor[3] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + vertColor[3] = 0.0f; + break; + case AGEN_VERTEX: + baseColor[3] = 0.0f; + vertColor[3] = 1.0f; + break; + case AGEN_ONE_MINUS_VERTEX: + baseColor[3] = 1.0f; + vertColor[3] = -1.0f; + break; + case AGEN_IDENTITY: + case AGEN_LIGHTING_SPECULAR: + case AGEN_PORTAL: + // Done entirely in vertex program + baseColor[3] = 1.0f; + vertColor[3] = 0.0f; + break; + } + + // FIXME: find some way to implement this. +#if 0 + // if in greyscale rendering mode turn all color values into greyscale. + if(r_greyscale->integer) + { + int scale; + + for(i = 0; i < tess.numVertexes; i++) + { + scale = (tess.svars.colors[i][0] + tess.svars.colors[i][1] + tess.svars.colors[i][2]) / 3; + tess.svars.colors[i][0] = tess.svars.colors[i][1] = tess.svars.colors[i][2] = scale; + } + } +#endif +} + + +static void ComputeFogValues(vec4_t fogDistanceVector, vec4_t fogDepthVector, float *eyeT) +{ + // from RB_CalcFogTexCoords() + fog_t *fog; + vec3_t local; + + if (!tess.fogNum) + return; + + fog = tr.world->fogs + tess.fogNum; + + VectorSubtract( backEnd.orientation.origin, backEnd.viewParms.orientation.origin, local ); + fogDistanceVector[0] = -backEnd.orientation.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.orientation.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.orientation.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.orientation.axis[0] ); + + // scale the fog vectors based on the fog's thickness + VectorScale4(fogDistanceVector, fog->tcScale, fogDistanceVector); + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.orientation.axis[0][0] + + fog->surface[1] * backEnd.orientation.axis[0][1] + fog->surface[2] * backEnd.orientation.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.orientation.axis[1][0] + + fog->surface[1] * backEnd.orientation.axis[1][1] + fog->surface[2] * backEnd.orientation.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.orientation.axis[2][0] + + fog->surface[1] * backEnd.orientation.axis[2][1] + fog->surface[2] * backEnd.orientation.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.orientation.origin, fog->surface ); + + *eyeT = DotProduct( backEnd.orientation.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + *eyeT = 1; // non-surface fog always has eye inside + } +} + + +static void ComputeFogColorMask( shaderStage_t *pStage, vec4_t fogColorMask ) +{ + switch(pStage->adjustColorsForFog) + { + case ACFF_MODULATE_RGB: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = 1.0f; + fogColorMask[3] = 0.0f; + break; + case ACFF_MODULATE_ALPHA: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = 0.0f; + fogColorMask[3] = 1.0f; + break; + case ACFF_MODULATE_RGBA: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = + fogColorMask[3] = 1.0f; + break; + default: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = + fogColorMask[3] = 0.0f; + break; + } +} + + +static void ForwardDlight( void ) { + int l; + //vec3_t origin; + //float scale; + float radius; + + int deformGen; + vec5_t deformParams; + + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + + shaderCommands_t *input = &tess; + shaderStage_t *pStage = tess.xstages[0]; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + ComputeDeformValues(&deformGen, deformParams); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + shaderProgram_t *sp; + vec4_t vector; + vec4_t texMatrix; + vec4_t texOffTurb; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + dl = &backEnd.refdef.dlights[l]; + //VectorCopy( dl->transformed, origin ); + radius = dl->radius; + //scale = 1.0f / radius; + + //if (pStage->glslShaderGroup == tr.lightallShader) + { + int index = pStage->glslShaderIndex; + + index &= ~LIGHTDEF_LIGHTTYPE_MASK; + index |= LIGHTDEF_USE_LIGHT_VECTOR; + + sp = &tr.lightallShader[index]; + } + + backEnd.pc.c_lightallDraws++; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec3(sp, UNIFORM_VIEWORIGIN, backEnd.viewParms.orientation.origin); + GLSL_SetUniformVec3(sp, UNIFORM_LOCALVIEWORIGIN, backEnd.orientation.viewOrigin); + + GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime); + } + + if ( input->fogNum ) { + vec4_t fogColorMask; + + GLSL_SetUniformVec4(sp, UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, UNIFORM_FOGEYET, eyeT); + + ComputeFogColorMask(pStage, fogColorMask); + + GLSL_SetUniformVec4(sp, UNIFORM_FOGCOLORMASK, fogColorMask); + } + + { + vec4_t baseColor; + vec4_t vertColor; + + ComputeShaderColors(pStage, baseColor, vertColor, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + + GLSL_SetUniformVec4(sp, UNIFORM_BASECOLOR, baseColor); + GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, vertColor); + } + + if (pStage->alphaGen == AGEN_PORTAL) + { + GLSL_SetUniformFloat(sp, UNIFORM_PORTALRANGE, tess.shader->portalRange); + } + + GLSL_SetUniformInt(sp, UNIFORM_COLORGEN, pStage->rgbGen); + GLSL_SetUniformInt(sp, UNIFORM_ALPHAGEN, pStage->alphaGen); + + GLSL_SetUniformVec3(sp, UNIFORM_DIRECTEDLIGHT, dl->color); + + VectorSet(vector, 0, 0, 0); + GLSL_SetUniformVec3(sp, UNIFORM_AMBIENTLIGHT, vector); + + VectorCopy(dl->origin, vector); + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vector); + + GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, radius); + + GLSL_SetUniformVec4(sp, UNIFORM_NORMALSCALE, pStage->normalScale); + GLSL_SetUniformVec4(sp, UNIFORM_SPECULARSCALE, pStage->specularScale); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.orientation.transformMatrix); + + if (pStage->bundle[TB_DIFFUSEMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP); + + // bind textures that are sampled and used in the glsl shader, and + // bind whiteImage to textures that are sampled but zeroed in the glsl shader + // + // alternatives: + // - use the last bound texture + // -> costs more to sample a higher res texture then throw out the result + // - disable texture sampling in glsl shader with #ifdefs, as before + // -> increases the number of shaders that must be compiled + // + + if (pStage->bundle[TB_NORMALMAP].image[0]) + { + R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP); + } + else if (r_normalMapping->integer) + GL_BindToTMU( tr.whiteImage, TB_NORMALMAP ); + + if (pStage->bundle[TB_SPECULARMAP].image[0]) + { + R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP); + } + else if (r_specularMapping->integer) + GL_BindToTMU( tr.whiteImage, TB_SPECULARMAP ); + + { + vec4_t enableTextures; + + VectorSet4(enableTextures, 0.0f, 0.0f, 0.0f, 0.0f); + GLSL_SetUniformVec4(sp, UNIFORM_ENABLETEXTURES, enableTextures); + } + + if (r_dlightMode->integer >= 2) + GL_BindToTMU(tr.shadowCubemaps[l], TB_SHADOWMAP); + + ComputeTexMods( pStage, TB_DIFFUSEMAP, texMatrix, texOffTurb ); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, texMatrix); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, texOffTurb); + + GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, pStage->bundle[0].tcGen); + + // + // draw + // + + R_DrawElements(input->numIndexes, input->firstIndex); + + backEnd.pc.c_totalIndexes += tess.numIndexes; + backEnd.pc.c_dlightIndexes += tess.numIndexes; + backEnd.pc.c_dlightVertexes += tess.numVertexes; + } +} + + +static void ProjectPshadowVBOGLSL( void ) { + int l; + vec3_t origin; + float radius; + + int deformGen; + vec5_t deformParams; + + shaderCommands_t *input = &tess; + + if ( !backEnd.refdef.num_pshadows ) { + return; + } + + ComputeDeformValues(&deformGen, deformParams); + + for ( l = 0 ; l < backEnd.refdef.num_pshadows ; l++ ) { + pshadow_t *ps; + shaderProgram_t *sp; + vec4_t vector; + + if ( !( tess.pshadowBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this shadow + } + + ps = &backEnd.refdef.pshadows[l]; + VectorCopy( ps->lightOrigin, origin ); + radius = ps->lightRadius; + + sp = &tr.pshadowShader; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + VectorCopy(origin, vector); + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vector); + + VectorScale(ps->lightViewAxis[0], 1.0f / ps->viewRadius, vector); + GLSL_SetUniformVec3(sp, UNIFORM_LIGHTFORWARD, vector); + + VectorScale(ps->lightViewAxis[1], 1.0f / ps->viewRadius, vector); + GLSL_SetUniformVec3(sp, UNIFORM_LIGHTRIGHT, vector); + + VectorScale(ps->lightViewAxis[2], 1.0f / ps->viewRadius, vector); + GLSL_SetUniformVec3(sp, UNIFORM_LIGHTUP, vector); + + GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, radius); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + GL_BindToTMU( tr.pshadowMaps[l], TB_DIFFUSEMAP ); + + // + // draw + // + + R_DrawElements(input->numIndexes, input->firstIndex); + + backEnd.pc.c_totalIndexes += tess.numIndexes; + //backEnd.pc.c_dlightIndexes += tess.numIndexes; + } +} + + + +/* +=================== +RB_FogPass + +Blends a fog texture on top of everything else +=================== +*/ +static void RB_FogPass( void ) { + fog_t *fog; + vec4_t color; + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + shaderProgram_t *sp; + + int deformGen; + vec5_t deformParams; + + ComputeDeformValues(&deformGen, deformParams); + + { + int index = 0; + + if (deformGen != DGEN_NONE) + index |= FOGDEF_USE_DEFORM_VERTEXES; + + if (glState.vertexAnimation) + index |= FOGDEF_USE_VERTEX_ANIMATION; + + sp = &tr.fogShader[index]; + } + + backEnd.pc.c_fogDraws++; + + GLSL_BindProgram(sp); + + fog = tr.world->fogs + tess.fogNum; + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime); + } + + color[0] = ((unsigned char *)(&fog->colorInt))[0] / 255.0f; + color[1] = ((unsigned char *)(&fog->colorInt))[1] / 255.0f; + color[2] = ((unsigned char *)(&fog->colorInt))[2] / 255.0f; + color[3] = ((unsigned char *)(&fog->colorInt))[3] / 255.0f; + GLSL_SetUniformVec4(sp, UNIFORM_COLOR, color); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + GLSL_SetUniformVec4(sp, UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, UNIFORM_FOGEYET, eyeT); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + R_DrawElements(tess.numIndexes, tess.firstIndex); +} + + +static unsigned int RB_CalcShaderVertexAttribs( shaderCommands_t *input ) +{ + unsigned int vertexAttribs = input->shader->vertexAttribs; + + if(glState.vertexAnimation) + { + vertexAttribs |= ATTR_POSITION2; + if (vertexAttribs & ATTR_NORMAL) + { + vertexAttribs |= ATTR_NORMAL2; + vertexAttribs |= ATTR_TANGENT2; + } + } + + return vertexAttribs; +} + +static void RB_IterateStagesGeneric( shaderCommands_t *input ) +{ + int stage; + + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + + int deformGen; + vec5_t deformParams; + + bool renderToCubemap = tr.renderCubeFbo && glState.currentFBO == tr.renderCubeFbo; + + ComputeDeformValues(&deformGen, deformParams); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = input->xstages[stage]; + shaderProgram_t *sp; + vec4_t texMatrix; + vec4_t texOffTurb; + + if ( !pStage ) + { + break; + } + + if (backEnd.depthFill) + { + if (pStage->glslShaderGroup == tr.lightallShader) + { + int index = 0; + + if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + index |= LIGHTDEF_ENTITY; + } + + if (pStage->stateBits & GLS_ATEST_BITS) + { + index |= LIGHTDEF_USE_TCGEN_AND_TCMOD; + } + + sp = &pStage->glslShaderGroup[index]; + } + else + { + int shaderAttribs = 0; + + if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader)) + { + shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES; + } + + if (glState.vertexAnimation) + { + shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION; + } + + if (pStage->stateBits & GLS_ATEST_BITS) + { + shaderAttribs |= GENERICDEF_USE_TCGEN_AND_TCMOD; + } + + sp = &tr.genericShader[shaderAttribs]; + } + } + else if (pStage->glslShaderGroup == tr.lightallShader) + { + int index = pStage->glslShaderIndex; + + if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + index |= LIGHTDEF_ENTITY; + } + + if (r_sunlightMode->integer && (backEnd.viewParms.flags & VPF_USESUNLIGHT) && (index & LIGHTDEF_LIGHTTYPE_MASK)) + { + index |= LIGHTDEF_USE_SHADOWMAP; + } + + if (r_lightmap->integer && ((index & LIGHTDEF_LIGHTTYPE_MASK) == LIGHTDEF_USE_LIGHTMAP)) + { + index = LIGHTDEF_USE_TCGEN_AND_TCMOD; + } + + sp = &pStage->glslShaderGroup[index]; + + backEnd.pc.c_lightallDraws++; + } + else + { + sp = GLSL_GetGenericShaderProgram(stage); + + backEnd.pc.c_genericDraws++; + } + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec3(sp, UNIFORM_VIEWORIGIN, backEnd.viewParms.orientation.origin); + GLSL_SetUniformVec3(sp, UNIFORM_LOCALVIEWORIGIN, backEnd.orientation.viewOrigin); + + GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime); + } + + if ( input->fogNum ) { + GLSL_SetUniformVec4(sp, UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, UNIFORM_FOGEYET, eyeT); + } + + GL_State( pStage->stateBits ); + if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_GT_0) + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 1); + } + else if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_LT_80) + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 2); + } + else if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_GE_80) + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 3); + } + else + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + } + + + { + vec4_t baseColor; + vec4_t vertColor; + + ComputeShaderColors(pStage, baseColor, vertColor, pStage->stateBits); + + GLSL_SetUniformVec4(sp, UNIFORM_BASECOLOR, baseColor); + GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, vertColor); + } + + if (pStage->rgbGen == CGEN_LIGHTING_DIFFUSE) + { + vec4_t vec; + + VectorScale(backEnd.currentEntity->ambientLight, 1.0f / 255.0f, vec); + GLSL_SetUniformVec3(sp, UNIFORM_AMBIENTLIGHT, vec); + + VectorScale(backEnd.currentEntity->directedLight, 1.0f / 255.0f, vec); + GLSL_SetUniformVec3(sp, UNIFORM_DIRECTEDLIGHT, vec); + + VectorCopy(backEnd.currentEntity->lightDir, vec); + vec[3] = 0.0f; + GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vec); + GLSL_SetUniformVec3(sp, UNIFORM_MODELLIGHTDIR, backEnd.currentEntity->modelLightDir); + + GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, 0.0f); + } + + if (pStage->alphaGen == AGEN_PORTAL) + { + GLSL_SetUniformFloat(sp, UNIFORM_PORTALRANGE, tess.shader->portalRange); + } + + GLSL_SetUniformInt(sp, UNIFORM_COLORGEN, pStage->rgbGen); + GLSL_SetUniformInt(sp, UNIFORM_ALPHAGEN, pStage->alphaGen); + + if ( input->fogNum ) + { + vec4_t fogColorMask; + + ComputeFogColorMask(pStage, fogColorMask); + + GLSL_SetUniformVec4(sp, UNIFORM_FOGCOLORMASK, fogColorMask); + } + + if (r_lightmap->integer) + { + vec4_t v; + VectorSet4(v, 1.0f, 0.0f, 0.0f, 1.0f); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, v); + VectorSet4(v, 0.0f, 0.0f, 0.0f, 0.0f); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, v); + + GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, TCGEN_LIGHTMAP); + } + else + { + ComputeTexMods(pStage, TB_DIFFUSEMAP, texMatrix, texOffTurb); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, texMatrix); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, texOffTurb); + + GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, pStage->bundle[0].tcGen); + if (pStage->bundle[0].tcGen == TCGEN_VECTOR) + { + vec3_t vec; + + VectorCopy(pStage->bundle[0].tcGenVectors[0], vec); + GLSL_SetUniformVec3(sp, UNIFORM_TCGEN0VECTOR0, vec); + VectorCopy(pStage->bundle[0].tcGenVectors[1], vec); + GLSL_SetUniformVec3(sp, UNIFORM_TCGEN0VECTOR1, vec); + } + } + + GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.orientation.transformMatrix); + + GLSL_SetUniformVec4(sp, UNIFORM_NORMALSCALE, pStage->normalScale); + + { + vec4_t specularScale; + Vector4Copy(pStage->specularScale, specularScale); + + if (renderToCubemap) + { + // force specular to nonmetal if rendering cubemaps + if (r_pbr->integer) + specularScale[1] = 0.0f; + } + + GLSL_SetUniformVec4(sp, UNIFORM_SPECULARSCALE, specularScale); + } + + //GLSL_SetUniformFloat(sp, UNIFORM_MAPLIGHTSCALE, backEnd.refdef.mapLightScale); + + // + // do multitexture + // + if ( backEnd.depthFill ) + { + if (!(pStage->stateBits & GLS_ATEST_BITS)) + GL_BindToTMU( tr.whiteImage, TB_COLORMAP ); + else if ( pStage->bundle[TB_COLORMAP].image[0] != 0 ) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_COLORMAP], TB_COLORMAP ); + } + else if ( pStage->glslShaderGroup == tr.lightallShader ) + { + int i; + vec4_t enableTextures; + + if (r_sunlightMode->integer && (backEnd.viewParms.flags & VPF_USESUNLIGHT) && (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK)) + { + // FIXME: screenShadowImage is NULL if no framebuffers + if (tr.screenShadowImage) + GL_BindToTMU(tr.screenShadowImage, TB_SHADOWMAP); + GLSL_SetUniformVec3(sp, UNIFORM_PRIMARYLIGHTAMBIENT, backEnd.refdef.sunAmbCol); + if (r_pbr->integer) + { + vec3_t color; + + color[0] = backEnd.refdef.sunCol[0] * backEnd.refdef.sunCol[0]; + color[1] = backEnd.refdef.sunCol[1] * backEnd.refdef.sunCol[1]; + color[2] = backEnd.refdef.sunCol[2] * backEnd.refdef.sunCol[2]; + GLSL_SetUniformVec3(sp, UNIFORM_PRIMARYLIGHTCOLOR, color); + } + else + { + GLSL_SetUniformVec3(sp, UNIFORM_PRIMARYLIGHTCOLOR, backEnd.refdef.sunCol); + } + GLSL_SetUniformVec4(sp, UNIFORM_PRIMARYLIGHTORIGIN, backEnd.refdef.sunDir); + } + + VectorSet4(enableTextures, 0, 0, 0, 0); + if ((r_lightmap->integer == 1 || r_lightmap->integer == 2) && pStage->bundle[TB_LIGHTMAP].image[0]) + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if (i == TB_COLORMAP) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_LIGHTMAP], i); + else + GL_BindToTMU( tr.whiteImage, i ); + } + } + else if (r_lightmap->integer == 3 && pStage->bundle[TB_DELUXEMAP].image[0]) + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if (i == TB_COLORMAP) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DELUXEMAP], i); + else + GL_BindToTMU( tr.whiteImage, i ); + } + } + else + { + bool light = (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) != 0; + bool fastLight = !(r_normalMapping->integer || r_specularMapping->integer); + + if (pStage->bundle[TB_DIFFUSEMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP); + + if (pStage->bundle[TB_LIGHTMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_LIGHTMAP], TB_LIGHTMAP); + + // bind textures that are sampled and used in the glsl shader, and + // bind whiteImage to textures that are sampled but zeroed in the glsl shader + // + // alternatives: + // - use the last bound texture + // -> costs more to sample a higher res texture then throw out the result + // - disable texture sampling in glsl shader with #ifdefs, as before + // -> increases the number of shaders that must be compiled + // + if (light && !fastLight) + { + if (pStage->bundle[TB_NORMALMAP].image[0]) + { + R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP); + enableTextures[0] = 1.0f; + } + else if (r_normalMapping->integer) + GL_BindToTMU( tr.whiteImage, TB_NORMALMAP ); + + if (pStage->bundle[TB_DELUXEMAP].image[0]) + { + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DELUXEMAP], TB_DELUXEMAP); + enableTextures[1] = 1.0f; + } + else if (r_deluxeMapping->integer) + GL_BindToTMU( tr.whiteImage, TB_DELUXEMAP ); + + if (pStage->bundle[TB_SPECULARMAP].image[0]) + { + R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP); + enableTextures[2] = 1.0f; + } + else if (r_specularMapping->integer) + GL_BindToTMU( tr.whiteImage, TB_SPECULARMAP ); + } + + enableTextures[3] = (r_cubeMapping->integer && !(tr.viewParms.flags & VPF_NOCUBEMAPS) && input->cubemapIndex) ? 1.0f : 0.0f; + } + + GLSL_SetUniformVec4(sp, UNIFORM_ENABLETEXTURES, enableTextures); + } + else if ( pStage->bundle[1].image[0] != 0 ) + { + R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 ); + R_BindAnimatedImageToTMU( &pStage->bundle[1], 1 ); + } + else + { + // + // set state + // + R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 ); + } + + // + // testing cube map + // + if (!(tr.viewParms.flags & VPF_NOCUBEMAPS) && input->cubemapIndex && r_cubeMapping->integer) + { + vec4_t vec; + cubemap_t *cubemap = &tr.cubemaps[input->cubemapIndex - 1]; + + // FIXME: cubemap image could be NULL if cubemap isn't renderer or loaded + if (cubemap->image) + GL_BindToTMU( cubemap->image, TB_CUBEMAP); + + VectorSubtract(cubemap->origin, backEnd.viewParms.orientation.origin, vec); + vec[3] = 1.0f; + + VectorScale4(vec, 1.0f / cubemap->parallaxRadius, vec); + + GLSL_SetUniformVec4(sp, UNIFORM_CUBEMAPINFO, vec); + } + + // + // draw + // + R_DrawElements(input->numIndexes, input->firstIndex); + + // allow skipping out to show just lightmaps during development + if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap ) ) + { + break; + } + + if (backEnd.depthFill) + break; + } +} + + +static void RB_RenderShadowmap( shaderCommands_t *input ) +{ + int deformGen; + vec5_t deformParams; + + ComputeDeformValues(&deformGen, deformParams); + + { + shaderProgram_t *sp = &tr.shadowmapShader; + + vec4_t vector; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.orientation.transformMatrix); + + GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime); + } + + VectorCopy(backEnd.viewParms.orientation.origin, vector); + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vector); + GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, backEnd.viewParms.zFar); + + GL_State( 0 ); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + // + // do multitexture + // + //if ( pStage->glslShaderGroup ) + { + // + // draw + // + + R_DrawElements(input->numIndexes, input->firstIndex); + } + } +} + + + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) +{ + shaderCommands_t *input; + unsigned int vertexAttribs = 0; + + input = &tess; + + if (!input->numVertexes || !input->numIndexes) + { + return; + } + + if (tess.useInternalVao) + { + RB_DeformTessGeometry(); + } + + vertexAttribs = RB_CalcShaderVertexAttribs( input ); + + if (tess.useInternalVao) + { + RB_UpdateTessVao(vertexAttribs); + } + else + { + backEnd.pc.c_staticVaoDraws++; + } + + // + // log this call + // + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( (char*)va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + if (input->shader->cullType == CT_TWO_SIDED) + { + GL_Cull( CT_TWO_SIDED ); + } + else + { + bool cullFront = (input->shader->cullType == CT_FRONT_SIDED); + + if ( backEnd.viewParms.flags & VPF_DEPTHSHADOW ) + cullFront = !cullFront; + + if ( backEnd.viewParms.isMirror ) + cullFront = !cullFront; + + if ( backEnd.currentEntity && backEnd.currentEntity->mirrored ) + cullFront = !cullFront; + + if (cullFront) + GL_Cull( CT_FRONT_SIDED ); + else + GL_Cull( CT_BACK_SIDED ); + } + + // set polygon offset if necessary + if ( input->shader->polygonOffset ) + { + qglEnable( GL_POLYGON_OFFSET_FILL ); + } + + // + // render depth if in depthfill mode + // + if (backEnd.depthFill) + { + RB_IterateStagesGeneric( input ); + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + return; + } + + // + // render shadowmap if in shadowmap mode + // + if (backEnd.viewParms.flags & VPF_SHADOWMAP) + { + if ( input->shader->sort == SS_OPAQUE ) + { + RB_RenderShadowmap( input ); + } + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + return; + } + + // + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // pshadows! + // + if (glRefConfig.framebufferObject && r_shadows->integer == 4 && tess.pshadowBits + && tess.shader->sort <= SS_OPAQUE && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { + ProjectPshadowVBOGLSL(); + } + + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE && r_lightmap->integer == 0 + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { + if (tess.shader->numUnfoggedPasses == 1 && tess.xstages[0]->glslShaderGroup == tr.lightallShader + && (tess.xstages[0]->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) && r_dlightMode->integer) + { + ForwardDlight(); + } + else + { + ProjectDlightTexture(); + } + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } +} + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if (input->numIndexes == 0 || input->numVertexes == 0) { + return; + } + + if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); + } + if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); + } + + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + if (tess.useCacheVao) + { + // upload indexes now + VaoCache_Commit(); + } + + // + // update performance counters + // + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + + // + // draw debugging stuff + // + if ( r_showtris->integer ) { + DrawTris (input); + } + if ( r_shownormals->integer ) { + DrawNormals (input); + } + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + + GLimp_LogComment( "----------\n" ); +} diff --git a/src/renderergl2/tr_shade_calc.cpp b/src/renderergl2/tr_shade_calc.cpp new file mode 100644 index 0000000..182020b --- /dev/null +++ b/src/renderergl2/tr_shade_calc.cpp @@ -0,0 +1,843 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_shade_calc.c + +#include "tr_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif + + +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ static_cast( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) + +static float *TableForFunc( genFunc_t func ) +{ + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +static float EvalWaveForm( const waveForm_t *wf ) +{ + float *table; + + table = TableForFunc( wf->func ); + + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) +{ + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) + { + return 0; + } + + if ( glow > 1 ) + { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexMatrix +*/ +void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix ) +{ + float p; + + p = 1.0f / EvalWaveForm( wf ); + + matrix[0] = p; matrix[2] = 0; matrix[4] = 0.5f - 0.5f * p; + matrix[1] = 0; matrix[3] = p; matrix[5] = 0.5f - 0.5f * p; +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) +{ + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + int16_t *normal = tess.normal[0]; + float *table; + + if ( ds->deformationWave.frequency == 0 ) + { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + R_VaoUnpackNormal(offset, normal); + + xyz[0] += offset[0] * scale; + xyz[1] += offset[1] * scale; + xyz[2] += offset[2] * scale; + } + } + else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + R_VaoUnpackNormal(offset, normal); + + xyz[0] += offset[0] * scale; + xyz[1] += offset[1] * scale; + xyz[2] += offset[2] * scale; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + int16_t *normal = tess.normal[0]; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + vec3_t fNormal; + + R_VaoUnpackNormal(fNormal, normal); + + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + fNormal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + fNormal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + fNormal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( fNormal ); + + R_VaoPackNormal(normal, fNormal); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) { + int i; + const float *st = ( const float * ) tess.texCoords[0]; + float *xyz = ( float * ) tess.xyz; + int16_t *normal = tess.normal[0]; + + double now = backEnd.refdef.time * 0.001 * ds->bulgeSpeed; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2, normal += 4 ) { + int64_t off; + float scale; + vec3_t fNormal; + + R_VaoUnpackNormal(fNormal, normal); + + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += fNormal[0] * scale; + xyz[1] += fNormal[1] * scale; + xyz[2] += fNormal[2] * scale; + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + float color[4]; + float bottom, top; + vec3_t mid; + vec3_t fNormal; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + + R_VaoUnpackNormal(fNormal, tess.normal[0]); + CrossProduct( fNormal, height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = 999999; + top = -999999; + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, (len-1), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + + color[0] = color[1] = color[2] = color[3] = 1.0f; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625f; + fcol = col*0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.orientation.axis[0] ); + out[1] = DotProduct( in, backEnd.orientation.axis[1] ); + out[2] = DotProduct( in, backEnd.orientation.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count\n", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count\n", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.firstIndex = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.orientation.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.orientation.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.orientation.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.orientation.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i+=4 ) { + vec4_t color; + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); + mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); + mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + // compensate for scale in the axes if necessary + if ( backEnd.currentEntity->e.nonNormalizedAxes ) { + float axisLength; + axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + VectorScale(left, axisLength, left); + VectorScale(up, axisLength, up); + } + + VectorScale4(tess.color[i], 1.0f / 65535.0f, color); + RB_AddQuadStamp( mid, left, up, color ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +int edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count\n", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count\n", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.orientation.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.orientation.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * (v1[0] + v2[0]); + mid[j][1] = 0.5f * (v1[1] + v2[1]); + mid[j][2] = 0.5f * (v1[2] + v2[2]); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5 * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + if(!ShaderRequiresCPUDeforms(tess.shader)) + { + // we don't need the following CPU deforms + return; + } + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = &tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: + RB_ProjectionShadowDeform(); + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + break; + } + } +} + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcWaveColorSingle +*/ +float RB_CalcWaveColorSingle( const waveForm_t *wf ) +{ + float glow; + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + return glow; +} + +/* +** RB_CalcWaveAlphaSingle +*/ +float RB_CalcWaveAlphaSingle( const waveForm_t *wf ) +{ + return EvalWaveFormClamped( wf ); +} + +/* +** RB_CalcModulateColorsByFog +*/ +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2] = {{0.0f}}; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + bool eyeOutside; + fog_t *fog; + vec3_t local; + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.orientation.origin, backEnd.viewParms.orientation.origin, local ); + fogDistanceVector[0] = -backEnd.orientation.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.orientation.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.orientation.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.orientation.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.orientation.axis[0][0] + + fog->surface[1] * backEnd.orientation.axis[0][1] + fog->surface[2] * backEnd.orientation.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.orientation.axis[1][0] + + fog->surface[1] * backEnd.orientation.axis[1][1] + fog->surface[2] * backEnd.orientation.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.orientation.axis[2][0] + + fog->surface[1] * backEnd.orientation.axis[2][1] + fog->surface[2] * backEnd.orientation.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.orientation.origin, fog->surface ); + + eyeT = DotProduct( backEnd.orientation.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = true; + } else { + eyeOutside = false; + } + + fogDistanceVector[3] += 1.0/512; + + // calculate density for each point + for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 31.0/32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + +/* +** RB_CalcTurbulentFactors +*/ +void RB_CalcTurbulentFactors( const waveForm_t *wf, float *amplitude, float *now ) +{ + *now = wf->phase + tess.shaderTime * wf->frequency; + *amplitude = wf->amplitude; +} + +/* +** RB_CalcScaleTexMatrix +*/ +void RB_CalcScaleTexMatrix( const float scale[2], float *matrix ) +{ + matrix[0] = scale[0]; matrix[2] = 0.0f; matrix[4] = 0.0f; + matrix[1] = 0.0f; matrix[3] = scale[1]; matrix[5] = 0.0f; +} + +/* +** RB_CalcScrollTexMatrix +*/ +void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix ) +{ + double timeScale = tess.shaderTime; + double adjustedScrollS = scrollSpeed[0] * timeScale; + double adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS -= floor( adjustedScrollS ); + adjustedScrollT -= floor( adjustedScrollT ); + + matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = adjustedScrollS; + matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = adjustedScrollT; +} + +/* +** RB_CalcTransformTexMatrix +*/ +void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix ) +{ + matrix[0] = tmi->matrix[0][0]; matrix[2] = tmi->matrix[1][0]; matrix[4] = tmi->translate[0]; + matrix[1] = tmi->matrix[0][1]; matrix[3] = tmi->matrix[1][1]; matrix[5] = tmi->translate[1]; +} + +/* +** RB_CalcRotateTexMatrix +*/ +void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix ) +{ + double timeScale = tess.shaderTime; + double degs; + float sinValue, cosValue; + + degs = -degsPerSecond * timeScale; + int i = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ i & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( i + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + matrix[0] = cosValue; matrix[2] = -sinValue; matrix[4] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + matrix[1] = sinValue; matrix[3] = cosValue; matrix[5] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; +} + +bool ShaderRequiresCPUDeforms(const shader_t * shader) +{ + if(shader->numDeforms) + { + const deformStage_t *ds = &shader->deforms[0]; + + if (shader->numDeforms > 1) + return true; + + switch (ds->deformation) + { + case DEFORM_WAVE: + case DEFORM_BULGE: + // need CPU deforms at high level-times to avoid floating point percision loss + return ( backEnd.refdef.floatTime != (float)backEnd.refdef.floatTime ); + + default: + return true; + } + } + + return false; +} diff --git a/src/renderergl2/tr_shader.cpp b/src/renderergl2/tr_shader.cpp new file mode 100644 index 0000000..b3ebacf --- /dev/null +++ b/src/renderergl2/tr_shader.cpp @@ -0,0 +1,3891 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + +// tr_shader.c -- this file deals with the parsing and definition of shaders + +static char *s_shaderText; + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; + +#define FILE_HASH_SIZE 1024 +static shader_t* hashTable[FILE_HASH_SIZE]; + +#define MAX_SHADERTEXT_HASH 2048 +static char **shaderTextHashTable[MAX_SHADERTEXT_HASH]; + +/* +================ +return a hash value for the filename +================ +*/ +#ifdef __GNUCC__ + #warning TODO: check if long is ok here +#endif +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (size-1); + return hash; +} + +void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if (sh == NULL || sh == tr.defaultShader) { + h = RE_RegisterShaderLightMap(shaderName, 0); + sh = R_GetShaderByHandle(h); + } + if (sh == NULL || sh == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if (sh2 == NULL || sh2 == tr.defaultShader) { + h = RE_RegisterShaderLightMap(newShaderName, 0); + sh2 = R_GetShaderByHandle(h); + } + + if (sh2 == NULL || sh2 == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension(shaderName, strippedName, sizeof(strippedName)); + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + for (sh = hashTable[hash]; sh; sh = sh->next) { + if (Q_stricmp(sh->name, strippedName) == 0) { + if (sh != sh2) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if (timeOffset) { + sh2->timeOffset = atof(timeOffset); + } +} + +/* +=============== +ParseVector +=============== +*/ +static bool ParseVector( char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return false; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name ); + return false; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return false; + } + + return true; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "GT0" ) ) + { + return GLS_ATEST_GT_0; + } + else if ( !Q_stricmp( funcname, "LT128" ) ) + { + return GLS_ATEST_LT_80; + } + else if ( !Q_stricmp( funcname, "GE128" ) ) + { + return GLS_ATEST_GE_80; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_SRCBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_SRCBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) + { + return GLS_SRCBLEND_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + if (r_ignoreDstAlpha->integer) + return GLS_SRCBLEND_ONE; + + return GLS_SRCBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + if (r_ignoreDstAlpha->integer) + return GLS_SRCBLEND_ZERO; + + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) + { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_DSTBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_DSTBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + if (r_ignoreDstAlpha->integer) + return GLS_DSTBLEND_ONE; + + return GLS_DSTBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + if (r_ignoreDstAlpha->integer) + return GLS_DSTBLEND_ZERO; + + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_SRC_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "sin" ) ) + { + return GF_SIN; + } + else if ( !Q_stricmp( funcname, "square" ) ) + { + return GF_SQUARE; + } + else if ( !Q_stricmp( funcname, "triangle" ) ) + { + return GF_TRIANGLE; + } + else if ( !Q_stricmp( funcname, "sawtooth" ) ) + { + return GF_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) + { + return GF_INVERSE_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "noise" ) ) + { + return GF_NOISE; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( char **text, waveForm_t *wave ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( char *_text, shaderStage_t *stage ) +{ + const char *token; + char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // turb + // + if ( !Q_stricmp( token, "turb" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[1] = atof( token ); + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[1] = atof( token ); + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->rotateSpeed = atof( token ); + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) + { + tmi->type = TMOD_ENTITY_TRANSLATE; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + +/* +=================== +ParseStage +=================== +*/ +static bool ParseStage( shaderStage_t *stage, char **text ) +{ + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + bool depthMaskExplicit = false; + + stage->active = true; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" ); + return false; + } + + if ( token[0] == '}' ) + { + break; + } + // + // map + // + else if ( !Q_stricmp( token, "map" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return false; + } + + if ( !Q_stricmp( token, "$whiteimage" ) ) + { + stage->bundle[0].image[0] = tr.whiteImage; + continue; + } + else if ( !Q_stricmp( token, "$lightmap" ) ) + { + stage->bundle[0].isLightmap = true; + if ( shader.lightmapIndex < 0 || !tr.lightmaps ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + } + continue; + } + else if ( !Q_stricmp( token, "$deluxemap" ) ) + { + if (!tr.worldDeluxeMapping) + { + ri.Printf( PRINT_WARNING, "WARNING: shader '%s' wants a deluxe map in a map compiled without them\n", shader.name ); + return false; + } + + stage->bundle[0].isLightmap = true; + if ( shader.lightmapIndex < 0 ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.deluxemaps[shader.lightmapIndex]; + } + continue; + } + else + { + imgType_t type = IMGTYPE_COLORALPHA; + int/*imgFlags_t*/ flags = IMGFLAG_NONE; + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + if (stage->type == ST_NORMALMAP || stage->type == ST_NORMALPARALLAXMAP) + { + type = IMGTYPE_NORMAL; + flags |= IMGFLAG_NOLIGHTSCALE; + + if (stage->type == ST_NORMALPARALLAXMAP) + type = IMGTYPE_NORMALHEIGHT; + } + else + { + if (r_genNormalMaps->integer) + flags |= IMGFLAG_GENNORMALMAP; + } + + stage->bundle[0].image[0] = R_FindImageFile( token, type, flags ); + + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return false; + } + } + } + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) + { + imgType_t type = IMGTYPE_COLORALPHA; + int/*imgFlags_t*/ flags = IMGFLAG_CLAMPTOEDGE; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return false; + } + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + if (stage->type == ST_NORMALMAP || stage->type == ST_NORMALPARALLAXMAP) + { + type = IMGTYPE_NORMAL; + flags |= IMGFLAG_NOLIGHTSCALE; + + if (stage->type == ST_NORMALPARALLAXMAP) + type = IMGTYPE_NORMALHEIGHT; + } + else + { + if (r_genNormalMaps->integer) + flags |= IMGFLAG_GENNORMALMAP; + } + + + stage->bundle[0].image[0] = R_FindImageFile( token, type, flags ); + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return false; + } + } + // + // animMap .... + // + else if ( !Q_stricmp( token, "animMap" ) ) + { + int totalImages = 0; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMap' keyword in shader '%s'\n", shader.name ); + return false; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { + int/*imgFlags_t*/ flags = IMGFLAG_NONE; + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + stage->bundle[0].image[num] = R_FindImageFile( token, IMGTYPE_COLORALPHA, flags ); + if ( !stage->bundle[0].image[num] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return false; + } + stage->bundle[0].numImageAnimations++; + } + totalImages++; + } + + if ( totalImages > MAX_IMAGE_ANIMATIONS ) + { + ri.Printf( PRINT_WARNING, "WARNING: ignoring excess images for 'animMap' (found %d, max is %d) in shader '%s'\n", + totalImages, MAX_IMAGE_ANIMATIONS, shader.name ); + } + } + else if ( !Q_stricmp( token, "videoMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMap' keyword in shader '%s'\n", shader.name ); + return false; + } + stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader)); + if (stage->bundle[0].videoMapHandle != -1) { + stage->bundle[0].isVideoMap = true; + stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } else { + ri.Printf( PRINT_WARNING, "WARNING: could not load '%s' for 'videoMap' keyword in shader '%s'\n", token, shader.name ); + } + } + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return false; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return false; + } + + if ( !Q_stricmp( token, "lequal" ) ) + { + depthFuncBits = 0; + } + else if ( !Q_stricmp( token, "equal" ) ) + { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) + { + stage->isDetail = true; + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) + { + depthMaskBits = 0; + } + } + // + // stage + // + else if(!Q_stricmp(token, "stage")) + { + token = COM_ParseExt(text, qfalse); + if(token[0] == 0) + { + ri.Printf(PRINT_WARNING, "WARNING: missing parameters for stage in shader '%s'\n", shader.name); + continue; + } + + if(!Q_stricmp(token, "diffuseMap")) + { + stage->type = ST_DIFFUSEMAP; + } + else if(!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap")) + { + stage->type = ST_NORMALMAP; + VectorSet4(stage->normalScale, r_baseNormalX->value, r_baseNormalY->value, 1.0f, r_baseParallax->value); + } + else if(!Q_stricmp(token, "normalParallaxMap") || !Q_stricmp(token, "bumpParallaxMap")) + { + if (r_parallaxMapping->integer) + stage->type = ST_NORMALPARALLAXMAP; + else + stage->type = ST_NORMALMAP; + VectorSet4(stage->normalScale, r_baseNormalX->value, r_baseNormalY->value, 1.0f, r_baseParallax->value); + } + else if(!Q_stricmp(token, "specularMap")) + { + stage->type = ST_SPECULARMAP; + VectorSet4(stage->specularScale, 1.0f, 1.0f, 1.0f, 1.0f); + } + else + { + ri.Printf(PRINT_WARNING, "WARNING: unknown stage parameter '%s' in shader '%s'\n", token, shader.name); + continue; + } + } + // + // specularReflectance + // + else if (!Q_stricmp(token, "specularreflectance")) + { + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specular reflectance in shader '%s'\n", shader.name ); + continue; + } + + if (r_pbr->integer) + { + // interpret specularReflectance < 0.5 as nonmetal + stage->specularScale[1] = (atof(token) < 0.5f) ? 0.0f : 1.0f; + } + else + { + stage->specularScale[0] = + stage->specularScale[1] = + stage->specularScale[2] = atof( token ); + } + } + // + // specularExponent + // + else if (!Q_stricmp(token, "specularexponent")) + { + float exponent; + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specular exponent in shader '%s'\n", shader.name ); + continue; + } + + exponent = atof( token ); + + if (r_pbr->integer) + stage->specularScale[0] = 1.0f - powf(2.0f / (exponent + 2.0), 0.25); + else + { + // Change shininess to gloss + // Assumes max exponent of 8190 and min of 0, must change here if altered in lightall_fp.glsl + exponent = CLAMP(exponent, 0.0f, 8190.0f); + stage->specularScale[3] = (log2f(exponent + 2.0f) - 1.0f) / 12.0f; + } + } + // + // gloss + // + else if (!Q_stricmp(token, "gloss")) + { + float gloss; + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for gloss in shader '%s'\n", shader.name ); + continue; + } + + gloss = atof(token); + + if (r_pbr->integer) + stage->specularScale[0] = 1.0f - exp2f(-3.0f * gloss); + else + stage->specularScale[3] = gloss; + } + // + // roughness + // + else if (!Q_stricmp(token, "roughness")) + { + float roughness; + + token = COM_ParseExt(text, qfalse); + if (token[0] == 0) + { + ri.Printf(PRINT_WARNING, "WARNING: missing parameter for roughness in shader '%s'\n", shader.name); + continue; + } + + roughness = atof(token); + + if (r_pbr->integer) + stage->specularScale[0] = 1.0 - roughness; + else + { + if (roughness >= 0.125) + stage->specularScale[3] = log2f(1.0f / roughness) / 3.0f; + else + stage->specularScale[3] = 1.0f; + } + } + // + // parallaxDepth + // + else if (!Q_stricmp(token, "parallaxdepth")) + { + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for parallaxDepth in shader '%s'\n", shader.name ); + continue; + } + + stage->normalScale[3] = atof( token ); + } + // + // normalScale + // or normalScale + // or normalScale + // + else if (!Q_stricmp(token, "normalscale")) + { + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for normalScale in shader '%s'\n", shader.name ); + continue; + } + + stage->normalScale[0] = atof( token ); + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + // one value, applies to X/Y + stage->normalScale[1] = stage->normalScale[0]; + continue; + } + + stage->normalScale[1] = atof( token ); + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + // two values, no height + continue; + } + + stage->normalScale[3] = atof( token ); + } + // + // specularScale + // or specularScale with r_pbr 1 + // or specularScale + // or specularScale + // + else if (!Q_stricmp(token, "specularscale")) + { + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specularScale in shader '%s'\n", shader.name ); + continue; + } + + stage->specularScale[0] = atof( token ); + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specularScale in shader '%s'\n", shader.name ); + continue; + } + + stage->specularScale[1] = atof( token ); + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + if (r_pbr->integer) + { + // two values, metallic then smoothness + float smoothness = stage->specularScale[1]; + stage->specularScale[1] = (stage->specularScale[0] < 0.5f) ? 0.0f : 1.0f; + stage->specularScale[0] = smoothness; + } + { + // two values, rgb then gloss + stage->specularScale[3] = stage->specularScale[1]; + stage->specularScale[1] = + stage->specularScale[2] = stage->specularScale[0]; + } + continue; + } + + stage->specularScale[2] = atof( token ); + + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + // three values, rgb + continue; + } + + stage->specularScale[2] = atof( token ); + + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + vec3_t color; + + VectorClear( color ); + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->rgbGen = CGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "identityLighting" ) ) + { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->rgbGen = CGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertex" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX; + } + else if ( !Q_stricmp( token, "vertexLit" ) ) + { + stage->rgbGen = CGEN_VERTEX_LIT; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertexLit" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX_LIT; + } + else if ( !Q_stricmp( token, "lightingDiffuse" ) ) + { + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->alphaGen = AGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->alphaGen = AGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->alphaGen = AGEN_VERTEX; + } + else if ( !Q_stricmp( token, "lightingSpecular" ) ) + { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } + else if ( !Q_stricmp( token, "portal" ) ) + { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + shader.portalRange = 256; + ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } + else + { + shader.portalRange = atof( token ); + } + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) + { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; + } + else if ( !Q_stricmp( token, "lightmap" ) ) + { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) + { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } + else if ( !Q_stricmp( token, "vector" ) ) + { + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + Q_strcat( buffer, sizeof (buffer), token ); + Q_strcat( buffer, sizeof (buffer), " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) + { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = true; + + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return false; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) + { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == AGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return true; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + ds = &shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = (deform_t)(DEFORM_TEXT0 + n); + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) + { + ds->deformationSpread = 1.0f / atof( token ); + } + else + { + ds->deformationSpread = 100.0f; + ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( char **text ) { + char *token; + static const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + int/*imgFlags_t*/ imgFlags = IMGFLAG_MIPMAP | IMGFLAG_PICMIP; + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, imgFlags | IMGFLAG_CLAMPTOEDGE ); + + if ( !shader.sky.outerbox[i] ) { + shader.sky.outerbox[i] = tr.defaultImage; + } + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + shader.sky.cloudHeight = atof( token ); + if ( !shader.sky.cloudHeight ) { + shader.sky.cloudHeight = 512; + } + R_InitSkyTexCoords( shader.sky.cloudHeight ); + + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, imgFlags ); + if ( !shader.sky.innerbox[i] ) { + shader.sky.innerbox[i] = tr.defaultImage; + } + } + } + + shader.isSky = true; +} + + +/* +================= +ParseSort +================= +*/ +void ParseSort( char **text ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + }else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else { + shader.sort = atof( token ); + } +} + + + +// this table is also present in q3map + +typedef struct { + const char *name; + unsigned clearSolid, surfaceFlags, contents; +} infoParm_t; + +infoParm_t infoParms[] = { + // server relevant contents + {"water", 1, 0, CONTENTS_WATER }, + {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging + {"lava", 1, 0, CONTENTS_LAVA }, // very damaging + {"playerclip", 1, 0, CONTENTS_PLAYERCLIP }, + {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP }, + {"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag + + // utility relevant attributes + {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes + {"trans", 0, 0, CONTENTS_TRANSLUCENT }, // don't eat contained surfaces + {"detail", 0, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"structural", 0, 0, CONTENTS_STRUCTURAL }, // force into structural bsp even if trnas + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"clusterportal", 1,0, CONTENTS_CLUSTERPORTAL }, // for bots + {"donotenter", 1, 0, CONTENTS_DONOTENTER }, // for bots + + {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering + {"sky", 0, SURF_SKY, 0 }, // emit light from an environment map + {"lightfilter", 0, SURF_LIGHTFILTER, 0 }, // filter light going through it + {"alphashadow", 0, SURF_ALPHASHADOW, 0 }, // test light on a per-pixel basis + {"hint", 0, SURF_HINT, 0 }, // use as a primary splitter + + // server attributes + {"slick", 0, SURF_SLICK, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nomarks", 0, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode + {"ladder", 0, SURF_LADDER, 0 }, + {"nodamage", 0, SURF_NODAMAGE, 0 }, + {"metalsteps", 0, SURF_METALSTEPS,0 }, + {"flesh", 0, SURF_FLESH, 0 }, + {"nosteps", 0, SURF_NOSTEPS, 0 }, + + // drawsurf attributes + {"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap) + {"pointlight", 0, SURF_POINTLIGHT, 0 }, // sample lighting at vertexes + {"nolightmap", 0, SURF_NOLIGHTMAP,0 }, // don't generate a lightmap + {"nodlight", 0, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights + {"dust", 0, SURF_DUST, 0} // leave a dust trail when walking on this surface +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( char **text ) { + char *token; + int numInfoParms = ARRAY_LEN( infoParms ); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; +#if 0 + if ( infoParms[i].clearSolid ) { + si->contents &= ~CONTENTS_SOLID; + } +#endif + break; + } + } +} + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static bool ParseShader( char **text ) +{ + char *token; + int s; + + s = 0; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return false; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name ); + return false; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + if ( s >= MAX_SHADER_STAGES ) { + ri.Printf( PRINT_WARNING, "WARNING: too many stages in shader %s (max is %i)\n", shader.name, MAX_SHADER_STAGES ); + return false; + } + + if ( !ParseStage( &stages[s], text ) ) + { + return false; + } + stages[s].active = true; + s++; + + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // sun parms + else if ( !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "q3map_sunExt" ) || !Q_stricmp( token, "q3gl2_sun" ) ) { + float a, b; + bool isGL2Sun = false; + + if (!Q_stricmp( token, "q3gl2_sun" ) && r_sunShadows->integer ) + { + isGL2Sun = true; + tr.sunShadows = true; + } + + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + + if (isGL2Sun) + { + token = COM_ParseExt( text, qfalse ); + tr.sunShadowScale = atof(token); + + // parse twice, since older shaders may include mapLightScale before sunShadowScale + token = COM_ParseExt( text, qfalse ); + if (token[0]) + tr.sunShadowScale = atof(token); + } + + SkipRestOfLine( text ); + continue; + } + // tonemap parms + else if ( !Q_stricmp( token, "q3gl2_tonemap" ) ) { + token = COM_ParseExt( text, qfalse ); + tr.toneMinAvgMaxLevel[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.toneMinAvgMaxLevel[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.toneMinAvgMaxLevel[2] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + tr.autoExposureMinMax[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.autoExposureMinMax[1] = atof( token ); + + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "deformVertexes" ) ) { + ParseDeform( text ); + continue; + } + else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "clampTime" ) ) { + token = COM_ParseExt( text, qfalse ); + if (token[0]) { + shader.clampTime = atof(token); + } + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only q3map or the server needs + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( !Q_stricmp( token, "nomipmaps" ) ) + { + shader.noMipMaps = true; + shader.noPicMip = true; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) + { + shader.noPicMip = true; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) + { + shader.polygonOffset = true; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) + { + shader.entityMergable = true; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) + { + if ( !ParseVector( text, 3, shader.fogParms.color ) ) { + return false; + } + + if ( r_greyscale->integer ) + { + float luminance; + + luminance = LUMA( shader.fogParms.color[0], shader.fogParms.color[1], shader.fogParms.color[2] ); + VectorSet( shader.fogParms.color, luminance, luminance, luminance ); + } + else if ( r_greyscale->value ) + { + float luminance; + + luminance = LUMA( shader.fogParms.color[0], shader.fogParms.color[1], shader.fogParms.color[2] ); + shader.fogParms.color[0] = LERP( shader.fogParms.color[0], luminance, r_greyscale->value ); + shader.fogParms.color[1] = LERP( shader.fogParms.color[1], luminance, r_greyscale->value ); + shader.fogParms.color[2] = LERP( shader.fogParms.color[2], luminance, r_greyscale->value ); + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms.depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp(token, "portal") ) + { + shader.sort = SS_PORTAL; + shader.isPortal = true; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) + { + ParseSkyParms( text ); + continue; + } + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp(token, "light") ) + { + COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull") ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) + { + shader.cullType = CT_TWO_SIDED; + } + else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) + { + shader.cullType = CT_BACK_SIDED; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) + { + ParseSort( text ); + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return false; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) { + return false; + } + + shader.explicitlyDefined = true; + + return true; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +/* +=================== +ComputeStageIteratorFunc + +See if we can use on of the simple fastpath stage functions, +otherwise set to the generic stage function +=================== +*/ +static void ComputeStageIteratorFunc( void ) +{ + shader.optimalStageIteratorFunc = RB_StageIteratorGeneric; + + // + // see if this should go into the sky path + // + if ( shader.isSky ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorSky; + return; + } +} + +/* +=================== +ComputeVertexAttribs + +Check which vertex attributes we only need, so we +don't need to submit/copy all of them. +=================== +*/ +static void ComputeVertexAttribs(void) +{ + int i, stage; + + // dlights always need ATTR_NORMAL + shader.vertexAttribs = ATTR_POSITION | ATTR_NORMAL; + + // portals always need normals, for SurfIsOffscreen() + if (shader.isPortal) + { + shader.vertexAttribs |= ATTR_NORMAL; + } + + if (shader.defaultShader) + { + shader.vertexAttribs |= ATTR_TEXCOORD; + return; + } + + if(shader.numDeforms) + { + for ( i = 0; i < shader.numDeforms; i++) + { + deformStage_t *ds = &shader.deforms[i]; + + switch (ds->deformation) + { + case DEFORM_BULGE: + shader.vertexAttribs |= ATTR_NORMAL | ATTR_TEXCOORD; + break; + + case DEFORM_AUTOSPRITE: + shader.vertexAttribs |= ATTR_NORMAL | ATTR_COLOR; + break; + + case DEFORM_WAVE: + case DEFORM_NORMALS: + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + default: + case DEFORM_NONE: + case DEFORM_MOVE: + case DEFORM_PROJECTION_SHADOW: + case DEFORM_AUTOSPRITE2: + break; + } + } + } + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) + { + break; + } + + if (pStage->glslShaderGroup == tr.lightallShader) + { + shader.vertexAttribs |= ATTR_NORMAL; + + if ((pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) && !(r_normalMapping->integer == 0 && r_specularMapping->integer == 0)) + { + shader.vertexAttribs |= ATTR_TANGENT; + } + + switch (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) + { + case LIGHTDEF_USE_LIGHTMAP: + case LIGHTDEF_USE_LIGHT_VERTEX: + shader.vertexAttribs |= ATTR_LIGHTDIRECTION; + break; + default: + break; + } + } + + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if ( pStage->bundle[i].image[0] == 0 ) + { + continue; + } + + switch(pStage->bundle[i].tcGen) + { + case TCGEN_TEXTURE: + shader.vertexAttribs |= ATTR_TEXCOORD; + break; + case TCGEN_LIGHTMAP: + shader.vertexAttribs |= ATTR_LIGHTCOORD; + break; + case TCGEN_ENVIRONMENT_MAPPED: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + default: + break; + } + } + + switch(pStage->rgbGen) + { + case CGEN_EXACT_VERTEX: + case CGEN_VERTEX: + case CGEN_EXACT_VERTEX_LIT: + case CGEN_VERTEX_LIT: + case CGEN_ONE_MINUS_VERTEX: + shader.vertexAttribs |= ATTR_COLOR; + break; + + case CGEN_LIGHTING_DIFFUSE: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + default: + break; + } + + switch(pStage->alphaGen) + { + case AGEN_LIGHTING_SPECULAR: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + case AGEN_VERTEX: + case AGEN_ONE_MINUS_VERTEX: + shader.vertexAttribs |= ATTR_COLOR; + break; + + default: + break; + } + } +} + + +static void CollapseStagesToLightall(shaderStage_t *diffuse, + shaderStage_t *normal, shaderStage_t *specular, shaderStage_t *lightmap, + bool useLightVector, bool useLightVertex, bool parallax, bool tcgen) +{ + int defs = 0; + + //ri.Printf(PRINT_ALL, "shader %s has diffuse %s", shader.name, diffuse->bundle[0].image[0]->imgName); + + // reuse diffuse, mark others inactive + diffuse->type = ST_GLSL; + + if (lightmap) + { + //ri.Printf(PRINT_ALL, ", lightmap"); + diffuse->bundle[TB_LIGHTMAP] = lightmap->bundle[0]; + defs |= LIGHTDEF_USE_LIGHTMAP; + } + else if (useLightVector) + { + defs |= LIGHTDEF_USE_LIGHT_VECTOR; + } + else if (useLightVertex) + { + defs |= LIGHTDEF_USE_LIGHT_VERTEX; + } + + if (r_deluxeMapping->integer && tr.worldDeluxeMapping && lightmap) + { + //ri.Printf(PRINT_ALL, ", deluxemap"); + diffuse->bundle[TB_DELUXEMAP] = lightmap->bundle[0]; + diffuse->bundle[TB_DELUXEMAP].image[0] = tr.deluxemaps[shader.lightmapIndex]; + } + + if (r_normalMapping->integer) + { + image_t *diffuseImg; + if (normal) + { + //ri.Printf(PRINT_ALL, ", normalmap %s", normal->bundle[0].image[0]->imgName); + diffuse->bundle[TB_NORMALMAP] = normal->bundle[0]; + if (parallax && r_parallaxMapping->integer) + defs |= LIGHTDEF_USE_PARALLAXMAP; + + VectorCopy4(normal->normalScale, diffuse->normalScale); + } + else if ((lightmap || useLightVector || useLightVertex) && (diffuseImg = diffuse->bundle[TB_DIFFUSEMAP].image[0])) + { + char normalName[MAX_QPATH]; + image_t *normalImg; + int/*imgFlags_t*/ normalFlags = (diffuseImg->flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE; + + // try a normalheight image first + COM_StripExtension(diffuseImg->imgName, normalName, MAX_QPATH); + Q_strcat(normalName, MAX_QPATH, "_nh"); + + normalImg = R_FindImageFile(normalName, IMGTYPE_NORMALHEIGHT, normalFlags); + + if (normalImg) + { + parallax = true; + } + else + { + // try a normal image ("_n" suffix) + normalName[strlen(normalName) - 1] = '\0'; + normalImg = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags); + } + + if (normalImg) + { + diffuse->bundle[TB_NORMALMAP] = diffuse->bundle[0]; + diffuse->bundle[TB_NORMALMAP].numImageAnimations = 0; + diffuse->bundle[TB_NORMALMAP].image[0] = normalImg; + + if (parallax && r_parallaxMapping->integer) + defs |= LIGHTDEF_USE_PARALLAXMAP; + + VectorSet4(diffuse->normalScale, r_baseNormalX->value, r_baseNormalY->value, 1.0f, r_baseParallax->value); + } + } + } + + if (r_specularMapping->integer) + { + image_t *diffuseImg; + if (specular) + { + //ri.Printf(PRINT_ALL, ", specularmap %s", specular->bundle[0].image[0]->imgName); + diffuse->bundle[TB_SPECULARMAP] = specular->bundle[0]; + VectorCopy4(specular->specularScale, diffuse->specularScale); + } + else if ((lightmap || useLightVector || useLightVertex) && (diffuseImg = diffuse->bundle[TB_DIFFUSEMAP].image[0])) + { + char specularName[MAX_QPATH]; + image_t *specularImg; + int/*imgFlags_t*/ specularFlags = (diffuseImg->flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE; + + COM_StripExtension(diffuseImg->imgName, specularName, MAX_QPATH); + Q_strcat(specularName, MAX_QPATH, "_s"); + + specularImg = R_FindImageFile(specularName, IMGTYPE_COLORALPHA, specularFlags); + + if (specularImg) + { + diffuse->bundle[TB_SPECULARMAP] = diffuse->bundle[0]; + diffuse->bundle[TB_SPECULARMAP].numImageAnimations = 0; + diffuse->bundle[TB_SPECULARMAP].image[0] = specularImg; + + VectorSet4(diffuse->specularScale, 1.0f, 1.0f, 1.0f, 1.0f); + } + } + } + + if (tcgen || diffuse->bundle[0].numTexMods) + { + defs |= LIGHTDEF_USE_TCGEN_AND_TCMOD; + } + + //ri.Printf(PRINT_ALL, ".\n"); + + diffuse->glslShaderGroup = tr.lightallShader; + diffuse->glslShaderIndex = defs; +} + + +static int CollapseStagesToGLSL(void) +{ + int i, j, numStages; + bool skip = false; + + // skip shaders with deforms + if (shader.numDeforms != 0) + { + skip = true; + } + + if (!skip) + { + // if 2+ stages and first stage is lightmap, switch them + // this makes it easier for the later bits to process + if (stages[0].active && stages[0].bundle[0].tcGen == TCGEN_LIGHTMAP && stages[1].active) + { + int blendBits = stages[1].stateBits & ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + if (blendBits == (GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO) + || blendBits == (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR)) + { + int stateBits0 = stages[0].stateBits; + int stateBits1 = stages[1].stateBits; + shaderStage_t swapStage; + + swapStage = stages[0]; + stages[0] = stages[1]; + stages[1] = swapStage; + + stages[0].stateBits = stateBits0; + stages[1].stateBits = stateBits1; + } + } + } + + if (!skip) + { + // scan for shaders that aren't supported + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->adjustColorsForFog) + { + skip = true; + break; + } + + if (pStage->bundle[0].tcGen == TCGEN_LIGHTMAP) + { + int blendBits = pStage->stateBits & ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + if (blendBits != (GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO) + && blendBits != (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR)) + { + skip = true; + break; + } + } + + switch(pStage->bundle[0].tcGen) + { + case TCGEN_TEXTURE: + case TCGEN_LIGHTMAP: + case TCGEN_ENVIRONMENT_MAPPED: + case TCGEN_VECTOR: + break; + default: + skip = true; + break; + } + + switch(pStage->alphaGen) + { + case AGEN_LIGHTING_SPECULAR: + case AGEN_PORTAL: + skip = true; + break; + default: + break; + } + } + } + + if (!skip) + { + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + shaderStage_t *diffuse, *normal, *specular, *lightmap; + bool parallax, tcgen, diffuselit, vertexlit; + + if (!pStage->active) + continue; + + // skip normal and specular maps + if (pStage->type != ST_COLORMAP) + continue; + + // skip lightmaps + if (pStage->bundle[0].tcGen == TCGEN_LIGHTMAP) + continue; + + diffuse = pStage; + normal = NULL; + parallax = false; + specular = NULL; + lightmap = NULL; + + // we have a diffuse map, find matching normal, specular, and lightmap + for (j = i + 1; j < MAX_SHADER_STAGES; j++) + { + shaderStage_t *pStage2 = &stages[j]; + + if (!pStage2->active) + continue; + + switch(pStage2->type) + { + case ST_NORMALMAP: + if (!normal) + { + normal = pStage2; + } + break; + + case ST_NORMALPARALLAXMAP: + if (!normal) + { + normal = pStage2; + parallax = true; + } + break; + + case ST_SPECULARMAP: + if (!specular) + { + specular = pStage2; + } + break; + + case ST_COLORMAP: + if (pStage2->bundle[0].tcGen == TCGEN_LIGHTMAP) + { + lightmap = pStage2; + } + break; + + default: + break; + } + } + + tcgen = false; + if (diffuse->bundle[0].tcGen == TCGEN_ENVIRONMENT_MAPPED + || diffuse->bundle[0].tcGen == TCGEN_LIGHTMAP + || diffuse->bundle[0].tcGen == TCGEN_VECTOR) + { + tcgen = true; + } + + diffuselit = false; + if (diffuse->rgbGen == CGEN_LIGHTING_DIFFUSE) + { + diffuselit = true; + } + + vertexlit = false; + if (diffuse->rgbGen == CGEN_VERTEX_LIT || diffuse->rgbGen == CGEN_EXACT_VERTEX_LIT) + { + vertexlit = true; + } + + CollapseStagesToLightall(diffuse, normal, specular, lightmap, diffuselit, vertexlit, parallax, tcgen); + } + + // deactivate lightmap stages + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->bundle[0].tcGen == TCGEN_LIGHTMAP) + { + pStage->active = false; + } + } + } + + // deactivate normal and specular stages + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->type == ST_NORMALMAP) + { + pStage->active = false; + } + + if (pStage->type == ST_NORMALPARALLAXMAP) + { + pStage->active = false; + } + + if (pStage->type == ST_SPECULARMAP) + { + pStage->active = false; + } + } + + // remove inactive stages + numStages = 0; + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + if (!stages[i].active) + continue; + + if (i == numStages) + { + numStages++; + continue; + } + + stages[numStages] = stages[i]; + stages[i].active = false; + numStages++; + } + + // convert any remaining lightmap stages to a lighting pass with a white texture + // only do this with r_sunlightMode non-zero, as it's only for correct shadows. + if (r_sunlightMode->integer && shader.numDeforms == 0) + { + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->adjustColorsForFog) + continue; + + if (pStage->bundle[TB_DIFFUSEMAP].tcGen == TCGEN_LIGHTMAP) + { + pStage->glslShaderGroup = tr.lightallShader; + pStage->glslShaderIndex = LIGHTDEF_USE_LIGHTMAP; + pStage->bundle[TB_LIGHTMAP] = pStage->bundle[TB_DIFFUSEMAP]; + pStage->bundle[TB_DIFFUSEMAP].image[0] = tr.whiteImage; + pStage->bundle[TB_DIFFUSEMAP].isLightmap = false; + pStage->bundle[TB_DIFFUSEMAP].tcGen = TCGEN_TEXTURE; + } + } + } + + // convert any remaining lightingdiffuse stages to a lighting pass + if (shader.numDeforms == 0) + { + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->adjustColorsForFog) + continue; + + if (pStage->rgbGen == CGEN_LIGHTING_DIFFUSE) + { + pStage->glslShaderGroup = tr.lightallShader; + pStage->glslShaderIndex = LIGHTDEF_USE_LIGHT_VECTOR; + + if (pStage->bundle[0].tcGen != TCGEN_TEXTURE || pStage->bundle[0].numTexMods != 0) + pStage->glslShaderIndex |= LIGHTDEF_USE_TCGEN_AND_TCMOD; + } + } + } + + return numStages; +} + +/* +============= + +FixRenderCommandList +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 +Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated +but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces +to be rendered with bad shaders. To fix this, need to go through all render commands and fix +sortedIndex. +============== +*/ +static void FixRenderCommandList( int newShader ) { + renderCommandList_t *cmdList = &backEndData->commands; + + if( cmdList ) { + const void *curCmd = cmdList->cmds; + + while ( 1 ) { + curCmd = PADP(curCmd, sizeof(void *)); + + switch ( *(const int *)curCmd ) { + case RC_SET_COLOR: + { + const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd; + curCmd = (const void *)(sc_cmd + 1); + break; + } + case RC_STRETCH_PIC: + { + const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd; + curCmd = (const void *)(sp_cmd + 1); + break; + } + case RC_DRAW_SURFS: + { + int i; + drawSurf_t *drawSurf; + shader_t *shader; + int fogNum; + int entityNum; + int dlightMap; + int pshadowMap; + int sortedIndex; + const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd; + + for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) { + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap, &pshadowMap ); + sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1)); + if( sortedIndex >= newShader ) { + sortedIndex++; + drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | ( (int)pshadowMap << QSORT_PSHADOW_SHIFT) | (int)dlightMap; + } + } + curCmd = (const void *)(ds_cmd + 1); + break; + } + case RC_DRAW_BUFFER: + { + const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd; + curCmd = (const void *)(db_cmd + 1); + break; + } + case RC_SWAP_BUFFERS: + { + const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd; + curCmd = (const void *)(sb_cmd + 1); + break; + } + case RC_END_OF_LIST: + default: + return; + } + } + } +} + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted reletive to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i+1] = tr.sortedShaders[i]; + tr.sortedShaders[i+1]->sortedIndex++; + } + + // Arnout: fix rendercommandlist + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 + FixRenderCommandList( i+1 ); + + newShader->sortedIndex = i+1; + tr.sortedShaders[i+1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size, hash; + + if ( tr.numShaders == MAX_SHADERS ) { + ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); + return tr.defaultShader; + } + + newShader = (shader_t*)ri.Hunk_Alloc( sizeof( shader_t ), h_low ); + + *newShader = shader; + + if ( shader.sort <= SS_OPAQUE ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + break; + } + newShader->stages[i] = (shaderStage_t*)ri.Hunk_Alloc( sizeof( stages[i] ), h_low ); + *newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t ); + newShader->stages[i]->bundle[b].texMods = (texModInfo_t*)ri.Hunk_Alloc( size, h_low ); + Com_Memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + } + + SortNewShader(); + + hash = generateHashValue(newShader->name, FILE_HASH_SIZE); + newShader->next = hashTable[hash]; + hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. +================= +*/ +static void VertexLightingCollapse( void ) { + int stage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + Com_Memset( pStage, 0, sizeof( *pStage ) ); + } +} + +/* +=============== +InitShader +=============== +*/ +static void InitShader( const char *name, int lightmapIndex ) { + int i; + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, name, sizeof( shader.name ) ); + shader.lightmapIndex = lightmapIndex; + + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + + // default normal/specular + VectorSet4(stages[i].normalScale, 0.0f, 0.0f, 0.0f, 0.0f); + if (r_pbr->integer) + { + stages[i].specularScale[0] = r_baseGloss->value; + } + else + { + stages[i].specularScale[0] = + stages[i].specularScale[1] = + stages[i].specularScale[2] = r_baseSpecular->value; + stages[i].specularScale[3] = r_baseGloss->value; + } + } +} + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage; + bool hasLightmapStage; + bool vertexLightmap; + + hasLightmapStage = false; + vertexLightmap = false; + + // + // set sky stuff appropriate + // + if ( shader.isSky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + // + // set appropriate stage information + // + for ( stage = 0; stage < MAX_SHADER_STAGES; ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name ); + pStage->active = false; + stage++; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) + { + int index; + + for(index = stage + 1; index < MAX_SHADER_STAGES; index++) + { + if(!stages[index].active) + break; + } + + if(index < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage)); + else + { + if(stage + 1 < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1)); + + Com_Memset(&stages[index - 1], 0, sizeof(*stages)); + } + + continue; + } + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = true; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + + // not a true lightmap but we want to leave existing + // behaviour in place and not print out a warning + //if (pStage->rgbGen == CGEN_VERTEX) { + // vertexLightmap = true; + //} + + + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) { + shader.sort = SS_SEE_THROUGH; + } else { + shader.sort = SS_BLEND0; + } + } + } + + stage++; + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) { + VertexLightingCollapse(); + hasLightmapStage = false; + } + + // + // look for multitexture potential + // + stage = CollapseStagesToGLSL(); + + if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) { + if (vertexLightmap) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); + } else { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + // Don't set this, it will just add duplicate shaders to the hash + //shader.lightmapIndex = LIGHTMAP_NONE; + } + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if (stage == 0 && !shader.isSky) + shader.sort = SS_FOG; + + // determine which stage iterator function is appropriate + ComputeStageIteratorFunc(); + + // determine which vertex attributes this shader needs + ComputeVertexAttribs(); + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static char *FindShaderInShaderText( const char *shadername ) { + + char *token, *p; + + int i, hash; + + hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH); + + if(shaderTextHashTable[hash]) + { + for (i = 0; shaderTextHashTable[hash][i]; i++) + { + p = shaderTextHashTable[hash][i]; + token = COM_ParseExt(&p, qtrue); + + if(!Q_stricmp(token, shadername)) + return p; + } + } + + p = s_shaderText; + + if ( !p ) { + return NULL; + } + + // look for label + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( !Q_stricmp( token, shadername ) ) { + return p; + } + else { + // skip the definition + SkipBracedSection( &p, 0 ); + } + } + + return NULL; +} + + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( (name==NULL) || (name[0] == 0) ) { + return tr.defaultShader; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (Q_stricmp(sh->name, strippedName) == 0) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. + +=============== +*/ +shader_t *R_FindShader( const char *name, int lightmapIndex, bool mipRawImage ) { + char strippedName[MAX_QPATH]; + int hash; + char *shaderText; + image_t *image; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps + if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_BY_VERTEX; + } else if ( lightmapIndex < LIGHTMAP_2D ) { + // negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex]) + ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex ); + lightmapIndex = LIGHTMAP_BY_VERTEX; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh = hashTable[hash]; sh; sh = sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + !Q_stricmp(sh->name, strippedName)) { + // match found + return sh; + } + } + + InitShader( strippedName, lightmapIndex ); + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + // enable this when building a pak file to get a global list + // of all explicit shaders + if ( r_printShaders->integer ) { + ri.Printf( PRINT_ALL, "*SHADER* %s\n", name ); + } + + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = true; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single supported image file + // + { + int/*imgFlags_t*/ flags; + + flags = IMGFLAG_NONE; + + if (mipRawImage) + { + flags |= IMGFLAG_MIPMAP | IMGFLAG_PICMIP; + + if (r_genNormalMaps->integer) + flags |= IMGFLAG_GENNORMALMAP; + } + else + { + flags |= IMGFLAG_CLAMPTOEDGE; + } + + image = R_FindImageFile( name, IMGTYPE_COLORALPHA, flags ); + if ( !image ) { + ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name ); + shader.defaultShader = true; + return FinishShader(); + } + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = true; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + + +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, bool mipRawImage) { + int hash; + shader_t *sh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // probably not necessary since this function + // only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D + // but better safe than sorry. + if ( lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_WHITEIMAGE; + } + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + // index by name + !Q_stricmp(sh->name, name)) { + // match found + return sh->index; + } + } + + InitShader( name, lightmapIndex ); + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = true; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + sh = FinishShader(); + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, true ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, true ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, false ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f (void) { + int i; + int count; + shader_t *shader; + + ri.Printf (PRINT_ALL, "-----------------------\n"); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( ri.Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses ); + + if (shader->lightmapIndex >= 0 ) { + ri.Printf (PRINT_ALL, "L "); + } else { + ri.Printf (PRINT_ALL, " "); + } + if ( shader->explicitlyDefined ) { + ri.Printf( PRINT_ALL, "E " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) { + ri.Printf( PRINT_ALL, "gen " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) { + ri.Printf( PRINT_ALL, "sky " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->defaultShader ) { + ri.Printf (PRINT_ALL, ": %s (DEFAULTED)\n", shader->name); + } else { + ri.Printf (PRINT_ALL, ": %s\n", shader->name); + } + count++; + } + ri.Printf (PRINT_ALL, "%i total shaders\n", count); + ri.Printf (PRINT_ALL, "------------------\n"); +} + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 4096 +static void ScanAndLoadShaderFiles( void ) +{ + char **shaderFiles; + char *buffers[MAX_SHADER_FILES] = {NULL}; + char *p; + int numShaderFiles; + int i; + char *oldp, *token, *hashMem, *textEnd; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + char shaderName[MAX_QPATH]; + int shaderLine; + + long sum = 0, summand; + // scan for shader files + shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles ); + + if ( !shaderFiles || !numShaderFiles ) + { + ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaderFiles > MAX_SHADER_FILES ) { + numShaderFiles = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaderFiles; i++ ) + { + char filename[MAX_QPATH]; + + // look for a .mtr file first + { + char *ext; + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + if ( (ext = strrchr(filename, '.')) ) + { + strcpy(ext, ".mtr"); + } + + if ( ri.FS_ReadFile( filename, NULL ) <= 0 ) + { + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + } + } + + ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename ); + summand = ri.FS_ReadFile( filename, (void **)&buffers[i] ); + + if ( !buffers[i] ) + ri.Error( ERR_DROP, "Couldn't load %s", filename ); + + // Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders. + p = buffers[i]; + COM_BeginParseSession(filename); + while(1) + { + token = COM_ParseExt(&p, qtrue); + + if(!*token) + break; + + Q_strncpyz(shaderName, token, sizeof(shaderName)); + shaderLine = COM_GetCurrentParseLine(); + + token = COM_ParseExt(&p, qtrue); + if(token[0] != '{' || token[1] != '\0') + { + ri.Printf(PRINT_WARNING, "WARNING: Ignoring shader file %s. Shader \"%s\" on line %d missing opening brace", + filename, shaderName, shaderLine); + if (token[0]) + { + ri.Printf(PRINT_WARNING, " (found \"%s\" on line %d)", token, COM_GetCurrentParseLine()); + } + ri.Printf(PRINT_WARNING, ".\n"); + ri.FS_FreeFile(buffers[i]); + buffers[i] = NULL; + break; + } + + if(!SkipBracedSection(&p, 1)) + { + ri.Printf(PRINT_WARNING, "WARNING: Ignoring shader file %s. Shader \"%s\" on line %d missing closing brace.\n", + filename, shaderName, shaderLine); + ri.FS_FreeFile(buffers[i]); + buffers[i] = NULL; + break; + } + } + + + if (buffers[i]) + sum += summand; + } + + // build single large buffer + s_shaderText = (char*)ri.Hunk_Alloc( sum + numShaderFiles*2, h_low ); + s_shaderText[ 0 ] = '\0'; + textEnd = s_shaderText; + + // free in reverse order, so the temp files are all dumped + for ( i = numShaderFiles - 1; i >= 0 ; i-- ) + { + if ( !buffers[i] ) + continue; + + strcat( textEnd, buffers[i] ); + strcat( textEnd, "\n" ); + textEnd += strlen( textEnd ); + ri.FS_FreeFile( buffers[i] ); + } + + COM_Compress( s_shaderText ); + + // free up memory + ri.FS_FreeFileList( shaderFiles ); + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + + p = s_shaderText; + // look for shader names + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTableSizes[hash]++; + size++; + SkipBracedSection(&p, 0); + } + + size += MAX_SHADERTEXT_HASH; + + hashMem = (char*)ri.Hunk_Alloc( size * sizeof(char *), h_low ); + + for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { + shaderTextHashTable[i] = (char **) hashMem; + hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *)); + } + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + + p = s_shaderText; + // look for shader names + while ( 1 ) { + oldp = p; + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; + + SkipBracedSection(&p, 0); + } + + return; + +} + + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + + // init the default shader + InitShader( "", LIGHTMAP_NONE ); + stages[0].bundle[0].image[0] = tr.defaultImage; + stages[0].active = true; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, true ); + tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, true ); + + // Hack to make fogging work correctly on flares. Fog colors are calculated + // in tr_flare.c already. + if(!tr.flareShader->defaultShader) + { + int index; + + for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++) + { + tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE; + tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE; + } + } + + tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, true ); + + tr.sunFlareShader = R_FindShader( "gfx/2d/sunflare", LIGHTMAP_NONE, true); + + // HACK: if sunflare is missing, make one using the flare image or dlight image + if (tr.sunFlareShader->defaultShader) + { + image_t *image; + + if (!tr.flareShader->defaultShader && tr.flareShader->stages[0] && tr.flareShader->stages[0]->bundle[0].image[0]) + image = tr.flareShader->stages[0]->bundle[0].image[0]; + else + image = tr.dlightImage; + + InitShader( "gfx/2d/sunflare", LIGHTMAP_NONE ); + stages[0].bundle[0].image[0] = image; + stages[0].active = true; + stages[0].stateBits = GLS_DEFAULT; + tr.sunFlareShader = FinishShader(); + } + +} + +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders( void ) { + ri.Printf( PRINT_ALL, "Initializing Shaders\n" ); + + Com_Memset(hashTable, 0, sizeof(hashTable)); + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); +} diff --git a/src/renderergl2/tr_shadows.cpp b/src/renderergl2/tr_shadows.cpp new file mode 100644 index 0000000..a602c31 --- /dev/null +++ b/src/renderergl2/tr_shadows.cpp @@ -0,0 +1,327 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES/3]; +static vec3_t shadowXyz[SHADER_MAX_VERTEXES]; + +void R_AddEdgeDef( int i1, int i2, int facing ) { + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +} + +void R_RenderShadowEdges( void ) { + int i; + +#if 0 + int numTris; + + // dumb way -- render every triangle's edges + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + if ( !facing[i] ) { + continue; + } + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( shadowXyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( shadowXyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i3 ] ); + qglVertex3fv( shadowXyz[ i3 ] ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( shadowXyz[ i1 ] ); + qglEnd(); + } +#else + int c, c2; + int j, k; + int i2; + int c_edges, c_rejected; + int hit[2]; + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( shadowXyz[ i ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( shadowXyz[ i2 ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } + } + } +#endif +} + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_ShadowTessEnd( void ) { + int i; + int numTris; + vec3_t lightDir; + GLboolean rgba[4]; + + if ( glConfig.stencilBits < 4 ) { + return; + } + + VectorCopy( backEnd.currentEntity->modelLightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, shadowXyz[i] ); + } + + // decide which triangles face the light + Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + // draw the silhouette edges + + GL_BindToTMU( tr.whiteImage, TB_COLORMAP ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglGetBooleanv(GL_COLOR_WRITEMASK, rgba); + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + GL_Cull( CT_BACK_SIDED ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + GL_Cull( CT_FRONT_SIDED ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + + + // reenable writing to the color buffer + qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]); +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + + GL_BindToTMU( tr.whiteImage, TB_COLORMAP ); + + qglLoadIdentity (); + + qglColor3f( 0.6f, 0.6f, 0.6f ); + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); + +// qglColor3f( 1, 0, 0 ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd (); + + qglColor4f(1,1,1,1); + qglDisable( GL_STENCIL_TEST ); +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.orientation.axis[0][2]; + ground[1] = backEnd.orientation.axis[1][2]; + ground[2] = backEnd.orientation.axis[2][2]; + + groundDist = backEnd.orientation.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->modelLightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, (0.5 - d), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +} diff --git a/src/renderergl2/tr_sky.cpp b/src/renderergl2/tr_sky.cpp new file mode 100644 index 0000000..98fb451 --- /dev/null +++ b/src/renderergl2/tr_sky.cpp @@ -0,0 +1,904 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_sky.c +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + if (dv < 0.001) + continue; // don't divide by zero + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < sky_mins[0][axis]) + sky_mins[0][axis] = s; + if (t < sky_mins[1][axis]) + sky_mins[1][axis] = t; + if (s > sky_maxs[0][axis]) + sky_maxs[0][axis] = s; + if (t > sky_maxs[1][axis]) + sky_maxs[1][axis] = t; + } +} + +#define ON_EPSILON 0.1f // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + bool front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); + if (stage == 6) + { // fully clipped, so draw it + AddSkyPolygon (nump, vecs); + return; + } + + front = back = false; + norm = sky_clip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = true; + sides[i] = SIDE_FRONT; + } + else if (d < -ON_EPSILON) + { + back = true; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + ClipSkyPolygon (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumIndexes; i += 3 ) + { + for (j = 0 ; j < 3 ; j++) + { + VectorSubtract( input->xyz[input->indexes[i+j]], + backEnd.viewParms.orientation.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) +{ + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + b[0] = s*boxSize; + b[1] = t*boxSize; + b[2] = boxSize; + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + { + outXYZ[j] = -b[-k - 1]; + } + else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = (s+1)*0.5; + t = (t+1)*0.5; + if (s < sky_min) + { + s = sky_min; + } + else if (s > sky_max) + { + s = sky_max; + } + + if (t < sky_min) + { + t = sky_min; + } + else if (t > sky_max) + { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) + { + outSt[0] = s; + outSt[1] = t; + } +} + +static int sky_texorder[6] = {0,2,1,3,4,5}; +static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; +static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) +{ + int s, t; + int firstVertex = tess.numVertexes; + //int firstIndex = tess.numIndexes; + vec4_t color; + + //tess.numVertexes = 0; + //tess.numIndexes = 0; + tess.firstIndex = tess.numIndexes; + + GL_BindToTMU( image, TB_COLORMAP ); + GL_Cull( CT_TWO_SIDED ); + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + tess.xyz[tess.numVertexes][0] = s_skyPoints[t][s][0]; + tess.xyz[tess.numVertexes][1] = s_skyPoints[t][s][1]; + tess.xyz[tess.numVertexes][2] = s_skyPoints[t][s][2]; + tess.xyz[tess.numVertexes][3] = 1.0; + + tess.texCoords[tess.numVertexes][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if(tess.numVertexes >= SHADER_MAX_VERTEXES) + { + ri.Error(ERR_DROP, "SHADER_MAX_VERTEXES hit in DrawSkySideVBO()"); + } + } + } + + for ( t = 0; t < maxs[1] - mins[1]; t++ ) + { + for ( s = 0; s < maxs[0] - mins[0]; s++ ) + { + if (tess.numIndexes + 6 >= SHADER_MAX_INDEXES) + { + ri.Error(ERR_DROP, "SHADER_MAX_INDEXES hit in DrawSkySideVBO()"); + } + + tess.indexes[tess.numIndexes++] = s + t * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = s + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = (s + 1) + t * (maxs[0] - mins[0] + 1) + firstVertex; + + tess.indexes[tess.numIndexes++] = (s + 1) + t * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = s + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = (s + 1) + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex; + } + } + + // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function + RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD); +/* + { + shaderProgram_t *sp = &tr.textureColorShader; + + GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD); + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + color[0] = + color[1] = + color[2] = tr.identityLight; + color[3] = 1.0f; + GLSL_SetUniformVec4(sp, UNIFORM_COLOR, color); + } +*/ + { + shaderProgram_t *sp = &tr.lightallShader[0]; + vec4_t vector; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + color[0] = + color[1] = + color[2] = + color[3] = 1.0f; + GLSL_SetUniformVec4(sp, UNIFORM_BASECOLOR, color); + + color[0] = + color[1] = + color[2] = + color[3] = 0.0f; + GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, color); + + VectorSet4(vector, 1.0, 0.0, 0.0, 1.0); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, vector); + + VectorSet4(vector, 0.0, 0.0, 0.0, 0.0); + GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector); + + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + } + + R_DrawElements(tess.numIndexes - tess.firstIndex, tess.firstIndex); + + //qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t))); + + //R_BindNullVBO(); + //R_BindNullIBO(); + + tess.numIndexes = tess.firstIndex; + tess.numVertexes = firstVertex; + tess.firstIndex = 0; +} + +static void DrawSkyBox( shader_t *shader ) +{ + int i; + + sky_min = 0; + sky_max = 1; + + Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for (i=0 ; i<6 ; i++) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky.outerbox[sky_texorder[i]], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], bool addIndexes ) +{ + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.orientation.origin, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if ( tess.numVertexes >= SHADER_MAX_VERTEXES ) + { + ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight-1; t++ ) + { + for ( s = 0; s < sWidth-1; s++ ) + { + tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) +{ + int i; + + for ( i =0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) // FIXME? shader->sky.fullClouds ) + { + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) + continue; + } + else + { + switch( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = static_cast(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS); + sky_mins_subd[1] = static_cast(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS); + sky_maxs_subd[0] = static_cast(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS); + sky_maxs_subd[1] = static_cast(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < MIN_T ) + sky_mins_subd[1] = MIN_T; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < MIN_T ) + sky_maxs_subd[1] = MIN_T; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) +{ + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->isSky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + + if ( shader->sky.cloudHeight ) + { + for ( i = 0; i < MAX_SHADER_STAGES; i++ ) + { + if ( !tess.xstages[i] ) { + break; + } + FillCloudBox( shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ((a)*(a)) +void R_InitSkyTexCoords( float heightCloud ) +{ + int i, s, t; + float radiusWorld = 4096; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = Q_acos( v[0] ); + tRad = Q_acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +** RB_DrawSun +*/ +void RB_DrawSun( float scale, shader_t *shader ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + + if ( !backEnd.skyRenderedThisView ) { + return; + } + + //qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + //qglTranslatef (backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]); + { + // FIXME: this could be a lot cleaner + mat4_t translation, modelview; + + Mat4Translation( backEnd.viewParms.orientation.origin, translation ); + Mat4Multiply( backEnd.viewParms.world.modelMatrix, translation, modelview ); + GL_SetModelviewMatrix( modelview ); + } + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + size = dist * scale; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + RB_BeginSurface( shader, 0, 0 ); + + RB_AddQuadStamp(origin, vec1, vec2, colorWhite); + + RB_EndSurface(); + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) { + if ( r_fastsky->integer ) { + return; + } + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { + qglDepthRange( 1.0, 1.0 ); + } + + // draw the outer skybox + if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) { + mat4_t oldmodelview; + + GL_State( 0 ); + GL_Cull( CT_FRONT_SIDED ); + //qglTranslatef (backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]); + + { + // FIXME: this could be a lot cleaner + mat4_t trans, product; + + Mat4Copy( glState.modelview, oldmodelview ); + Mat4Translation( backEnd.viewParms.orientation.origin, trans ); + Mat4Multiply( glState.modelview, trans, product ); + GL_SetModelviewMatrix( product ); + + } + + DrawSkyBox( tess.shader ); + + GL_SetModelviewMatrix( oldmodelview ); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + RB_StageIteratorGeneric(); + + // draw the inner skybox + + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = true; +} diff --git a/src/renderergl2/tr_subs.cpp b/src/renderergl2/tr_subs.cpp new file mode 100644 index 0000000..e7040e5 --- /dev/null +++ b/src/renderergl2/tr_subs.cpp @@ -0,0 +1,49 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_subs.c - common function replacements for modular renderer + +#include "tr_local.h" + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, msg); + Q_vsnprintf(text, sizeof(text), msg, argptr); + va_end(argptr); + + ri.Printf(PRINT_ALL, "%s", text); +} + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, error); + Q_vsnprintf(text, sizeof(text), error, argptr); + va_end(argptr); + + ri.Error(level, "%s", text); +} diff --git a/src/renderergl2/tr_surface.cpp b/src/renderergl2/tr_surface.cpp new file mode 100644 index 0000000..39f2c34 --- /dev/null +++ b/src/renderergl2/tr_surface.cpp @@ -0,0 +1,1320 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_surf.c +#include "tr_local.h" +#if idppc_altivec && !defined(__APPLE__) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex ); +} + +void RB_CheckVao(vao_t *vao) +{ + if (vao != glState.currentVao) + { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex); + + R_BindVao(vao); + } + + if (vao != tess.vao) + tess.useInternalVao = false; +} + + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4], float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int16_t iNormal[4]; + uint16_t iColor[4]; + int ndx; + + RB_CheckVao(tess.vao); + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.orientation.axis[0], normal ); + + R_VaoPackNormal(iNormal, normal); + + VectorCopy4(iNormal, tess.normal[ndx]); + VectorCopy4(iNormal, tess.normal[ndx + 1]); + VectorCopy4(iNormal, tess.normal[ndx + 2]); + VectorCopy4(iNormal, tess.normal[ndx + 3]); + + // standard square texture coordinates + VectorSet2(tess.texCoords[ndx], s1, t1); + VectorSet2(tess.lightCoords[ndx], s1, t1); + + VectorSet2(tess.texCoords[ndx+1], s2, t1); + VectorSet2(tess.lightCoords[ndx+1], s2, t1); + + VectorSet2(tess.texCoords[ndx+2], s2, t2); + VectorSet2(tess.lightCoords[ndx+2], s2, t2); + + VectorSet2(tess.texCoords[ndx+3], s1, t2); + VectorSet2(tess.lightCoords[ndx+3], s1, t2); + + // constant color all the way around + // should this be identity and let the shader specify from entity? + + R_VaoPackColor(iColor, color); + + VectorCopy4(iColor, tess.color[ndx]); + VectorCopy4(iColor, tess.color[ndx + 1]); + VectorCopy4(iColor, tess.color[ndx + 2]); + VectorCopy4(iColor, tess.color[ndx + 3]); + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, float color[4] ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + + +/* +============== +RB_InstantQuad + +based on Tess_InstantQuad from xreal +============== +*/ +void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]) +{ + GLimp_LogComment("--- RB_InstantQuad2 ---\n"); + + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.firstIndex = 0; + + VectorCopy4(quadVerts[0], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[0], tess.texCoords[tess.numVertexes]); + tess.numVertexes++; + + VectorCopy4(quadVerts[1], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[1], tess.texCoords[tess.numVertexes]); + tess.numVertexes++; + + VectorCopy4(quadVerts[2], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[2], tess.texCoords[tess.numVertexes]); + tess.numVertexes++; + + VectorCopy4(quadVerts[3], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[3], tess.texCoords[tess.numVertexes]); + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD); + + R_DrawElements(tess.numIndexes, tess.firstIndex); + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; +} + + +void RB_InstantQuad(vec4_t quadVerts[4]) +{ + vec2_t texCoords[4]; + + VectorSet2(texCoords[0], 0.0f, 0.0f); + VectorSet2(texCoords[1], 1.0f, 0.0f); + VectorSet2(texCoords[2], 1.0f, 1.0f); + VectorSet2(texCoords[3], 0.0f, 1.0f); + + GLSL_BindProgram(&tr.textureColorShader); + + GLSL_SetUniformMat4(&tr.textureColorShader, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec4(&tr.textureColorShader, UNIFORM_COLOR, colorWhite); + + RB_InstantQuad2(quadVerts, texCoords); +} + + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + float colors[4]; + trRefEntity_t *ent = backEnd.currentEntity; + + // calculate the xyz locations for the four corners + radius = ent->e.radius; + if ( ent->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.orientation.axis[1], radius, left ); + VectorScale( backEnd.viewParms.orientation.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * ent->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.orientation.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.orientation.axis[2], left ); + + VectorScale( backEnd.viewParms.orientation.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.orientation.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + VectorScale4(ent->e.shaderRGBA, 1.0f / 255.0f, colors); + + RB_AddQuadStamp( ent->e.origin, left, up, colors ); +} + + +/* +============= +RB_SurfacePolychain +============= +*/ +static void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CheckVao(tess.vao); + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0] = p->verts[i].st[0]; + tess.texCoords[numv][1] = p->verts[i].st[1]; + tess.color[numv][0] = (int)p->verts[i].modulate[0] * 257; + tess.color[numv][1] = (int)p->verts[i].modulate[1] * 257; + tess.color[numv][2] = (int)p->verts[i].modulate[2] * 257; + tess.color[numv][3] = (int)p->verts[i].modulate[3] * 257; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + +static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIndexes, glIndex_t *indexes, int dlightBits, int pshadowBits) +{ + int i; + glIndex_t *inIndex; + srfVert_t *dv; + float *xyz, *texCoords, *lightCoords; + int16_t *lightdir; + int16_t *normal; + int16_t *tangent; + glIndex_t *outIndex; + uint16_t *color; + + RB_CheckVao(tess.vao); + + RB_CHECKOVERFLOW( numVerts, numIndexes ); + + inIndex = indexes; + outIndex = &tess.indexes[ tess.numIndexes ]; + for ( i = 0 ; i < numIndexes ; i++ ) { + *outIndex++ = tess.numVertexes + *inIndex++; + } + tess.numIndexes += numIndexes; + + if ( tess.shader->vertexAttribs & ATTR_POSITION ) + { + dv = verts; + xyz = tess.xyz[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, xyz+=4 ) + VectorCopy(dv->xyz, xyz); + } + + if ( tess.shader->vertexAttribs & ATTR_NORMAL ) + { + dv = verts; + normal = tess.normal[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, normal+=4 ) + VectorCopy4(dv->normal, normal); + } + + if ( tess.shader->vertexAttribs & ATTR_TANGENT ) + { + dv = verts; + tangent = tess.tangent[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, tangent+=4 ) + VectorCopy4(dv->tangent, tangent); + } + + if ( tess.shader->vertexAttribs & ATTR_TEXCOORD ) + { + dv = verts; + texCoords = tess.texCoords[tess.numVertexes]; + for ( i = 0 ; i < numVerts ; i++, dv++, texCoords+=2 ) + VectorCopy2(dv->st, texCoords); + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD ) + { + dv = verts; + lightCoords = tess.lightCoords[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, lightCoords+=2 ) + VectorCopy2(dv->lightmap, lightCoords); + } + + if ( tess.shader->vertexAttribs & ATTR_COLOR ) + { + dv = verts; + color = tess.color[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, color+=4 ) + VectorCopy4(dv->color, color); + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION ) + { + dv = verts; + lightdir = tess.lightdir[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, lightdir+=4 ) + VectorCopy4(dv->lightdir, lightdir); + } + +#if 0 // nothing even uses vertex dlightbits + for ( i = 0 ; i < numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i ] = dlightBits; + } +#endif + + tess.dlightBits |= dlightBits; + tess.pshadowBits |= pshadowBits; + + tess.numVertexes += numVerts; +} + +static bool RB_SurfaceVaoCached(int numVerts, srfVert_t *verts, int numIndexes, glIndex_t *indexes, int dlightBits, int pshadowBits) +{ + bool recycleVertexBuffer = false; + bool recycleIndexBuffer = false; + bool endSurface = false; + + if (!(!ShaderRequiresCPUDeforms(tess.shader) && !tess.shader->isSky && !tess.shader->isPortal)) + return false; + + if (!numIndexes || !numVerts) + return false; + + VaoCache_BindVao(); + + tess.dlightBits |= dlightBits; + tess.pshadowBits |= pshadowBits; + + VaoCache_CheckAdd(&endSurface, &recycleVertexBuffer, &recycleIndexBuffer, numVerts, numIndexes); + + if (endSurface) + { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex); + } + + if (recycleVertexBuffer) + VaoCache_RecycleVertexBuffer(); + + if (recycleIndexBuffer) + VaoCache_RecycleIndexBuffer(); + + if (!tess.numVertexes) + VaoCache_InitNewSurfaceSet(); + + VaoCache_AddSurface(verts, numVerts, indexes, numIndexes); + + tess.numIndexes += numIndexes; + tess.numVertexes += numVerts; + tess.useInternalVao = false; + tess.useCacheVao = true; + + return true; +} + +/* +============= +RB_SurfaceTriangles +============= +*/ +static void RB_SurfaceTriangles( srfBspSurface_t *srf ) { + if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits)) + { + return; + } + + RB_SurfaceVertsAndIndexes(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits); +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +static void RB_SurfaceBeam( void ) +{ +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + shaderProgram_t *sp = &tr.textureColorShader; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) + return; + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + for ( i = 0; i < NUM_BEAM_SEGS ; i++ ) + { + RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i ); +// VectorAdd( start_points[i], origin, start_points[i] ); + VectorAdd( start_points[i], direction, end_points[i] ); + } + + GL_BindToTMU( tr.whiteImage, TB_COLORMAP ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // FIXME: Quake3 doesn't use this, so I never tested it + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.firstIndex = 0; + + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + VectorCopy(start_points[ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]); + VectorCopy(end_points [ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]); + } + + for ( i = 0; i < NUM_BEAM_SEGS; i++ ) { + tess.indexes[tess.numIndexes++] = i * 2; + tess.indexes[tess.numIndexes++] = (i + 1) * 2; + tess.indexes[tess.numIndexes++] = 1 + i * 2; + + tess.indexes[tess.numIndexes++] = 1 + i * 2; + tess.indexes[tess.numIndexes++] = (i + 1) * 2; + tess.indexes[tess.numIndexes++] = 1 + (i + 1) * 2; + } + + // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function + RB_UpdateTessVao(ATTR_POSITION); + + GLSL_BindProgram(sp); + + GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformVec4(sp, UNIFORM_COLOR, colorRed); + + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + R_DrawElements(tess.numIndexes, tess.firstIndex); + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; +} + +//================================================================================ + +static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth ) +{ + float spanWidth2; + int vbase; + float t = len / 256.0f; + + RB_CheckVao(tess.vao); + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + // FIXME: use quad stamp? + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0] = 0; + tess.texCoords[tess.numVertexes][1] = 0; + tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25f * 257.0f; + tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25f * 257.0f; + tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25f * 257.0f; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0] = 0; + tess.texCoords[tess.numVertexes][1] = 1; + tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257; + tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257; + tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0] = t; + tess.texCoords[tess.numVertexes][1] = 0; + tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257; + tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257; + tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0] = t; + tess.texCoords[tess.numVertexes][1] = 1; + tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257; + tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257; + tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up ) +{ + int i; + vec3_t pos[4]; + vec3_t v; + int spanWidth = r_railWidth->integer; + float c, s; + float scale; + + if ( numSegs > 1 ) + numSegs--; + if ( !numSegs ) + return; + + scale = 0.25; + + for ( i = 0; i < 4; i++ ) + { + c = cos( DEG2RAD( 45 + i * 90 ) ); + s = sin( DEG2RAD( 45 + i * 90 ) ); + v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth; + v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth; + v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth; + VectorAdd( start, v, pos[i] ); + + if ( numSegs > 1 ) + { + // offset by 1 segment if we're doing a long distance shot + VectorAdd( pos[i], dir, pos[i] ); + } + } + + RB_CheckVao(tess.vao); + + for ( i = 0; i < numSegs; i++ ) + { + int j; + + RB_CHECKOVERFLOW( 4, 6 ); + + for ( j = 0; j < 4; j++ ) + { + VectorCopy( pos[j], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0] = (j < 2); + tess.texCoords[tess.numVertexes][1] = (j && j != 3); + tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257; + tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257; + tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257; + tess.numVertexes++; + + VectorAdd( pos[j], dir, pos[j] ); + } + + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2; + } +} + +/* +** RB_SurfaceRailRinges +*/ +static void RB_SurfaceRailRings( void ) { + refEntity_t *e; + int numSegs; + int len; + vec3_t vec; + vec3_t right, up; + vec3_t start, end; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + MakeNormalVectors( vec, right, up ); + numSegs = ( len ) / r_railSegmentLength->value; + if ( numSegs <= 0 ) { + numSegs = 1; + } + + VectorScale( vec, r_railSegmentLength->value, vec ); + + DoRailDiscs( numSegs, start, vec, right, up ); +} + +/* +** RB_SurfaceRailCore +*/ +static void RB_SurfaceRailCore( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.orientation.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.orientation.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoRailCore( start, end, right, len, r_railCoreWidth->integer ); +} + +/* +** RB_SurfaceLightningBolt +*/ +static void RB_SurfaceLightningBolt( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + int i; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.orientation.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.orientation.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + for ( i = 0 ; i < 4 ; i++ ) { + vec3_t temp; + + DoRailCore( start, end, right, len, 8 ); + RotatePointAroundVector( temp, vec, right, 45 ); + VectorCopy( temp, right ); + } +} + + +static void LerpMeshVertexes(mdvSurface_t *surf, float backlerp) +{ + float *outXyz; + int16_t *outNormal, *outTangent; + mdvVertex_t *newVerts; + int vertNum; + + newVerts = surf->verts + backEnd.currentEntity->e.frame * surf->numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + outTangent = tess.tangent[tess.numVertexes]; + + if (backlerp == 0) + { + // + // just copy the vertexes + // + + for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++) + { + VectorCopy(newVerts->xyz, outXyz); + VectorCopy4(newVerts->normal, outNormal); + VectorCopy4(newVerts->tangent, outTangent); + + newVerts++; + outXyz += 4; + outNormal += 4; + outTangent += 4; + } + } + else + { + // + // interpolate and copy the vertex and normal + // + + mdvVertex_t *oldVerts; + + oldVerts = surf->verts + backEnd.currentEntity->e.oldframe * surf->numVerts; + + for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++) + { + VectorLerp(newVerts->xyz, oldVerts->xyz, backlerp, outXyz); + + outNormal[0] = (int16_t)(newVerts->normal[0] * (1.0f - backlerp) + oldVerts->normal[0] * backlerp); + outNormal[1] = (int16_t)(newVerts->normal[1] * (1.0f - backlerp) + oldVerts->normal[1] * backlerp); + outNormal[2] = (int16_t)(newVerts->normal[2] * (1.0f - backlerp) + oldVerts->normal[2] * backlerp); + outNormal[3] = 0; + + outTangent[0] = (int16_t)(newVerts->tangent[0] * (1.0f - backlerp) + oldVerts->tangent[0] * backlerp); + outTangent[1] = (int16_t)(newVerts->tangent[1] * (1.0f - backlerp) + oldVerts->tangent[1] * backlerp); + outTangent[2] = (int16_t)(newVerts->tangent[2] * (1.0f - backlerp) + oldVerts->tangent[2] * backlerp); + outTangent[3] = newVerts->tangent[3]; + + newVerts++; + oldVerts++; + outXyz += 4; + outNormal += 4; + outTangent += 4; + } + } + +} + + +/* +============= +RB_SurfaceMesh +============= +*/ +static void RB_SurfaceMesh(mdvSurface_t *surface) { + int j; + float backlerp; + mdvSt_t *texCoords; + int Bob, Doug; + int numVerts; + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + + RB_CheckVao(tess.vao); + + RB_CHECKOVERFLOW( surface->numVerts, surface->numIndexes ); + + LerpMeshVertexes (surface, backlerp); + + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for (j = 0 ; j < surface->numIndexes ; j++) { + tess.indexes[Bob + j] = Doug + surface->indexes[j]; + } + tess.numIndexes += surface->numIndexes; + + texCoords = surface->st; + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0] = texCoords[j].st[0]; + tess.texCoords[Doug + j][1] = texCoords[j].st[1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} + + +/* +============== +RB_SurfaceFace +============== +*/ +static void RB_SurfaceFace( srfBspSurface_t *srf ) { + if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits)) + { + return; + } + + RB_SurfaceVertsAndIndexes(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits); +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + // never let it go negative + if ( r_lodCurveError->value < 0 ) { + return 0; + } + + world[0] = local[0] * backEnd.orientation.axis[0][0] + local[1] * backEnd.orientation.axis[1][0] + + local[2] * backEnd.orientation.axis[2][0] + backEnd.orientation.origin[0]; + world[1] = local[0] * backEnd.orientation.axis[0][1] + local[1] * backEnd.orientation.axis[1][1] + + local[2] * backEnd.orientation.axis[2][1] + backEnd.orientation.origin[1]; + world[2] = local[0] * backEnd.orientation.axis[0][2] + local[1] * backEnd.orientation.axis[1][2] + + local[2] * backEnd.orientation.axis[2][2] + backEnd.orientation.origin[2]; + + VectorSubtract( world, backEnd.viewParms.orientation.origin, world ); + d = DotProduct( world, backEnd.viewParms.orientation.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +static void RB_SurfaceGrid( srfBspSurface_t *srf ) { + int i, j; + float *xyz; + float *texCoords, *lightCoords; + int16_t *normal; + int16_t *tangent; + uint16_t *color; + int16_t *lightdir; + srfVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int pshadowBits; + //int *vDlightBits; + + if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits)) + { + return; + } + + RB_CheckVao(tess.vao); + + dlightBits = srf->dlightBits; + tess.dlightBits |= dlightBits; + + pshadowBits = srf->pshadowBits; + tess.pshadowBits |= pshadowBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( srf->lodOrigin, srf->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < srf->width-1 ; i++ ) { + if ( srf->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = srf->width-1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < srf->height-1 ; i++ ) { + if ( srf->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = srf->height-1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; + tangent = tess.tangent[numVertexes]; + texCoords = tess.texCoords[numVertexes]; + lightCoords = tess.lightCoords[numVertexes]; + color = tess.color[numVertexes]; + lightdir = tess.lightdir[numVertexes]; + //vDlightBits = &tess.vertexDlightBits[numVertexes]; + + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = srf->verts + heightTable[ used + i ] * srf->width + + widthTable[ j ]; + + if ( tess.shader->vertexAttribs & ATTR_POSITION ) + { + VectorCopy(dv->xyz, xyz); + xyz += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_NORMAL ) + { + VectorCopy4(dv->normal, normal); + normal += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_TANGENT ) + { + VectorCopy4(dv->tangent, tangent); + tangent += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_TEXCOORD ) + { + VectorCopy2(dv->st, texCoords); + texCoords += 2; + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD ) + { + VectorCopy2(dv->lightmap, lightCoords); + lightCoords += 2; + } + + if ( tess.shader->vertexAttribs & ATTR_COLOR ) + { + VectorCopy4(dv->color, color); + color += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION ) + { + VectorCopy4(dv->lightdir, lightdir); + lightdir += 4; + } + + //*vDlightBits++ = dlightBits; + } + } + + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for (i = 0 ; i < h ; i++) { + for (j = 0 ; j < w ; j++) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i*lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes+1] = v3; + tess.indexes[numIndexes+2] = v1; + + tess.indexes[numIndexes+3] = v1; + tess.indexes[numIndexes+4] = v3; + tess.indexes[numIndexes+5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +static void RB_SurfaceAxis( void ) { + // FIXME: implement this +#if 0 + GL_BindToTMU( tr.whiteImage, TB_COLORMAP ); + GL_State( GLS_DEFAULT ); + qglLineWidth( 3 ); + qglBegin( GL_LINES ); + qglColor3f( 1,0,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 16,0,0 ); + qglColor3f( 0,1,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,16,0 ); + qglColor3f( 0,0,1 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,0,16 ); + qglEnd(); + qglLineWidth( 1 ); +#endif +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +static void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch( backEnd.currentEntity->e.reType ) { + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_RAIL_CORE: + RB_SurfaceRailCore(); + break; + case RT_RAIL_RINGS: + RB_SurfaceRailRings(); + break; + case RT_LIGHTNING: + RB_SurfaceLightningBolt(); + break; + default: + RB_SurfaceAxis(); + break; + } +} + +static void RB_SurfaceBad( surfaceType_t *surfType ) { + ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + +static void RB_SurfaceFlare(srfFlare_t *surf) +{ + if (r_flares->integer) + RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal); +} + +void RB_SurfaceVaoMdvMesh(srfVaoMdvMesh_t * surface) +{ + //mdvModel_t *mdvModel; + //mdvSurface_t *mdvSurface; + refEntity_t *refEnt; + + GLimp_LogComment("--- RB_SurfaceVaoMdvMesh ---\n"); + + if (ShaderRequiresCPUDeforms(tess.shader)) + { + RB_SurfaceMesh(surface->mdvSurface); + return; + } + + if(!surface->vao) + return; + + //RB_CheckVao(surface->vao); + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex); + + R_BindVao(surface->vao); + + tess.useInternalVao = false; + + tess.numIndexes = surface->numIndexes; + tess.numVertexes = surface->numVerts; + + //mdvModel = surface->mdvModel; + //mdvSurface = surface->mdvSurface; + + refEnt = &backEnd.currentEntity->e; + + glState.vertexAttribsInterpolation = (refEnt->oldframe == refEnt->frame) ? 0.0f : refEnt->backlerp; + + if (surface->mdvModel->numFrames > 1) + { + int frameOffset, attribIndex; + vaoAttrib_t *vAtb; + + glState.vertexAnimation = true; + + if (glRefConfig.vertexArrayObject) + { + qglBindBuffer(GL_ARRAY_BUFFER, surface->vao->vertexesVBO); + } + + frameOffset = refEnt->frame * surface->vao->frameSize; + + attribIndex = ATTR_INDEX_POSITION; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset)); + + attribIndex = ATTR_INDEX_NORMAL; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset)); + + attribIndex = ATTR_INDEX_TANGENT; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset)); + + frameOffset = refEnt->oldframe * surface->vao->frameSize; + + attribIndex = ATTR_INDEX_POSITION2; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset)); + + attribIndex = ATTR_INDEX_NORMAL2; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset)); + + attribIndex = ATTR_INDEX_TANGENT2; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset)); + + + if (!glRefConfig.vertexArrayObject) + { + attribIndex = ATTR_INDEX_TEXCOORD; + vAtb = &surface->vao->attribs[attribIndex]; + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset)); + } + } + + RB_EndSurface(); + + // So we don't lerp surfaces that shouldn't be lerped + glState.vertexAnimation = false; +} + +static void RB_SurfaceSkip( void *surf ) { +} + + +void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { + (void(*)(void*))RB_SurfaceBad, // SF_BAD, + (void(*)(void*))RB_SurfaceSkip, // SF_SKIP, + (void(*)(void*))RB_SurfaceFace, // SF_FACE, + (void(*)(void*))RB_SurfaceGrid, // SF_GRID, + (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES, + (void(*)(void*))RB_SurfacePolychain, // SF_POLY, + (void(*)(void*))RB_SurfaceMesh, // SF_MDV, + (void(*)(void*))RB_MDRSurfaceAnim, // SF_MDR, + (void(*)(void*))RB_IQMSurfaceAnim, // SF_IQM, + (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, + (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY + (void(*)(void*))RB_SurfaceVaoMdvMesh, // SF_VAO_MDVMESH +}; diff --git a/src/renderergl2/tr_vbo.cpp b/src/renderergl2/tr_vbo.cpp new file mode 100644 index 0000000..58836cd --- /dev/null +++ b/src/renderergl2/tr_vbo.cpp @@ -0,0 +1,945 @@ +/* +=========================================================================== +Copyright (C) 2007-2009 Robert Beckebans +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// tr_vbo.c +#include "tr_local.h" + + +void R_VaoPackTangent(int16_t *out, vec4_t v) +{ + out[0] = v[0] * 32767.0f + (v[0] > 0.0f ? 0.5f : -0.5f); + out[1] = v[1] * 32767.0f + (v[1] > 0.0f ? 0.5f : -0.5f); + out[2] = v[2] * 32767.0f + (v[2] > 0.0f ? 0.5f : -0.5f); + out[3] = v[3] * 32767.0f + (v[3] > 0.0f ? 0.5f : -0.5f); +} + +void R_VaoPackNormal(int16_t *out, vec3_t v) +{ + out[0] = v[0] * 32767.0f + (v[0] > 0.0f ? 0.5f : -0.5f); + out[1] = v[1] * 32767.0f + (v[1] > 0.0f ? 0.5f : -0.5f); + out[2] = v[2] * 32767.0f + (v[2] > 0.0f ? 0.5f : -0.5f); + out[3] = 0; +} + +void R_VaoPackColor(uint16_t *out, vec4_t c) +{ + out[0] = c[0] * 65535.0f + 0.5f; + out[1] = c[1] * 65535.0f + 0.5f; + out[2] = c[2] * 65535.0f + 0.5f; + out[3] = c[3] * 65535.0f + 0.5f; +} + +void R_VaoUnpackTangent(vec4_t v, int16_t *pack) +{ + v[0] = pack[0] / 32767.0f; + v[1] = pack[1] / 32767.0f; + v[2] = pack[2] / 32767.0f; + v[3] = pack[3] / 32767.0f; +} + +void R_VaoUnpackNormal(vec3_t v, int16_t *pack) +{ + v[0] = pack[0] / 32767.0f; + v[1] = pack[1] / 32767.0f; + v[2] = pack[2] / 32767.0f; +} + +void Vao_SetVertexPointers(vao_t *vao) +{ + int attribIndex; + + // set vertex pointers + for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++) + { + uint32_t attribBit = 1 << attribIndex; + vaoAttrib_t *vAtb = &vao->attribs[attribIndex]; + + if (vAtb->enabled) + { + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset)); + if (glRefConfig.vertexArrayObject || !(glState.vertexAttribsEnabled & attribBit)) + qglEnableVertexAttribArray(attribIndex); + + if (!glRefConfig.vertexArrayObject || vao == tess.vao) + glState.vertexAttribsEnabled |= attribBit; + } + else + { + // don't disable vertex attribs when using vertex array objects + // Vao_SetVertexPointers is only called during init when using VAOs, and vertex attribs start disabled anyway + if (!glRefConfig.vertexArrayObject && (glState.vertexAttribsEnabled & attribBit)) + qglDisableVertexAttribArray(attribIndex); + + if (!glRefConfig.vertexArrayObject || vao == tess.vao) + glState.vertexAttribsEnabled &= ~attribBit; + } + } +} + +/* +============ +R_CreateVao +============ +*/ +vao_t *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage) +{ + vao_t *vao; + int glUsage; + + switch (usage) + { + case VAO_USAGE_STATIC: + glUsage = GL_STATIC_DRAW; + break; + + case VAO_USAGE_DYNAMIC: + glUsage = GL_DYNAMIC_DRAW; + break; + + default: + Com_Error(ERR_FATAL, "bad vaoUsage_t given: %i", usage); + return NULL; + } + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "R_CreateVao: \"%s\" is too long", name); + } + + if ( tr.numVaos == MAX_VAOS ) { + ri.Error( ERR_DROP, "R_CreateVao: MAX_VAOS hit"); + } + + R_IssuePendingRenderCommands(); + + vao = tr.vaos[tr.numVaos] = (vao_t*)ri.Hunk_Alloc(sizeof(*vao), h_low); + tr.numVaos++; + + memset(vao, 0, sizeof(*vao)); + + Q_strncpyz(vao->name, name, sizeof(vao->name)); + + + if (glRefConfig.vertexArrayObject) + { + qglGenVertexArrays(1, &vao->vao); + qglBindVertexArray(vao->vao); + } + + + vao->vertexesSize = vertexesSize; + + qglGenBuffers(1, &vao->vertexesVBO); + + qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO); + qglBufferData(GL_ARRAY_BUFFER, vertexesSize, vertexes, glUsage); + + + vao->indexesSize = indexesSize; + + qglGenBuffers(1, &vao->indexesIBO); + + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO); + qglBufferData(GL_ELEMENT_ARRAY_BUFFER, indexesSize, indexes, glUsage); + + + glState.currentVao = vao; + + GL_CheckErrors(); + + return vao; +} + +/* +============ +R_CreateVao2 +============ +*/ +vao_t *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *indexes) +{ + vao_t *vao; + int i; + + byte *data; + int dataSize; + int dataOfs; + + int glUsage = GL_STATIC_DRAW; + + if(!numVertexes || !numIndexes) + return NULL; + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "R_CreateVao2: \"%s\" is too long", name); + } + + if ( tr.numVaos == MAX_VAOS ) { + ri.Error( ERR_DROP, "R_CreateVao2: MAX_VAOS hit"); + } + + R_IssuePendingRenderCommands(); + + vao = tr.vaos[tr.numVaos] = (vao_t*)ri.Hunk_Alloc(sizeof(*vao), h_low); + tr.numVaos++; + + memset(vao, 0, sizeof(*vao)); + + Q_strncpyz(vao->name, name, sizeof(vao->name)); + + // since these vertex attributes are never altered, interleave them + vao->attribs[ATTR_INDEX_POSITION ].enabled = 1; + vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1; + vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1; + vao->attribs[ATTR_INDEX_TEXCOORD ].enabled = 1; + vao->attribs[ATTR_INDEX_LIGHTCOORD ].enabled = 1; + vao->attribs[ATTR_INDEX_COLOR ].enabled = 1; + vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1; + + vao->attribs[ATTR_INDEX_POSITION ].count = 3; + vao->attribs[ATTR_INDEX_NORMAL ].count = 4; + vao->attribs[ATTR_INDEX_TANGENT ].count = 4; + vao->attribs[ATTR_INDEX_TEXCOORD ].count = 2; + vao->attribs[ATTR_INDEX_LIGHTCOORD ].count = 2; + vao->attribs[ATTR_INDEX_COLOR ].count = 4; + vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4; + + vao->attribs[ATTR_INDEX_POSITION ].type = GL_FLOAT; + vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT; + vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT; + vao->attribs[ATTR_INDEX_TEXCOORD ].type = GL_FLOAT; + vao->attribs[ATTR_INDEX_LIGHTCOORD ].type = GL_FLOAT; + vao->attribs[ATTR_INDEX_COLOR ].type = GL_UNSIGNED_SHORT; + vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT; + + vao->attribs[ATTR_INDEX_POSITION ].normalized = GL_FALSE; + vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE; + vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE; + vao->attribs[ATTR_INDEX_TEXCOORD ].normalized = GL_FALSE; + vao->attribs[ATTR_INDEX_LIGHTCOORD ].normalized = GL_FALSE; + vao->attribs[ATTR_INDEX_COLOR ].normalized = GL_TRUE; + vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE; + + vao->attribs[ATTR_INDEX_POSITION ].offset = 0; dataSize = sizeof(verts[0].xyz); + vao->attribs[ATTR_INDEX_NORMAL ].offset = dataSize; dataSize += sizeof(verts[0].normal); + vao->attribs[ATTR_INDEX_TANGENT ].offset = dataSize; dataSize += sizeof(verts[0].tangent); + vao->attribs[ATTR_INDEX_TEXCOORD ].offset = dataSize; dataSize += sizeof(verts[0].st); + vao->attribs[ATTR_INDEX_LIGHTCOORD ].offset = dataSize; dataSize += sizeof(verts[0].lightmap); + vao->attribs[ATTR_INDEX_COLOR ].offset = dataSize; dataSize += sizeof(verts[0].color); + vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(verts[0].lightdir); + + vao->attribs[ATTR_INDEX_POSITION ].stride = dataSize; + vao->attribs[ATTR_INDEX_NORMAL ].stride = dataSize; + vao->attribs[ATTR_INDEX_TANGENT ].stride = dataSize; + vao->attribs[ATTR_INDEX_TEXCOORD ].stride = dataSize; + vao->attribs[ATTR_INDEX_LIGHTCOORD ].stride = dataSize; + vao->attribs[ATTR_INDEX_COLOR ].stride = dataSize; + vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize; + + + if (glRefConfig.vertexArrayObject) + { + qglGenVertexArrays(1, &vao->vao); + qglBindVertexArray(vao->vao); + } + + + // create VBO + dataSize *= numVertexes; + data = (byte*)ri.Hunk_AllocateTempMemory(dataSize); + dataOfs = 0; + + for (i = 0; i < numVertexes; i++) + { + // xyz + memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz)); + dataOfs += sizeof(verts[i].xyz); + + // normal + memcpy(data + dataOfs, &verts[i].normal, sizeof(verts[i].normal)); + dataOfs += sizeof(verts[i].normal); + + // tangent + memcpy(data + dataOfs, &verts[i].tangent, sizeof(verts[i].tangent)); + dataOfs += sizeof(verts[i].tangent); + + // texcoords + memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st)); + dataOfs += sizeof(verts[i].st); + + // lightmap texcoords + memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap)); + dataOfs += sizeof(verts[i].lightmap); + + // colors + memcpy(data + dataOfs, &verts[i].color, sizeof(verts[i].color)); + dataOfs += sizeof(verts[i].color); + + // light directions + memcpy(data + dataOfs, &verts[i].lightdir, sizeof(verts[i].lightdir)); + dataOfs += sizeof(verts[i].lightdir); + } + + vao->vertexesSize = dataSize; + + qglGenBuffers(1, &vao->vertexesVBO); + + qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO); + qglBufferData(GL_ARRAY_BUFFER, vao->vertexesSize, data, glUsage); + + + // create IBO + vao->indexesSize = numIndexes * sizeof(glIndex_t); + + qglGenBuffers(1, &vao->indexesIBO); + + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO); + qglBufferData(GL_ELEMENT_ARRAY_BUFFER, vao->indexesSize, indexes, glUsage); + + + Vao_SetVertexPointers(vao); + + + glState.currentVao = vao; + + GL_CheckErrors(); + + ri.Hunk_FreeTempMemory(data); + + return vao; +} + + +/* +============ +R_BindVao +============ +*/ +void R_BindVao(vao_t * vao) +{ + if(!vao) + { + //R_BindNullVao(); + ri.Error(ERR_DROP, "R_BindVao: NULL vao"); + return; + } + + if(r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment((char*)va("--- R_BindVao( %s ) ---\n", vao->name)); + } + + if(glState.currentVao != vao) + { + glState.currentVao = vao; + + glState.vertexAttribsInterpolation = 0; + glState.vertexAnimation = false; + backEnd.pc.c_vaoBinds++; + + if (glRefConfig.vertexArrayObject) + { + qglBindVertexArray(vao->vao); + + // Intel Graphics doesn't save GL_ELEMENT_ARRAY_BUFFER binding with VAO binding. + if (glRefConfig.intelGraphics || vao == tess.vao) + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO); + + // tess VAO always has buffers bound + if (vao == tess.vao) + qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO); + } + else + { + qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO); + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO); + + // tess VAO doesn't have vertex pointers set until data is uploaded + if (vao != tess.vao) + Vao_SetVertexPointers(vao); + } + } +} + +/* +============ +R_BindNullVao +============ +*/ +void R_BindNullVao(void) +{ + GLimp_LogComment("--- R_BindNullVao ---\n"); + + if(glState.currentVao) + { + if (glRefConfig.vertexArrayObject) + { + qglBindVertexArray(0); + + // why you no save GL_ELEMENT_ARRAY_BUFFER binding, Intel? + if (1) qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + else + { + qglBindBuffer(GL_ARRAY_BUFFER, 0); + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + glState.currentVao = NULL; + } + + GL_CheckErrors(); +} + + +/* +============ +R_InitVaos +============ +*/ +void R_InitVaos(void) +{ + int vertexesSize, indexesSize; + int offset; + + ri.Printf(PRINT_ALL, "------- R_InitVaos -------\n"); + + tr.numVaos = 0; + + vertexesSize = sizeof(tess.xyz[0]); + vertexesSize += sizeof(tess.normal[0]); + vertexesSize += sizeof(tess.tangent[0]); + vertexesSize += sizeof(tess.color[0]); + vertexesSize += sizeof(tess.texCoords[0]); + vertexesSize += sizeof(tess.lightCoords[0]); + vertexesSize += sizeof(tess.lightdir[0]); + vertexesSize *= SHADER_MAX_VERTEXES; + + indexesSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES; + + tess.vao = R_CreateVao("tessVertexArray_VAO", NULL, vertexesSize, NULL, indexesSize, VAO_USAGE_DYNAMIC); + + offset = 0; + + tess.vao->attribs[ATTR_INDEX_POSITION ].enabled = 1; + tess.vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1; + tess.vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1; + tess.vao->attribs[ATTR_INDEX_TEXCOORD ].enabled = 1; + tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].enabled = 1; + tess.vao->attribs[ATTR_INDEX_COLOR ].enabled = 1; + tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1; + + tess.vao->attribs[ATTR_INDEX_POSITION ].count = 3; + tess.vao->attribs[ATTR_INDEX_NORMAL ].count = 4; + tess.vao->attribs[ATTR_INDEX_TANGENT ].count = 4; + tess.vao->attribs[ATTR_INDEX_TEXCOORD ].count = 2; + tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].count = 2; + tess.vao->attribs[ATTR_INDEX_COLOR ].count = 4; + tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4; + + tess.vao->attribs[ATTR_INDEX_POSITION ].type = GL_FLOAT; + tess.vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT; + tess.vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT; + tess.vao->attribs[ATTR_INDEX_TEXCOORD ].type = GL_FLOAT; + tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].type = GL_FLOAT; + tess.vao->attribs[ATTR_INDEX_COLOR ].type = GL_UNSIGNED_SHORT; + tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT; + + tess.vao->attribs[ATTR_INDEX_POSITION ].normalized = GL_FALSE; + tess.vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE; + tess.vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE; + tess.vao->attribs[ATTR_INDEX_TEXCOORD ].normalized = GL_FALSE; + tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].normalized = GL_FALSE; + tess.vao->attribs[ATTR_INDEX_COLOR ].normalized = GL_TRUE; + tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE; + + tess.vao->attribs[ATTR_INDEX_POSITION ].offset = offset; offset += sizeof(tess.xyz[0]) * SHADER_MAX_VERTEXES; + tess.vao->attribs[ATTR_INDEX_NORMAL ].offset = offset; offset += sizeof(tess.normal[0]) * SHADER_MAX_VERTEXES; + tess.vao->attribs[ATTR_INDEX_TANGENT ].offset = offset; offset += sizeof(tess.tangent[0]) * SHADER_MAX_VERTEXES; + tess.vao->attribs[ATTR_INDEX_TEXCOORD ].offset = offset; offset += sizeof(tess.texCoords[0]) * SHADER_MAX_VERTEXES; + tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].offset = offset; offset += sizeof(tess.lightCoords[0]) * SHADER_MAX_VERTEXES; + tess.vao->attribs[ATTR_INDEX_COLOR ].offset = offset; offset += sizeof(tess.color[0]) * SHADER_MAX_VERTEXES; + tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = offset; + + tess.vao->attribs[ATTR_INDEX_POSITION ].stride = sizeof(tess.xyz[0]); + tess.vao->attribs[ATTR_INDEX_NORMAL ].stride = sizeof(tess.normal[0]); + tess.vao->attribs[ATTR_INDEX_TANGENT ].stride = sizeof(tess.tangent[0]); + tess.vao->attribs[ATTR_INDEX_TEXCOORD ].stride = sizeof(tess.texCoords[0]); + tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].stride = sizeof(tess.lightCoords[0]); + tess.vao->attribs[ATTR_INDEX_COLOR ].stride = sizeof(tess.color[0]); + tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = sizeof(tess.lightdir[0]); + + tess.attribPointers[ATTR_INDEX_POSITION] = tess.xyz; + tess.attribPointers[ATTR_INDEX_NORMAL] = tess.normal; + tess.attribPointers[ATTR_INDEX_TANGENT] = tess.tangent; + tess.attribPointers[ATTR_INDEX_TEXCOORD] = tess.texCoords; + tess.attribPointers[ATTR_INDEX_LIGHTCOORD] = tess.lightCoords; + tess.attribPointers[ATTR_INDEX_COLOR] = tess.color; + tess.attribPointers[ATTR_INDEX_LIGHTDIRECTION] = tess.lightdir; + + Vao_SetVertexPointers(tess.vao); + + R_BindNullVao(); + + VaoCache_Init(); + + GL_CheckErrors(); +} + +/* +============ +R_ShutdownVaos +============ +*/ +void R_ShutdownVaos(void) +{ + int i; + vao_t *vao; + + ri.Printf(PRINT_ALL, "------- R_ShutdownVaos -------\n"); + + R_BindNullVao(); + + for(i = 0; i < tr.numVaos; i++) + { + vao = tr.vaos[i]; + + if(vao->vao) + qglDeleteVertexArrays(1, &vao->vao); + + if(vao->vertexesVBO) + { + qglDeleteBuffers(1, &vao->vertexesVBO); + } + + if(vao->indexesIBO) + { + qglDeleteBuffers(1, &vao->indexesIBO); + } + } + + tr.numVaos = 0; +} + +/* +============ +R_VaoList_f +============ +*/ +void R_VaoList_f(void) +{ + int i; + vao_t *vao; + int vertexesSize = 0; + int indexesSize = 0; + + ri.Printf(PRINT_ALL, " size name\n"); + ri.Printf(PRINT_ALL, "----------------------------------------------------------\n"); + + for(i = 0; i < tr.numVaos; i++) + { + vao = tr.vaos[i]; + + ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->vertexesSize / (1024 * 1024), + (vao->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name); + + vertexesSize += vao->vertexesSize; + } + + for(i = 0; i < tr.numVaos; i++) + { + vao = tr.vaos[i]; + + ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->indexesSize / (1024 * 1024), + (vao->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name); + + indexesSize += vao->indexesSize; + } + + ri.Printf(PRINT_ALL, " %i total VAOs\n", tr.numVaos); + ri.Printf(PRINT_ALL, " %d.%02d MB total vertices memory\n", vertexesSize / (1024 * 1024), + (vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024)); + ri.Printf(PRINT_ALL, " %d.%02d MB total triangle indices memory\n", indexesSize / (1024 * 1024), + (indexesSize % (1024 * 1024)) * 100 / (1024 * 1024)); +} + + +/* +============== +RB_UpdateTessVao + +Adapted from Tess_UpdateVBOs from xreal + +Update the default VAO to replace the client side vertex arrays +============== +*/ +void RB_UpdateTessVao(unsigned int attribBits) +{ + GLimp_LogComment("--- RB_UpdateTessVao ---\n"); + + backEnd.pc.c_dynamicVaoDraws++; + + // update the default VAO + if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES && tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES) + { + int attribIndex; + int attribUpload; + + R_BindVao(tess.vao); + + // orphan old vertex buffer so we don't stall on it + qglBufferData(GL_ARRAY_BUFFER, tess.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW); + + // if nothing to set, set everything + if(!(attribBits & ATTR_BITS)) + attribBits = ATTR_BITS; + + attribUpload = attribBits; + + for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++) + { + uint32_t attribBit = 1 << attribIndex; + vaoAttrib_t *vAtb = &tess.vao->attribs[attribIndex]; + + if (attribUpload & attribBit) + { + // note: tess has a VBO where stride == size + qglBufferSubData(GL_ARRAY_BUFFER, vAtb->offset, tess.numVertexes * vAtb->stride, tess.attribPointers[attribIndex]); + } + + if (attribBits & attribBit) + { + if (!glRefConfig.vertexArrayObject) + qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset)); + + if (!(glState.vertexAttribsEnabled & attribBit)) + { + qglEnableVertexAttribArray(attribIndex); + glState.vertexAttribsEnabled |= attribBit; + } + } + else + { + if ((glState.vertexAttribsEnabled & attribBit)) + { + qglDisableVertexAttribArray(attribIndex); + glState.vertexAttribsEnabled &= ~attribBit; + } + } + } + + // orphan old index buffer so we don't stall on it + qglBufferData(GL_ELEMENT_ARRAY_BUFFER, tess.vao->indexesSize, NULL, GL_DYNAMIC_DRAW); + + qglBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes); + } +} + +typedef struct bufferCacheEntry_s +{ + void *data; + int size; + int bufferOffset; +} +bufferCacheEntry_t; + +typedef struct queuedSurface_s +{ + srfVert_t *vertexes; + int numVerts; + glIndex_t *indexes; + int numIndexes; +} +queuedSurface_t; + +#define VAOCACHE_MAX_BUFFERED_SURFACES (1 << 16) +#define VAOCACHE_VERTEX_BUFFER_SIZE 12 * 1024 * 1024 +#define VAOCACHE_INDEX_BUFFER_SIZE 12 * 1024 * 1024 + +#define VAOCACHE_MAX_QUEUED_VERTEXES (1 << 16) +#define VAOCACHE_MAX_QUEUED_INDEXES (VAOCACHE_MAX_QUEUED_VERTEXES * 6 / 4) + +#define VAOCACHE_MAX_QUEUED_SURFACES 4096 + +static struct +{ + vao_t *vao; + bufferCacheEntry_t indexEntries[VAOCACHE_MAX_BUFFERED_SURFACES]; + int indexChainLengths[VAOCACHE_MAX_BUFFERED_SURFACES]; + int numIndexEntries; + int vertexOffset; + int indexOffset; + + srfVert_t vertexes[VAOCACHE_MAX_QUEUED_VERTEXES]; + int vertexCommitSize; + + glIndex_t indexes[VAOCACHE_MAX_QUEUED_INDEXES]; + int indexCommitSize; + + queuedSurface_t surfaceQueue[VAOCACHE_MAX_QUEUED_SURFACES]; + int numSurfacesQueued; +} +vc = { 0 }; + +void VaoCache_Commit(void) +{ + bufferCacheEntry_t *entry1; + queuedSurface_t *surf, *end = vc.surfaceQueue + vc.numSurfacesQueued; + + R_BindVao(vc.vao); + + // search entire cache for exact chain of indexes + // FIXME: use faster search + for (entry1 = vc.indexEntries; entry1 < vc.indexEntries + vc.numIndexEntries;) + { + int chainLength = vc.indexChainLengths[entry1 - vc.indexEntries]; + + if (chainLength == vc.numSurfacesQueued) + { + bufferCacheEntry_t *entry2 = entry1; + for (surf = vc.surfaceQueue; surf < end; surf++, entry2++) + { + if (surf->indexes != entry2->data || (surf->numIndexes * sizeof(glIndex_t)) != entry2->size) + break; + } + + if (surf == end) + break; + } + + entry1 += chainLength; + } + + // if found, use that + if (entry1 < vc.indexEntries + vc.numIndexEntries) + { + tess.firstIndex = entry1->bufferOffset / sizeof(glIndex_t); + //ri.Printf(PRINT_ALL, "firstIndex %d numIndexes %d\n", tess.firstIndex, tess.numIndexes); + //ri.Printf(PRINT_ALL, "vc.numIndexEntries %d\n", vc.numIndexEntries); + } + // if not, rebuffer all the indexes but reuse any existing vertexes + else + { + srfVert_t *dstVertex = vc.vertexes; + glIndex_t *dstIndex = vc.indexes; + int *indexChainLength = vc.indexChainLengths + vc.numIndexEntries; + + tess.firstIndex = vc.indexOffset / sizeof(glIndex_t); + vc.vertexCommitSize = 0; + vc.indexCommitSize = 0; + for (surf = vc.surfaceQueue; surf < end; surf++) + { + srfVert_t *srcVertex = surf->vertexes; + glIndex_t *srcIndex = surf->indexes; + int vertexesSize = surf->numVerts * sizeof(srfVert_t); + int indexesSize = surf->numIndexes * sizeof(glIndex_t); + int i, indexOffset = (vc.vertexOffset + vc.vertexCommitSize) / sizeof(srfVert_t); + + for (i = 0; i < surf->numVerts; i++) + *dstVertex++ = *srcVertex++; + + vc.vertexCommitSize += vertexesSize; + + entry1 = vc.indexEntries + vc.numIndexEntries; + entry1->data = surf->indexes; + entry1->size = indexesSize; + entry1->bufferOffset = vc.indexOffset + vc.indexCommitSize; + vc.numIndexEntries++; + + *indexChainLength++ = ((surf == vc.surfaceQueue) ? vc.numSurfacesQueued : 0); + + for (i = 0; i < surf->numIndexes; i++) + *dstIndex++ = *srcIndex++ + indexOffset; + + vc.indexCommitSize += indexesSize; + } + + //ri.Printf(PRINT_ALL, "committing %d to %d, %d to %d\n", vc.vertexCommitSize, vc.vertexOffset, vc.indexCommitSize, vc.indexOffset); + + if (vc.vertexCommitSize) + { + qglBindBuffer(GL_ARRAY_BUFFER, vc.vao->vertexesVBO); + qglBufferSubData(GL_ARRAY_BUFFER, vc.vertexOffset, vc.vertexCommitSize, vc.vertexes); + vc.vertexOffset += vc.vertexCommitSize; + } + + if (vc.indexCommitSize) + { + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesIBO); + qglBufferSubData(GL_ELEMENT_ARRAY_BUFFER, vc.indexOffset, vc.indexCommitSize, vc.indexes); + vc.indexOffset += vc.indexCommitSize; + } + } +} + +void VaoCache_Init(void) +{ + srfVert_t vert; + int dataSize; + + vc.vao = R_CreateVao("VaoCache", NULL, VAOCACHE_VERTEX_BUFFER_SIZE, NULL, VAOCACHE_INDEX_BUFFER_SIZE, VAO_USAGE_DYNAMIC); + + vc.vao->attribs[ATTR_INDEX_POSITION].enabled = 1; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].enabled = 1; + vc.vao->attribs[ATTR_INDEX_NORMAL].enabled = 1; + vc.vao->attribs[ATTR_INDEX_TANGENT].enabled = 1; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1; + vc.vao->attribs[ATTR_INDEX_COLOR].enabled = 1; + + vc.vao->attribs[ATTR_INDEX_POSITION].count = 3; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].count = 2; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].count = 2; + vc.vao->attribs[ATTR_INDEX_NORMAL].count = 4; + vc.vao->attribs[ATTR_INDEX_TANGENT].count = 4; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4; + vc.vao->attribs[ATTR_INDEX_COLOR].count = 4; + + vc.vao->attribs[ATTR_INDEX_POSITION].type = GL_FLOAT; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].type = GL_FLOAT; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].type = GL_FLOAT; + vc.vao->attribs[ATTR_INDEX_NORMAL].type = GL_SHORT; + vc.vao->attribs[ATTR_INDEX_TANGENT].type = GL_SHORT; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT; + vc.vao->attribs[ATTR_INDEX_COLOR].type = GL_UNSIGNED_SHORT; + + vc.vao->attribs[ATTR_INDEX_POSITION].normalized = GL_FALSE; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].normalized = GL_FALSE; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].normalized = GL_FALSE; + vc.vao->attribs[ATTR_INDEX_NORMAL].normalized = GL_TRUE; + vc.vao->attribs[ATTR_INDEX_TANGENT].normalized = GL_TRUE; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE; + vc.vao->attribs[ATTR_INDEX_COLOR].normalized = GL_TRUE; + + vc.vao->attribs[ATTR_INDEX_POSITION].offset = 0; dataSize = sizeof(vert.xyz); + vc.vao->attribs[ATTR_INDEX_TEXCOORD].offset = dataSize; dataSize += sizeof(vert.st); + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].offset = dataSize; dataSize += sizeof(vert.lightmap); + vc.vao->attribs[ATTR_INDEX_NORMAL].offset = dataSize; dataSize += sizeof(vert.normal); + vc.vao->attribs[ATTR_INDEX_TANGENT].offset = dataSize; dataSize += sizeof(vert.tangent); + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(vert.lightdir); + vc.vao->attribs[ATTR_INDEX_COLOR].offset = dataSize; dataSize += sizeof(vert.color); + + vc.vao->attribs[ATTR_INDEX_POSITION].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_NORMAL].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_TANGENT].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_COLOR].stride = dataSize; + + Vao_SetVertexPointers(vc.vao); + + vc.numIndexEntries = 0; + vc.vertexOffset = 0; + vc.indexOffset = 0; + vc.vertexCommitSize = 0; + vc.indexCommitSize = 0; + vc.numSurfacesQueued = 0; +} + +void VaoCache_BindVao(void) +{ + R_BindVao(vc.vao); +} + +void VaoCache_CheckAdd(bool *endSurface, bool *recycleVertexBuffer, bool *recycleIndexBuffer, int numVerts, int numIndexes) +{ + int vertexesSize = sizeof(srfVert_t) * numVerts; + int indexesSize = sizeof(glIndex_t) * numIndexes; + + if (vc.vao->vertexesSize < vc.vertexOffset + vc.vertexCommitSize + vertexesSize) + { + //ri.Printf(PRINT_ALL, "out of space in vertex cache: %d < %d + %d + %d\n", vc.vao->vertexesSize, vc.vertexOffset, vc.vertexCommitSize, vertexesSize); + *recycleVertexBuffer = true; + *recycleIndexBuffer = true; + *endSurface = true; + } + + if (vc.vao->indexesSize < vc.indexOffset + vc.indexCommitSize + indexesSize) + { + //ri.Printf(PRINT_ALL, "out of space in index cache\n"); + *recycleIndexBuffer = true; + *endSurface = true; + } + + if (vc.numIndexEntries + vc.numSurfacesQueued >= VAOCACHE_MAX_BUFFERED_SURFACES) + { + //ri.Printf(PRINT_ALL, "out of surfaces in index cache\n"); + *recycleIndexBuffer = true; + *endSurface = true; + } + + if (vc.numSurfacesQueued == VAOCACHE_MAX_QUEUED_SURFACES) + { + //ri.Printf(PRINT_ALL, "out of queued surfaces\n"); + *endSurface = true; + } + + if (VAOCACHE_MAX_QUEUED_VERTEXES * sizeof(srfVert_t) < vc.vertexCommitSize + vertexesSize) + { + //ri.Printf(PRINT_ALL, "out of queued vertexes\n"); + *endSurface = true; + } + + if (VAOCACHE_MAX_QUEUED_INDEXES * sizeof(glIndex_t) < vc.indexCommitSize + indexesSize) + { + //ri.Printf(PRINT_ALL, "out of queued indexes\n"); + *endSurface = true; + } +} + +void VaoCache_RecycleVertexBuffer(void) +{ + qglBindBuffer(GL_ARRAY_BUFFER, vc.vao->vertexesVBO); + qglBufferData(GL_ARRAY_BUFFER, vc.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW); + vc.vertexOffset = 0; +} + +void VaoCache_RecycleIndexBuffer(void) +{ + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesIBO); + qglBufferData(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesSize, NULL, GL_DYNAMIC_DRAW); + vc.indexOffset = 0; + vc.numIndexEntries = 0; +} + +void VaoCache_InitNewSurfaceSet(void) +{ + vc.vertexCommitSize = 0; + vc.indexCommitSize = 0; + vc.numSurfacesQueued = 0; +} + +void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int numIndexes) +{ + int vertexesSize = sizeof(srfVert_t) * numVerts; + int indexesSize = sizeof(glIndex_t) * numIndexes; + queuedSurface_t *queueEntry = vc.surfaceQueue + vc.numSurfacesQueued; + queueEntry->vertexes = verts; + queueEntry->numVerts = numVerts; + queueEntry->indexes = indexes; + queueEntry->numIndexes = numIndexes; + vc.numSurfacesQueued++; + + vc.vertexCommitSize += vertexesSize; + vc.indexCommitSize += indexesSize; +} diff --git a/src/renderergl2/tr_world.cpp b/src/renderergl2/tr_world.cpp new file mode 100644 index 0000000..0361a88 --- /dev/null +++ b/src/renderergl2/tr_world.cpp @@ -0,0 +1,811 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#include "tr_local.h" + + + +/* +================ +R_CullSurface + +Tries to cull surfaces before they are lighted or +added to the sorting list. +================ +*/ +static bool R_CullSurface( msurface_t *surf ) { + if ( r_nocull->integer || surf->cullinfo.type == CULLINFO_NONE) { + return false; + } + + if ( *surf->data == SF_GRID && r_nocurves->integer ) { + return true; + } + + if (surf->cullinfo.type & CULLINFO_PLANE) + { + // Only true for SF_FACE, so treat like its own function + float d; + cullType_t ct; + + if ( !r_facePlaneCull->integer ) { + return false; + } + + ct = surf->shader->cullType; + + if (ct == CT_TWO_SIDED) + { + return false; + } + + // don't cull for depth shadow + /* + if ( tr.viewParms.flags & VPF_DEPTHSHADOW ) + { + return false; + } + */ + + // shadowmaps draw back surfaces + if ( tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW) ) + { + if (ct == CT_FRONT_SIDED) + { + ct = CT_BACK_SIDED; + } + else + { + ct = CT_FRONT_SIDED; + } + } + + // do proper cull for orthographic projection + if (tr.viewParms.flags & VPF_ORTHOGRAPHIC) { + d = DotProduct(tr.viewParms.orientation.axis[0], surf->cullinfo.plane.normal); + if ( ct == CT_FRONT_SIDED ) { + if (d > 0) + return true; + } else { + if (d < 0) + return true; + } + return false; + } + + d = DotProduct (tr.orientation.viewOrigin, surf->cullinfo.plane.normal); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( ct == CT_FRONT_SIDED ) { + if ( d < surf->cullinfo.plane.dist - 8 ) { + return true; + } + } else { + if ( d > surf->cullinfo.plane.dist + 8 ) { + return true; + } + } + + return false; + } + + if (surf->cullinfo.type & CULLINFO_SPHERE) + { + int sphereCull; + + if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) { + sphereCull = R_CullLocalPointAndRadius( surf->cullinfo.localOrigin, surf->cullinfo.radius ); + } else { + sphereCull = R_CullPointAndRadius( surf->cullinfo.localOrigin, surf->cullinfo.radius ); + } + + if ( sphereCull == CULL_OUT ) + { + return true; + } + } + + if (surf->cullinfo.type & CULLINFO_BOX) + { + int boxCull; + + if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) { + boxCull = R_CullLocalBox( surf->cullinfo.bounds ); + } else { + boxCull = R_CullBox( surf->cullinfo.bounds ); + } + + if ( boxCull == CULL_OUT ) + { + return true; + } + } + + return false; +} + + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + if ( surf->cullinfo.type & CULLINFO_PLANE ) + { + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, surf->cullinfo.plane.normal ) - surf->cullinfo.plane.dist; + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_BOX ) + { + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > surf->cullinfo.bounds[1][0] + || dl->origin[0] + dl->radius < surf->cullinfo.bounds[0][0] + || dl->origin[1] - dl->radius > surf->cullinfo.bounds[1][1] + || dl->origin[1] + dl->radius < surf->cullinfo.bounds[0][1] + || dl->origin[2] - dl->radius > surf->cullinfo.bounds[1][2] + || dl->origin[2] + dl->radius < surf->cullinfo.bounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_SPHERE ) + { + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if (!SpheresIntersect(dl->origin, dl->radius, surf->cullinfo.localOrigin, surf->cullinfo.radius)) + { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + } + + switch(*surf->data) + { + case SF_FACE: + case SF_GRID: + case SF_TRIANGLES: + ((srfBspSurface_t *)surf->data)->dlightBits = dlightBits; + break; + + default: + dlightBits = 0; + break; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } else { + tr.pc.c_dlightSurfacesCulled++; + } + + return dlightBits; +} + +/* +==================== +R_PshadowSurface + +Just like R_DlightSurface, cull any we can +==================== +*/ +static int R_PshadowSurface( msurface_t *surf, int pshadowBits ) { + float d; + int i; + pshadow_t *ps; + + if ( surf->cullinfo.type & CULLINFO_PLANE ) + { + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + if ( ! ( pshadowBits & ( 1 << i ) ) ) { + continue; + } + ps = &tr.refdef.pshadows[i]; + d = DotProduct( ps->lightOrigin, surf->cullinfo.plane.normal ) - surf->cullinfo.plane.dist; + if ( d < -ps->lightRadius || d > ps->lightRadius ) { + // pshadow doesn't reach the plane + pshadowBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_BOX ) + { + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + if ( ! ( pshadowBits & ( 1 << i ) ) ) { + continue; + } + ps = &tr.refdef.pshadows[i]; + if ( ps->lightOrigin[0] - ps->lightRadius > surf->cullinfo.bounds[1][0] + || ps->lightOrigin[0] + ps->lightRadius < surf->cullinfo.bounds[0][0] + || ps->lightOrigin[1] - ps->lightRadius > surf->cullinfo.bounds[1][1] + || ps->lightOrigin[1] + ps->lightRadius < surf->cullinfo.bounds[0][1] + || ps->lightOrigin[2] - ps->lightRadius > surf->cullinfo.bounds[1][2] + || ps->lightOrigin[2] + ps->lightRadius < surf->cullinfo.bounds[0][2] + || BoxOnPlaneSide(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1], &ps->cullPlane) == 2 ) { + // pshadow doesn't reach the bounds + pshadowBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_SPHERE ) + { + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + if ( ! ( pshadowBits & ( 1 << i ) ) ) { + continue; + } + ps = &tr.refdef.pshadows[i]; + if (!SpheresIntersect(ps->viewOrigin, ps->viewRadius, surf->cullinfo.localOrigin, surf->cullinfo.radius) + || DotProduct( surf->cullinfo.localOrigin, ps->cullPlane.normal ) - ps->cullPlane.dist < -surf->cullinfo.radius) + { + // pshadow doesn't reach the bounds + pshadowBits &= ~( 1 << i ); + } + } + } + + switch(*surf->data) + { + case SF_FACE: + case SF_GRID: + case SF_TRIANGLES: + ((srfBspSurface_t *)surf->data)->pshadowBits = pshadowBits; + break; + + default: + pshadowBits = 0; + break; + } + + if ( pshadowBits ) { + //tr.pc.c_dlightSurfaces++; + } + + return pshadowBits; +} + + +/* +====================== +R_AddWorldSurface +====================== +*/ +static void R_AddWorldSurface( msurface_t *surf, int dlightBits, int pshadowBits ) { + // FIXME: bmodel fog? + + // try to cull before dlighting or adding + if ( R_CullSurface( surf ) ) { + return; + } + + // check for dlighting + /*if ( dlightBits ) */{ + dlightBits = R_DlightSurface( surf, dlightBits ); + dlightBits = ( dlightBits != 0 ); + } + + // check for pshadows + /*if ( pshadowBits ) */{ + pshadowBits = R_PshadowSurface( surf, pshadowBits); + pshadowBits = ( pshadowBits != 0 ); + } + + R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits, pshadowBits, surf->cubemapIndex ); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + R_SetupEntityLighting( &tr.refdef, ent ); + R_DlightBmodel( bmodel ); + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + int surf = bmodel->firstSurface + i; + + if (tr.world->surfacesViewCount[surf] != tr.viewCount) + { + tr.world->surfacesViewCount[surf] = tr.viewCount; + R_AddWorldSurface( tr.world->surfaces + surf, tr.currentEntity->needDlights, 0 ); + } + } +} + + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + + +/* +================ +R_RecursiveWorldNode +================ +*/ +static void R_RecursiveWorldNode( mnode_t *node, uint32_t planeBits, uint32_t dlightBits, uint32_t pshadowBits ) { + + do { + uint32_t newDlights[2]; + uint32_t newPShadows[2]; + + // if the node wasn't marked as potentially visible, exit + // pvs is skipped for depth shadows + if (!(tr.viewParms.flags & VPF_DEPTHSHADOW) && node->visCounts[tr.visIndex] != tr.visCounts[tr.visIndex]) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + if ( planeBits & 16 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[4]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~16; // all descendants will also be in front + } + } + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) { + int i; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + dlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + + newPShadows[0] = 0; + newPShadows[1] = 0; + if ( pshadowBits ) { + int i; + + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + pshadow_t *shadow; + float dist; + + if ( pshadowBits & ( 1 << i ) ) { + shadow = &tr.refdef.pshadows[i]; + dist = DotProduct( shadow->lightOrigin, node->plane->normal ) - node->plane->dist; + + if ( dist > -shadow->lightRadius ) { + newPShadows[0] |= ( 1 << i ); + } + if ( dist < shadow->lightRadius ) { + newPShadows[1] |= ( 1 << i ); + } + } + } + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0], newPShadows[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + pshadowBits = newPShadows[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + int surf, *view; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add surfaces + view = tr.world->marksurfaces + node->firstmarksurface; + + c = node->nummarksurfaces; + while (c--) { + // just mark it as visible, so we don't jump out of the cache derefencing the surface + surf = *view; + if (tr.world->surfacesViewCount[surf] != tr.viewCount) + { + tr.world->surfacesViewCount[surf] = tr.viewCount; + tr.world->surfacesDlightBits[surf] = dlightBits; + tr.world->surfacesPshadowBits[surf] = pshadowBits; + } + else + { + tr.world->surfacesDlightBits[surf] |= dlightBits; + tr.world->surfacesPshadowBits[surf] |= pshadowBits; + } + view++; + } + } + +} + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( const vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + ri.Error (ERR_DROP, "R_PointInLeaf: bad model"); + } + + node = tr.world->nodes; + while( 1 ) { + if (node->contents != -1) { + break; + } + plane = node->plane; + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS (int cluster) { + if (!tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return NULL; + } + + return tr.world->vis + cluster * tr.world->clusterBytes; +} + +/* +================= +R_inPVS +================= +*/ +bool R_inPVS( const vec3_t p1, const vec3_t p2 ) { + mnode_t *leaf; + byte *vis; + + leaf = R_PointInLeaf( p1 ); + vis = ri.CM_ClusterPVS( leaf->cluster ); // why not R_ClusterPVS ?? + leaf = R_PointInLeaf( p2 ); + + if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) { + return false; + } + return true; +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +static void R_MarkLeaves (void) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + for(i = 0; i < MAX_VISCOUNTS; i++) + { + // if the areamask or r_showcluster was modified, invalidate all visclusters + // this caused doors to open into undrawn areas + if (tr.refdef.areamaskModified || r_showcluster->modified) + { + tr.visClusters[i] = -2; + } + else if(tr.visClusters[i] == cluster) + { + if(tr.visClusters[i] != tr.visClusters[tr.visIndex] && r_showcluster->integer) + { + ri.Printf(PRINT_ALL, "found cluster:%i area:%i index:%i\n", cluster, leaf->area, i); + } + tr.visIndex = i; + return; + } + } + + tr.visIndex = (tr.visIndex + 1) % MAX_VISCOUNTS; + tr.visCounts[tr.visIndex]++; + tr.visClusters[tr.visIndex] = cluster; + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = false; + if ( r_showcluster->integer ) { + ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + vis = R_ClusterPVS(tr.visClusters[tr.visIndex]); + + for (i=0,leaf=tr.world->nodes ; inumnodes ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( vis && !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if(parent->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex]) + break; + parent->visCounts[tr.visIndex] = tr.visCounts[tr.visIndex]; + parent = parent->parent; + } while (parent); + } +} + + +/* +============= +R_AddWorldSurfaces +============= +*/ +void R_AddWorldSurfaces (void) { + uint32_t planeBits, dlightBits, pshadowBits; + + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = REFENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + if (!(tr.viewParms.flags & VPF_DEPTHSHADOW)) + R_MarkLeaves (); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and flag all the potentially visible surfaces + if ( tr.refdef.num_dlights > MAX_DLIGHTS ) { + tr.refdef.num_dlights = MAX_DLIGHTS ; + } + + if ( tr.refdef.num_pshadows > MAX_DRAWN_PSHADOWS ) { + tr.refdef.num_pshadows = MAX_DRAWN_PSHADOWS; + } + + planeBits = (tr.viewParms.flags & VPF_FARPLANEFRUSTUM) ? 31 : 15; + + if ( tr.viewParms.flags & VPF_DEPTHSHADOW ) + { + dlightBits = 0; + pshadowBits = 0; + } + else if ( !(tr.viewParms.flags & VPF_SHADOWMAP) ) + { + dlightBits = ( 1ULL << tr.refdef.num_dlights ) - 1; + pshadowBits = ( 1ULL << tr.refdef.num_pshadows ) - 1; + } + else + { + dlightBits = ( 1ULL << tr.refdef.num_dlights ) - 1; + pshadowBits = 0; + } + + R_RecursiveWorldNode( tr.world->nodes, planeBits, dlightBits, pshadowBits); + + // now add all the potentially visible surfaces + // also mask invisible dlights for next frame + { + int i; + + tr.refdef.dlightMask = 0; + + for (i = 0; i < tr.world->numWorldSurfaces; i++) + { + if (tr.world->surfacesViewCount[i] != tr.viewCount) + continue; + + R_AddWorldSurface( tr.world->surfaces + i, tr.world->surfacesDlightBits[i], tr.world->surfacesPshadowBits[i] ); + tr.refdef.dlightMask |= tr.world->surfacesDlightBits[i]; + } + + tr.refdef.dlightMask = ~tr.refdef.dlightMask; + } +} diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt new file mode 100644 index 0000000..d7741f7 --- /dev/null +++ b/src/script/CMakeLists.txt @@ -0,0 +1,35 @@ +add_library( + script_api STATIC + client.h + cvar.h + http_client.h + lnettlelib.c + lnettlelib.h + lnettlelib.h + nettle.h + rapidjson.h + rapidjson/document.cpp + rapidjson/file.hpp + rapidjson/luax.hpp + rapidjson/rapidjson.cpp + rapidjson/schema.cpp + rapidjson/userdata.hpp + rapidjson/values.cpp + rapidjson/values.hpp + ) + + +include_directories( + # these are in global namespace + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/lua-5.3.3/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/sol + ${CMAKE_CURRENT_SOURCE_DIR}/../common + # these have they're own namespace + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/nettle-3.3 + ${CMAKE_CURRENT_SOURCE_DIR}/../../external/rapidjson + ) + +target_link_libraries( + script_api + lua + ) diff --git a/src/script/bind.h b/src/script/bind.h new file mode 100644 index 0000000..a1e93e9 --- /dev/null +++ b/src/script/bind.h @@ -0,0 +1,107 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_KEY_H +#define SCRIPT_KEY_H + +#include "client/client.h" +#include "client/keys.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + class InvalidKey : public std::exception { + public: + virtual const char* what() const throw() + { return "Invalid Key"; } + }; + + struct Bind { + private: + qkey_t *my = nullptr; + int keynum = 0; + + Bind() = delete; + + public: + Bind(qkey_t* key) + : my(key) + { } + + Bind(std::string key) + { + keynum = Key_StringToKeynum(key.c_str()); + if ( keynum < 0 ) throw InvalidKey(); + my = &keys[ keynum ]; + } + + Bind(std::string key, std::string cmd) + { + keynum = Key_StringToKeynum(key.c_str()); + if ( keynum < 0 ) throw InvalidKey(); + my = &keys[ keynum ]; + Key_SetBinding(keynum, cmd.c_str()); + } + + Bind(std::string key, std::string cmd, bool overwrite) + { + keynum = Key_StringToKeynum(key.c_str()); + if ( keynum < 0 ) throw InvalidKey(); + my = &keys[ keynum ]; + if ( !my->binding || overwrite ) + Key_SetBinding(keynum, cmd.c_str()); + } + + std::string get_value() + { return my->binding; } + + void set_value(std::string cmd) { + if ( my->binding ) Z_Free( my->binding ); + my->binding = CopyString(cmd.c_str()); + // consider this like modifying an archived cvar, so the file + // write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } + + }; + + namespace keybind + { + static inline void init(sol::state&& lua) + { + lua.new_usertype( + "bind", sol::constructors, + sol::types, + sol::types>(), + "value", sol::property(&Bind::get_value, &Bind::set_value) + //"key", sol::property(&Bind::get_key) + ); + } + }; +}; + +#endif diff --git a/src/script/client.h b/src/script/client.h new file mode 100644 index 0000000..ee90771 --- /dev/null +++ b/src/script/client.h @@ -0,0 +1,54 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_CLIENT_H +#define SCRIPT_CLIENT_H + +#include + +#include "client/client.h" +#include "client/keys.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + struct Client {}; + + namespace client + { + static inline void init(sol::state&& lua) + { + lua.new_usertype( "client", + "addReliableCommand", &CL_AddReliableCommand + //"disconnect", &CL_Disconnect_f, + //"reconnect", &CL_Reconnect_f, + ); + } + }; +}; + +#endif diff --git a/src/script/cmd.h b/src/script/cmd.h new file mode 100644 index 0000000..718658a --- /dev/null +++ b/src/script/cmd.h @@ -0,0 +1,50 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_CMD_H +#define SCRIPT_CMD_H + +#include "qcommon/cmd.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + struct Cmd { }; + + namespace cmd + { + static inline void init(sol::state&& lua) + { + lua.new_usertype( + "cmd", + "execute", &Cmd_ExecuteString + ); + } + }; +}; + +#endif diff --git a/src/script/cvar.h b/src/script/cvar.h new file mode 100644 index 0000000..a5eb90c --- /dev/null +++ b/src/script/cvar.h @@ -0,0 +1,146 @@ +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . + +// It's quite possible this is _way over the top_ but I've been toying +// with several ideas. -Victor + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_CVAR_H +#define SCRIPT_CVAR_H + +#include +#include + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + ////////////////////////////////////////////// Exceptions //////////// + class CvarInvalidName : public std::exception { + public: + virtual const char* what() const throw() + { return "cvar name is invalid"; } + }; + + class CvarNotFound : public std::exception { + public: + virtual const char* what() const throw() + { return "cvar does not exist"; } + }; + + class CvarReadOnly : public std::exception { + public: + virtual const char *what() const throw() + { return "cvar is readonly"; } + }; + + class CvarWriteProtected : public std::exception { + public: + virtual const char *what() const throw() + { return "cvar is write protected"; } + }; + + class CvarCheatProtected : public std::exception { + public: + virtual const char *what() const throw() + { return "cvar is cheat protected"; } + }; + + class CvarLatchedUnsupported : public std::exception { + public: + virtual const char *what() const throw() + { return "modifying latched cvars is unsupported"; } + }; + + extern "C" cvar_t *cvar_cheats; + ////////////////////////////////////////////// class Cvar //////////// + struct Cvar { + private: + cvar_s *my = nullptr; + + Cvar() = delete; + + public: + Cvar(cvar_s* var) + : my(var) + { } + + Cvar(std::string name) + { my = Cvar_Get(name.c_str(), "", 0); } + + Cvar(std::string name, std::string value) + { my = Cvar_Get(name.c_str(), value.c_str(), CVAR_ARCHIVE); } + + Cvar(std::string name, std::string value, int flags) + { my = Cvar_Get(name.c_str(), value.c_str(), flags); } + + void set_value(const char*value) + { + if ( !my ) + throw CvarNotFound(); + else if ( my->flags & CVAR_ROM ) + throw CvarReadOnly(); + else if ( my->flags & CVAR_INIT ) + throw CvarWriteProtected(); + else if ( my->flags & CVAR_CHEAT && !cvar_cheats->integer ) + throw CvarCheatProtected(); + else if ( (my->flags & CVAR_LATCH) ) + throw CvarLatchedUnsupported(); + else if ( !value ) + value = my->resetString; + value = Cvar_Validate(my, value, false); + + cvar_modifiedFlags |= my->flags; + + my->modified = true; + my->modificationCount++; + my->string = CopyString(value); + my->value = atof(my->string); + my->integer = atoi(my->string); + } + + std::string get_value() + { return my->string; } + + std::string get_key() + { return my->name; } + }; + + namespace cvar { + static inline void init(sol::state&& lua) + { + lua.new_usertype( + "cvar", sol::constructors, + sol::types, + sol::types>(), + "value", sol::property(&Cvar::get_value, &Cvar::set_value), + "key", sol::property(&Cvar::get_key) + ); + } + }; +}; +#endif diff --git a/src/script/http_client.h b/src/script/http_client.h new file mode 100644 index 0000000..2556090 --- /dev/null +++ b/src/script/http_client.h @@ -0,0 +1,65 @@ +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . + +// It's quite possible this is _way over the top_ but I've been toying +// with several ideas. -Victor + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_RESTCLIENT_H +#define SCRIPT_RESTCLIENT_H + +#include +#include "restclient/restclient.h" + +namespace sol +{ + class state; +}; + +namespace script +{ + // No Exceptions at this time + + struct HttpClient + { }; + + namespace http_client + { + //using namespace RestClient; + static inline void init(sol::state&& lua) + { + + lua.new_usertype( "HttpResponse", + "code", &RestClient::Response::code, + "body", &RestClient::Response::body + //"headers", &RestClient::Response::headers + ); + + lua.new_usertype( "http", + "get", &RestClient::get, + "post", &RestClient::post, + "put", &RestClient::put, + "delete", &RestClient::del + ); + } + }; +}; + +#endif diff --git a/src/script/lnettlelib.c b/src/script/lnettlelib.c new file mode 100644 index 0000000..f1aedfa --- /dev/null +++ b/src/script/lnettlelib.c @@ -0,0 +1,94 @@ +#include + +#include "lauxlib.h" +#include "lua.h" +#include "nettle/sha2.h" + +//#include "../qcommon/q3_lauxlib.h" FIXME? This doesn't seem to be hooked correctly into tremded.exe + +#define SHA256_CTX "sha256_ctx*" + +static int lsha256(lua_State *L) +{ + struct sha256_ctx *ctx; + + ctx = (struct sha256_ctx *)lua_newuserdata(L, sizeof(struct sha256_ctx)); + luaL_setmetatable(L, SHA256_CTX); + sha256_init(ctx); + return 1; +} + +static int lsha256_digest(lua_State *L) +{ + struct sha256_ctx *ctx; + char digest[SHA256_DIGEST_SIZE]; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + sha256_digest(ctx, sizeof(digest), digest); + lua_pushlstring(L, digest, sizeof(digest)); + return 1; +} + +static int lsha256_update(lua_State *L) +{ + struct sha256_ctx *ctx; + const char *data; + size_t len; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + if (lua_isnil(L, 2)) { + return 0; + } + data = luaL_tolstring(L, 2, &len); + nettle_sha256_update(ctx, len, data); + return 0; +} + +static int lsha256_tostring(lua_State *L) +{ + struct sha256_ctx *ctx, ctx2; + char digest[SHA256_DIGEST_SIZE]; + char hex[SHA256_DIGEST_SIZE*2+1]; + int i; + + ctx = luaL_checkudata(L, 1, SHA256_CTX); + memcpy(&ctx2, ctx, sizeof(struct sha256_ctx)); + + sha256_digest(&ctx2, sizeof(digest), digest); + for (i = 0; i < sizeof(digest); i++) { + sprintf(hex + 2 * i, "%02hhx", digest[i]); + } + + lua_pushstring(L, hex); + return 1; +} + +/* functions for 'nettle' library */ +static const luaL_Reg nettlelib[] = { + {"sha256", lsha256}, + {NULL, NULL} +}; + +/* functions for sha256 objects */ +static const luaL_Reg lsha256_methods[] = { + {"digest", lsha256_digest}, + {"update", lsha256_update}, + {"__tostring", lsha256_tostring}, + {NULL, NULL} +}; + +static void createmeta (lua_State *L) +{ + luaL_newmetatable(L, SHA256_CTX); /* create metatable for file handles */ + lua_pushvalue(L, -1); /* push metatable */ + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ + luaL_setfuncs(L, lsha256_methods, 0); /* add file methods to new metatable */ + lua_pop(L, 1); /* pop new metatable */ +} + +LUAMOD_API int luaopen_nettle (lua_State *L) +{ + luaL_newlib(L, nettlelib); + createmeta(L); + return 1; +} diff --git a/src/script/lnettlelib.h b/src/script/lnettlelib.h new file mode 100644 index 0000000..c9609f9 --- /dev/null +++ b/src/script/lnettlelib.h @@ -0,0 +1,15 @@ +#ifndef lnettlelib_h +#define lnettlelib_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define LUA_NETTLELIBNAME "nettle" +LUAMOD_API int (luaopen_nettle) (lua_State *L); + +#ifdef __cplusplus +} +#endif + +#endif /* lnettlelib_h */ diff --git a/src/script/nettle.h b/src/script/nettle.h new file mode 100644 index 0000000..87e8c26 --- /dev/null +++ b/src/script/nettle.h @@ -0,0 +1,41 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_NETTLE_H +#define SCRIPT_NETTLE_H + +#include + +#include "lua.hpp" +#include "sol.hpp" + +#include "lnettlelib.h" + +namespace script { + namespace nettle { + static inline void init(sol::state&& lua) + { lua.require("nettle", luaopen_nettle, 1); } + }; +}; + +#endif diff --git a/src/script/rapidjson.h b/src/script/rapidjson.h new file mode 100644 index 0000000..0603bb7 --- /dev/null +++ b/src/script/rapidjson.h @@ -0,0 +1,52 @@ +// +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . +// + +#ifndef __cplusplus +#error __file__ " is only available to C++" +#endif + +#ifndef SCRIPT_RAPIDJSON_H +#define SCRIPT_RAPIDJSON_H + +#include + +#include "lua.hpp" +#include "sol.hpp" + +namespace sol +{ + class state; +}; + +extern "C" int luaopen_rapidjson(lua_State* L); + +namespace script +{ + // No Exceptions at this time + + namespace rapidjson + { + static inline void init(sol::state&& lua) + { + lua.require("rapidjson", luaopen_rapidjson, 1); + } + }; +}; + +#endif diff --git a/src/script/rapidjson/LICENSE b/src/script/rapidjson/LICENSE new file mode 100644 index 0000000..5a331f8 --- /dev/null +++ b/src/script/rapidjson/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Xpol Wan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/script/rapidjson/document.cpp b/src/script/rapidjson/document.cpp new file mode 100644 index 0000000..2487eb6 --- /dev/null +++ b/src/script/rapidjson/document.cpp @@ -0,0 +1,194 @@ + +#include +#include +#include + +#include "userdata.hpp" +#include "values.hpp" +#include "file.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +using namespace rapidjson; + +template<> +const char* const Userdata::metatable = "rapidjson.Document"; + +template<> +Document* Userdata::construct(lua_State * L) +{ + auto t = lua_type(L, 1); + if (t != LUA_TNONE && t != LUA_TSTRING && t != LUA_TTABLE) + { + luax::typerror(L, 1, "none, string or table"); + return nullptr; + } + + auto doc = new Document(); + if (t == LUA_TSTRING) + { + size_t len; + const char* s = luaL_checklstring(L, 1, &len); + doc->Parse(s, len); + } + else if (t == LUA_TTABLE) + { + values::toDocument(L, 1, doc); + } + return doc; +} + +static int pushParseResult(lua_State* L, Document* doc) +{ + auto err = doc->GetParseError(); + if (err != kParseErrorNone) + { + lua_pushnil(L); + lua_pushfstring(L, "%s (at Offset %d)", GetParseError_En(err), doc->GetErrorOffset()); + return 2; + } + + lua_pushboolean(L, 1); + return 1; +} + +static int Document_parse(lua_State* L) +{ + Document* doc = Userdata::get(L, 1); + + size_t l = 0; + auto s = luaL_checklstring(L, 2, &l); + doc->Parse(s, l); + + return pushParseResult(L, doc); +} + +static int Document_parseFile(lua_State* L) +{ + auto doc = Userdata::get(L, 1); + + auto s = luaL_checkstring(L, 2); + std::ifstream ifs(s); + IStreamWrapper isw(ifs); + + doc->ParseStream(isw); + + return pushParseResult(L, doc);; +} + +/** + * doc:get('path'[, default]) + */ +static int Document_get(lua_State* L) +{ + auto doc = Userdata::check(L, 1); + auto s = luaL_checkstring(L, 2); + Pointer ptr(s); + auto v = ptr.Get(*doc); + + if (!v) + { + if (lua_gettop(L) >= 3) + { + lua_pushvalue(L, 3); + } + else + { + lua_pushnil(L); + } + } + else + { + values::push(L, *v); + } + return 1; +} + +static int Document_set(lua_State* L) +{ + auto doc = Userdata::check(L, 1); + Pointer ptr(luaL_checkstring(L, 2)); + auto v = values::toValue(L, 3, doc->GetAllocator()); + + ptr.Set(*doc, v, doc->GetAllocator()); + + return 0; +} + +/** + * local jsonstr = doc:stringify( + {pretty=false}) + */ +static int Document_stringify(lua_State* L) +{ + auto doc = Userdata::check(L, 1); + + auto pretty = luax::optboolfield(L, 2, "pretty", false); + + StringBuffer sb; + if (pretty) + { + PrettyWriter writer(sb); + doc->Accept(writer); + } + else + { + Writer writer(sb); + doc->Accept(writer); + } + lua_pushlstring(L, sb.GetString(), sb.GetSize()); + + return 1; +} + +/** + * doc:save(filename, + {pretty=false, sort_keys=false}) + */ +static int Document_save(lua_State* L) +{ + auto doc = Userdata::check(L, 1); + auto filename = luaL_checkstring(L, 2); + auto pretty = luax::optboolfield(L, 3, "pretty", false); + + auto fp = file::open(filename, "wb"); + char buffer[512]; + FileWriteStream fs(fp, buffer, sizeof(buffer)); + + if (pretty) + { + PrettyWriter writer(fs); + doc->Accept(writer); + } + else + { + Writer writer(fs); + doc->Accept(writer); + } + fclose(fp); + + return 0; +} + +template <> +const luaL_Reg* Userdata::methods() +{ + static const luaL_Reg reg[] = + { + { "parse", Document_parse }, + { "parseFile", Document_parseFile }, + + { "__gc", metamethod_gc }, + { "__tostring", metamethod_tostring }, + + { "get", Document_get }, + { "set", Document_set }, + + { "stringify", Document_stringify }, + { "save", Document_save }, + + { nullptr, nullptr } + }; + return reg; +} diff --git a/src/script/rapidjson/file.hpp b/src/script/rapidjson/file.hpp new file mode 100644 index 0000000..5e098d4 --- /dev/null +++ b/src/script/rapidjson/file.hpp @@ -0,0 +1,21 @@ +#ifndef __LUA_RAPIDJSION_FILE_HPP__ +#define __LUA_RAPIDJSION_FILE_HPP__ + +#include + +namespace file +{ + inline FILE* open(const char* filename, const char* mode) + { +#if WIN32 + FILE* fp = nullptr; + fopen_s(&fp, filename, mode); + return fp; +#else + return fopen(filename, mode); +#endif + } +} + +#endif + diff --git a/src/script/rapidjson/luax.hpp b/src/script/rapidjson/luax.hpp new file mode 100644 index 0000000..e8ea7c6 --- /dev/null +++ b/src/script/rapidjson/luax.hpp @@ -0,0 +1,82 @@ +#ifndef __LUA_RAPIDJSION_LUACOMPAT_H__ +#define __LUA_RAPIDJSION_LUACOMPAT_H__ + +#include +#include +#include + +#include "qcommon/q3_lauxlib.h" + +#include "lua.hpp" + +namespace luax +{ + inline void setfuncs(lua_State* L, const luaL_Reg* funcs) + { luaL_setfuncs(L, funcs, 0); } + + inline size_t rawlen(lua_State* L, int idx) + { return lua_rawlen(L, idx); } + + inline bool isinteger(lua_State* L, int idx, int64_t* out = nullptr) + { + if (lua_isinteger(L, idx)) // but it maybe not detect all integers. + { + if (out) + *out = lua_tointeger(L, idx); + return true; + } + + double intpart; + if (std::modf(lua_tonumber(L, idx), &intpart) == 0.0) + { + if (std::numeric_limits::min() <= intpart + && intpart <= std::numeric_limits::max()) + { + if (out) + *out = static_cast(intpart); + return true; + } + } + return false; + } + + inline int typerror(lua_State* L, int narg, const char* tname) + { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); + } + + inline bool optboolfield(lua_State* L, int idx, const char* name, bool def) + { + auto v = def; + auto t = lua_type(L, idx); + + if (t != LUA_TTABLE && t != LUA_TNONE) + luax::typerror(L, idx, "table"); + + if (t != LUA_TNONE) + { + lua_getfield(L, idx, name); + if (!lua_isnoneornil(L, -1)) + v = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + } + + return v; + } + + inline int optintfield(lua_State* L, int idx, const char* name, int def) + { + auto v = def; + lua_getfield(L, idx, name); + + if (lua_isnumber(L, -1)) + v = static_cast(lua_tointeger(L, -1)); + + lua_pop(L, 1); + return v; + } + +} + +#endif diff --git a/src/script/rapidjson/rapidjson.cpp b/src/script/rapidjson/rapidjson.cpp new file mode 100644 index 0000000..9e87812 --- /dev/null +++ b/src/script/rapidjson/rapidjson.cpp @@ -0,0 +1,414 @@ + +#include +#include +#include +#include + +#include "userdata.hpp" +#include "values.hpp" +#include "luax.hpp" +#include "file.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +using namespace rapidjson; + +#ifndef LUA_RAPIDJSON_VERSION +#define LUA_RAPIDJSON_VERSION "1.0.0" +#endif + +static void createSharedMeta(lua_State* L, const char* meta, const char* type) +{ + luaL_newmetatable(L, meta); + lua_pushstring(L, type); + lua_setfield(L, -2, "__jsontype"); + lua_pop(L, 1); +} + +static int makeTableType(lua_State* L, int idx, const char* meta, const char* type) +{ + auto isnoarg = lua_isnoneornil(L, idx); + auto istable = lua_istable(L, idx); + if (!isnoarg && !istable) + return luaL_argerror(L, idx, "optional table excepted"); + + if (isnoarg) + lua_createtable(L, 0, 0); + else // is table. + { + lua_pushvalue(L, idx); + if (lua_getmetatable(L, -1)) + { + // already have a metatable, just set the __jsontype field. + + lua_pushstring(L, type); + lua_setfield(L, -2, "__jsontype"); + lua_pop(L, 1); + return 1; + } + // else fall through + } + + // Now we have a table without meta table + luaL_getmetatable(L, meta); + lua_setmetatable(L, -2); + return 1; +} + +static int json_object(lua_State* L) +{ + return makeTableType(L, 1, "json.object", "object"); +} + +static int json_array(lua_State* L) +{ + return makeTableType(L, 1, "json.array", "array"); +} + +template +int decode(lua_State* L, Stream* s) +{ + auto top = lua_gettop(L); + values::ToLuaHandler handler(L); + Reader reader; + + ParseResult r = reader.Parse(*s, handler); + if (!r) + { + lua_settop(L, top); + lua_pushnil(L); + lua_pushfstring(L, "%s (%d)", GetParseError_En(r.Code()), r.Offset()); + return 2; + } + + return 1; +} + +static int json_decode(lua_State* L) +{ + size_t len = 0; + auto contents = luaL_checklstring(L, 1, &len); + StringStream s(contents); + return decode(L, &s); +} + +static int json_load(lua_State* L) +{ + auto filename = luaL_checklstring(L, 1, nullptr); + auto fp = file::open(filename, "rb"); + if (!fp) + luaL_error(L, "error while open file: %s", filename); + + char buffer[512]; + FileReadStream fs(fp, buffer, sizeof(buffer)); + AutoUTFInputStream eis(fs); + + auto n = decode(L, &eis); + + fclose(fp); + return n; +} + +struct Key +{ + Key(const char* k, SizeType l) : key(k), size(l) + {} + bool operator<(const Key& rhs) const + { + return strcmp(key, rhs.key) < 0; + } + const char* key; + SizeType size; +}; + +class Encoder +{ + bool pretty; + bool sort_keys; + int max_depth; + static const int MAX_DEPTH_DEFAULT = 128; + public: + Encoder(lua_State*L, int opt) : pretty(false), sort_keys(false), max_depth(MAX_DEPTH_DEFAULT) + { + if (lua_isnoneornil(L, opt)) + return; + luaL_checktype(L, opt, LUA_TTABLE); + + pretty = luax::optboolfield(L, opt, "pretty", false); + sort_keys = luax::optboolfield(L, opt, "sort_keys", false); + max_depth = luax::optintfield(L, opt, "max_depth", MAX_DEPTH_DEFAULT); + } + + private: + template + void encodeValue(lua_State* L, Writer* writer, int idx, int depth = 0) + { + size_t len; + const char* s; + int64_t integer; + auto t = lua_type(L, idx); + switch (t) + { + case LUA_TBOOLEAN: + writer->Bool(lua_toboolean(L, idx) != 0); + return; + case LUA_TNUMBER: + if (luax::isinteger(L, idx, &integer)) + writer->Int64(integer); + else + { + if (!writer->Double(lua_tonumber(L, idx))) + luaL_error(L, "error while encode double value."); + } + return; + case LUA_TSTRING: + s = lua_tolstring(L, idx, &len); + writer->String(s, static_cast(len)); + return; + case LUA_TTABLE: + return encodeTable(L, writer, idx, depth + 1); + case LUA_TNIL: + writer->Null(); + return; + case LUA_TFUNCTION: + if (values::isnull(L, idx)) + { + writer->Null(); + return; + } + // otherwise fall thought + case LUA_TLIGHTUSERDATA: // fall thought + case LUA_TUSERDATA: // fall thought + case LUA_TTHREAD: // fall thought + case LUA_TNONE: // fall thought + default: + luaL_error(L, "value type : %s", lua_typename(L, t)); + } + } + + template + void encodeTable(lua_State* L, Writer* writer, int idx, int depth) + { + if (depth > max_depth) + luaL_error(L, "nested too depth"); + + if (!lua_checkstack(L, 4)) // requires at least 4 slots in stack: table, key, value, key + luaL_error(L, "stack overflow"); + + lua_pushvalue(L, idx); + if (values::isarray(L, -1)) + { + encodeArray(L, writer, depth); + lua_pop(L, 1); + return; + } + + // is object. + if (!sort_keys) + { + encodeObject(L, writer, depth); + lua_pop(L, 1); + return; + } + + lua_pushnil(L); + std::vector keys; + + while (lua_next(L, -2)) + { + + + if (lua_type(L, -2) == LUA_TSTRING) + { + size_t len = 0; + auto key = lua_tolstring(L, -2, &len); + keys.push_back(Key(key, static_cast(len))); + } + + // pop value, leaving original key + lua_pop(L, 1); + + } + + encodeObject(L, writer, depth, keys); + lua_pop(L, 1); + } + + template + void encodeObject(lua_State* L, Writer* writer, int depth) + { + writer->StartObject(); + + + lua_pushnil(L); + while (lua_next(L, -2)) + { + + if (lua_type(L, -2) == LUA_TSTRING) + { + size_t len = 0; + auto key = lua_tolstring(L, -2, &len); + writer->Key(key, static_cast(len)); + encodeValue(L, writer, -1, depth); + } + + // pop value, leaving original key + lua_pop(L, 1); + + } + + writer->EndObject(); + } + + template + void encodeObject(lua_State* L, Writer* writer, int depth, std::vector &keys) + { + + writer->StartObject(); + std::sort(keys.begin(), keys.end()); + + std::vector::const_iterator i = keys.begin(); + std::vector::const_iterator e = keys.end(); + for ( auto const& i : keys ) + { + writer->Key(i.key, static_cast(i.size)); + lua_pushlstring(L, i.key, i.size); + lua_gettable(L, -2); + encodeValue(L, writer, -1, depth); + lua_pop(L, 1); + } + + writer->EndObject(); + } + + template + void encodeArray(lua_State* L, Writer* writer, int depth) + { + + writer->StartArray(); + auto MAX = static_cast(luax::rawlen(L, -1)); // lua_rawlen always returns value >= 0 + for (auto n = 1; n <= MAX; ++n) + { + lua_rawgeti(L, -1, n); + encodeValue(L, writer, -1, depth); + lua_pop(L, 1); + } + writer->EndArray(); + + } + + public: + template + void encode(lua_State* L, Stream* s, int idx) + { + if (pretty) + { + PrettyWriter writer(*s); + encodeValue(L, &writer, idx); + } + else + { + Writer writer(*s); + encodeValue(L, &writer, idx); + } + } +}; + +static int json_encode(lua_State* L) +{ + try + { + Encoder encode(L, 2); + StringBuffer s; + encode.encode(L, &s, 1); + lua_pushlstring(L, s.GetString(), s.GetSize()); + return 1; + } + catch (...) + { + luaL_error(L, "error while encoding"); + } + return 0; +} + +static int json_dump(lua_State* L) +{ + Encoder encoder(L, 3); + + auto filename = luaL_checkstring(L, 2); + auto fp = file::open(filename, "wb"); + if (fp == nullptr) + luaL_error(L, "error while open file: %s", filename); + + char buffer[512]; + FileWriteStream fs(fp, buffer, sizeof(buffer)); + encoder.encode(L, &fs, 1); + fclose(fp); + return 0; +} + +namespace values +{ + static auto nullref = LUA_NOREF; + /** + * Returns rapidjson.null. + */ + int json_null(lua_State* L) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, nullref); + return 1; + } +} + +static const luaL_Reg methods[] = +{ + // string <--> lua table + { "decode", json_decode }, + { "encode", json_encode }, + + // file <--> lua table + { "load", json_load }, + { "dump", json_dump }, + + // special tags and functions + { "null", values::json_null }, + { "object", json_object }, + { "array", json_array }, + + // JSON types + { "Document", Userdata::create }, + { "SchemaDocument", Userdata::create }, + { "SchemaValidator", Userdata::create }, + + {nullptr, nullptr } +}; + +extern "C" +{ + LUALIB_API int luaopen_rapidjson(lua_State* L) + { + lua_newtable(L); + + luax::setfuncs(L, methods); + + lua_pushliteral(L, "rapidjson"); + lua_setfield(L, -2, "_NAME"); + + lua_pushliteral(L, LUA_RAPIDJSON_VERSION); + lua_setfield(L, -2, "_VERSION"); + + lua_getfield(L, -1, "null"); + values::nullref = luaL_ref(L, LUA_REGISTRYINDEX); + + + createSharedMeta(L, "json.object", "object"); + createSharedMeta(L, "json.array", "array"); + + Userdata::luaopen(L); + Userdata::luaopen(L); + Userdata::luaopen(L); + + return 1; + } +} diff --git a/src/script/rapidjson/schema.cpp b/src/script/rapidjson/schema.cpp new file mode 100644 index 0000000..ade5736 --- /dev/null +++ b/src/script/rapidjson/schema.cpp @@ -0,0 +1,116 @@ + +#include "userdata.hpp" +#include "values.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +using namespace rapidjson; + +template<> +const char* const Userdata::metatable = "rapidjson.SchemaDocument"; + +template<> +SchemaDocument* Userdata::construct(lua_State * L) +{ + switch (lua_type(L, 1)) + { + case LUA_TNONE: + return new SchemaDocument(Document()); // empty schema + case LUA_TSTRING: + { + auto d = Document(); + size_t len = 0; + auto s = lua_tolstring(L, 1, &len); + d.Parse(s, len); + return new SchemaDocument(d); + } + case LUA_TTABLE: + { + auto doc = Document(); + values::toDocument(L, 1, &doc); + return new SchemaDocument(doc); + } + case LUA_TUSERDATA: + { + auto doc = Userdata::check(L, 1); + return new SchemaDocument(*doc); + } + default: + luax::typerror(L, 1, "none, string, table or rapidjson.Document"); + return nullptr; // Just make compiler happy + } +} + +template <> +const luaL_Reg* Userdata::methods() +{ + static const luaL_Reg reg[] = + { + { "__gc", metamethod_gc }, + { nullptr, nullptr } + }; + return reg; +} + +template<> +const char* const Userdata::metatable = "rapidjson.SchemaValidator"; + +template<> +SchemaValidator* Userdata::construct(lua_State * L) +{ + auto sd = Userdata::check(L, 1); + return new SchemaValidator(*sd); +} + +static void pushValidator_error(lua_State* L, SchemaValidator* validator) +{ + luaL_Buffer b; + luaL_buffinit(L, &b); + + luaL_addstring(&b, "invalid \""); + luaL_addstring(&b, validator->GetInvalidSchemaKeyword()); + luaL_addstring(&b, "\" in docuement at pointer \""); + + // docuement pointer + StringBuffer sb; + validator->GetInvalidDocumentPointer().StringifyUriFragment(sb); + luaL_addlstring(&b, sb.GetString(), sb.GetSize()); + luaL_addchar(&b, '"'); + + luaL_pushresult(&b); +} + +static int SchemaValidator_validate(lua_State* L) +{ + auto validator = Userdata::check(L, 1); + auto doc = Userdata::check(L, 2); + auto ok = doc->Accept(*validator); + lua_pushboolean(L, ok); + int nr; + + if (ok) + { + nr = 1; + } + else + { + pushValidator_error(L, validator); + nr = 2; + } + validator->Reset(); + + return nr; +} + +template <> +const luaL_Reg* Userdata::methods() +{ + static const luaL_Reg reg[] = + { + { "__gc", metamethod_gc }, + { "validate", SchemaValidator_validate }, + { nullptr, nullptr } + }; + return reg; +} diff --git a/src/script/rapidjson/userdata.hpp b/src/script/rapidjson/userdata.hpp new file mode 100644 index 0000000..1127bac --- /dev/null +++ b/src/script/rapidjson/userdata.hpp @@ -0,0 +1,112 @@ +#ifndef __LUA_RAPIDJSON_USERDATA_HPP__ +#define __LUA_RAPIDJSON_USERDATA_HPP__ + + +#include "qcommon/q3_lauxlib.h" + +#include "luax.hpp" + +#include "lua.hpp" +#include "rapidjson.h" + +template +struct Userdata +{ + static int create(lua_State* L) + { + push(L, construct(L)); + return 1; + } + + static T* construct(lua_State* L); + + static void luaopen(lua_State* L) + { + luaL_newmetatable(L, metatable); + lua_pushvalue(L, -1); + luax::setfuncs(L, methods()); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); + } + + static const luaL_Reg* methods(); + + static void push(lua_State* L, T* c) + { + if (!c) + { + lua_pushnil(L); + return; + } + + T** ud = reinterpret_cast(lua_newuserdata(L, sizeof(*ud))); + if (!ud) + luaL_error(L, "Out of memory"); + + *ud = c; + + luaL_getmetatable(L, metatable); + lua_setmetatable(L, -2); + } + + static T** getUserdata(lua_State* L, int idx) + { + return reinterpret_cast(lua_touserdata(L, idx)); + } + + + static T* get(lua_State* L, int idx) + { + auto p = getUserdata(L, idx); + if (p && *p ) + { + if (lua_getmetatable(L, idx)) + { /* does it have a metatable? */ + luaL_getmetatable(L, metatable); /* get correct metatable */ + if (lua_rawequal(L, -1, -2)) + { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return *p; + } + } + } + return nullptr; + } + + static T* check(lua_State* L, int idx) + { + auto ud = reinterpret_cast(luaL_checkudata(L, idx, metatable)); + if (!*ud) + luaL_error(L, "%s already closed", metatable); + return *ud; + } + + static int metamethod_gc(lua_State* L) + { + T** ud = reinterpret_cast(luaL_checkudata(L, 1, metatable)); + if (*ud) + { + delete *ud; + *ud = nullptr; + } + return 0; + } + + static int metamethod_tostring(lua_State* L) + { + auto ud = getUserdata(L, 1); + if (*ud) + { + lua_pushfstring(L, "%s (%p)", metatable, *ud); + } + else + { + lua_pushfstring(L, "%s (closed)", metatable); + } + return 1; + } + + static const char* const metatable; +}; + +#endif diff --git a/src/script/rapidjson/values.cpp b/src/script/rapidjson/values.cpp new file mode 100644 index 0000000..1d9c892 --- /dev/null +++ b/src/script/rapidjson/values.cpp @@ -0,0 +1,108 @@ + +#include "values.hpp" +#include "luax.hpp" + +using rapidjson::Value; +using rapidjson::SizeType; + +namespace values +{ + namespace details + { + static Value NumberValue(lua_State* L, int idx); + static Value StringValue(lua_State* L, int idx, Allocator& allocator); + static Value TableValue(lua_State* L, int idx, int depth, Allocator& allocator); + static Value ObjectValue(lua_State* L, int idx, int depth, Allocator& allocator); + static Value ArrayValue(lua_State* L, int idx, int depth, Allocator& allocator); + + Value toValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + auto t = lua_type(L, idx); + switch (t) + { + case LUA_TBOOLEAN: + return Value(lua_toboolean(L, idx) != 0); + case LUA_TNUMBER: + return NumberValue(L, idx); + case LUA_TSTRING: + return StringValue(L, idx, allocator); + case LUA_TTABLE: + return TableValue(L, idx, depth + 1, allocator); + case LUA_TNIL: + return Value(); + case LUA_TFUNCTION: + if (isnull(L, idx)) + return Value(); + // otherwise fall thought + case LUA_TLIGHTUSERDATA: // fall thought + case LUA_TUSERDATA: // fall thought + case LUA_TTHREAD: // fall thought + case LUA_TNONE: // fall thought + default: + luaL_error(L, "value type %s is not a valid json value", lua_typename(L, t)); + return Value(); // Just make compiler happy + } + } + + Value NumberValue(lua_State* L, int idx) + { + int64_t integer; + return luax::isinteger(L, idx, &integer) ? Value(integer) : Value(lua_tonumber(L, idx)); + } + + Value StringValue(lua_State* L, int idx, Allocator& allocator) + { + size_t len; + const char* s = lua_tolstring(L, idx, &len); + return Value(s, static_cast(len), allocator); + } + + Value TableValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + if (depth > 1024) + luaL_error(L, "nested too depth"); + + if (!lua_checkstack(L, 4)) // requires at least 4 slots in stack: table, key, value, key + luaL_error(L, "stack overflow"); + + return isarray(L, idx) ? ArrayValue(L, idx, depth, allocator) : ObjectValue(L, idx, depth, allocator); + } + + Value ObjectValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + Value object(rapidjson::kObjectType); + lua_pushvalue(L, idx); + lua_pushnil(L); + while (lua_next(L, -2)) + { + + if (lua_type(L, -2) == LUA_TSTRING) + { + object.AddMember(StringValue(L, -2, allocator), toValue(L, -1, depth, allocator), allocator); + } + + // pop value, leaving original key + lua_pop(L, 1); + + } + + lua_pop(L, 1); + return object; + } + + Value ArrayValue(lua_State* L, int idx, int depth, Allocator& allocator) + { + Value array(rapidjson::kArrayType); + auto MAX = static_cast(luax::rawlen(L, idx)); // luax::rawlen always returns size_t (>= 0) + for (auto n = 1; n <= MAX; ++n) + { + lua_rawgeti(L, idx, n); + array.PushBack(toValue(L, -1, depth, allocator), allocator); + lua_pop(L, 1); + } + + + return array; + } + } +} diff --git a/src/script/rapidjson/values.hpp b/src/script/rapidjson/values.hpp new file mode 100644 index 0000000..7fb3abe --- /dev/null +++ b/src/script/rapidjson/values.hpp @@ -0,0 +1,245 @@ +#ifndef __LUA_RAPIDJSON_TOLUAHANDLER_HPP__ +#define __LUA_RAPIDJSON_TOLUAHANDLER_HPP__ + +#include + + +#include "qcommon/q3_lauxlib.h" +#include "lua.hpp" +#include "rapidjson.h" + +#include "luax.hpp" +namespace values +{ + typedef rapidjson::Document::AllocatorType Allocator; + + int json_null(lua_State* L); + + inline bool isnull(lua_State* L, int idx) + { + lua_pushvalue(L, idx); // [value] + + json_null(L); // [value, json.null] + auto is = lua_rawequal(L, -1, -2) != 0; + lua_pop(L, 2); // [] + + return is; + } + + inline bool hasJsonType(lua_State* L, int idx, bool& isarray) + { + auto has = false; + if (lua_getmetatable(L, idx)) + { + // [metatable] + lua_getfield(L, -1, "__jsontype"); // [metatable, metatable.__jsontype] + if (lua_isstring(L, -1)) + { + size_t len; + auto s = lua_tolstring(L, -1, &len); + isarray = strncmp(s, "array", 6) == 0; + has = true; + } + lua_pop(L, 2); // [] + } + + return has; + } + + inline bool isarray(lua_State* L, int idx) + { + auto arr = false; + if (hasJsonType(L, idx, arr)) // any table with a meta field __jsontype set to 'array' are arrays + return arr; + + return luax::rawlen(L, idx) > 0; // any table has length > 0 are treat as array. + } + + /** + * Handle json SAX events and create Lua object. + */ + struct ToLuaHandler + { + explicit ToLuaHandler(lua_State* aL) : L(aL) + { stack_.reserve(32); } + + bool Null() + { + json_null(L); + context_.submit(L); + return true; + } + bool Bool(bool b) + { + lua_pushboolean(L, b); + context_.submit(L); + return true; + } + bool Int(int i) + { + lua_pushinteger(L, i); + context_.submit(L); + return true; + } + bool Uint(unsigned u) + { + if (sizeof(lua_Integer) > sizeof(unsigned int) || u <= static_cast(std::numeric_limits::max())) + lua_pushinteger(L, static_cast(u)); + else + lua_pushnumber(L, static_cast(u)); + context_.submit(L); + return true; + } + bool Int64(int64_t i) + { + if (sizeof(lua_Integer) >= sizeof(int64_t) || (i <= std::numeric_limits::max() && i >= std::numeric_limits::min())) + lua_pushinteger(L, static_cast(i)); + else + lua_pushnumber(L, static_cast(i)); + context_.submit(L); + return true; + } + bool Uint64(uint64_t u) + { + if (sizeof(lua_Integer) > sizeof(uint64_t) || u <= static_cast(std::numeric_limits::max())) + lua_pushinteger(L, static_cast(u)); + else + lua_pushnumber(L, static_cast(u)); + context_.submit(L); + return true; + } + bool Double(double d) + { + lua_pushnumber(L, static_cast(d)); + context_.submit(L); + return true; + } + bool RawNumber(const char* str, rapidjson::SizeType length, bool copy) + { + lua_getglobal(L, "tonumber"); + lua_pushlstring(L, str, length); + lua_call(L, 1, 1); + context_.submit(L); + return true; + } + bool String(const char* str, rapidjson::SizeType length, bool copy) + { + lua_pushlstring(L, str, length); + context_.submit(L); + return true; + } + bool StartObject() + { + lua_createtable(L, 0, 0); // [..., object] + + // mark as object. + luaL_getmetatable(L, "json.object"); //[..., object, json.object] + lua_setmetatable(L, -2); // [..., object] + + stack_.push_back(context_); + context_ = Ctx::Object(); + return true; + } + bool Key(const char* str, rapidjson::SizeType length, bool copy) const + { + lua_pushlstring(L, str, length); + return true; + } + bool EndObject(rapidjson::SizeType memberCount) + { + context_ = stack_.back(); + stack_.pop_back(); + context_.submit(L); + return true; + } + bool StartArray() + { + lua_createtable(L, 0, 0); + + // mark as array. + luaL_getmetatable(L, "json.array"); //[..., array, json.array] + lua_setmetatable(L, -2); // [..., array] + + stack_.push_back(context_); + context_ = Ctx::Array(); + return true; + } + bool EndArray(rapidjson::SizeType elementCount) + { + assert(elementCount == context_.index_); + context_ = stack_.back(); + stack_.pop_back(); + context_.submit(L); + return true; + } + private: + + + struct Ctx + { + Ctx() : index_(0), fn_(&topFn) {} + Ctx(const Ctx& rhs) : index_(rhs.index_), fn_(rhs.fn_) {} + + const Ctx& operator=(const Ctx& rhs) + { + if (this != &rhs) + { + index_ = rhs.index_; + fn_ = rhs.fn_; + } + return *this; + } + + static Ctx Object() + { return Ctx(&objectFn); } + + static Ctx Array() + { return Ctx(&arrayFn); } + + void submit(lua_State* L) + { fn_(L, this); } + + int index_; + + void(*fn_)(lua_State* L, Ctx* ctx); + + private: + explicit Ctx(void(*f)(lua_State* L, Ctx* ctx)) : index_(0), fn_(f) {} + + static void objectFn(lua_State* L, Ctx* ctx) + { lua_rawset(L, -3); } + + static void arrayFn(lua_State* L, Ctx* ctx) + { lua_rawseti(L, -2, ++ctx->index_); } + + static void topFn(lua_State* L, Ctx* ctx) {} + }; + + lua_State* L; + std::vector < Ctx > stack_; + Ctx context_; + }; + + namespace details + { + rapidjson::Value toValue(lua_State* L, int idx, int depth, Allocator& allocator); + } + + inline rapidjson::Value toValue(lua_State* L, int idx, Allocator& allocator) + { + return details::toValue(L, idx, 0, allocator); + } + + inline void toDocument(lua_State* L, int idx, rapidjson::Document* doc) + { + details::toValue(L, idx, 0, doc->GetAllocator()).Swap(*doc); + } + + inline void push(lua_State* L, const rapidjson::Value& v) + { + ToLuaHandler handler(L); + v.Accept(handler); + } +} + +#endif diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt new file mode 100644 index 0000000..20dbe32 --- /dev/null +++ b/src/sdl/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library ( + sdl STATIC + sdl_gamma.cpp + sdl_glimp.cpp + sdl_icon.h + sdl_input.cpp + sdl_snd.cpp +) diff --git a/src/sdl/sdl_gamma.cpp b/src/sdl/sdl_gamma.cpp new file mode 100644 index 0000000..372fdf2 --- /dev/null +++ b/src/sdl/sdl_gamma.cpp @@ -0,0 +1,100 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifdef _WIN32 +#include +#endif + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include "qcommon/cvar.h" +#include "qcommon/qcommon.h" +#include "renderercommon/tr_common.h" + +extern SDL_Window *SDL_window; + +/* +================= +GLimp_SetGamma +================= +*/ +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ) +{ + Uint16 table[3][256]; + int i, j; + + if( !glConfig.deviceSupportsGamma || r_ignorehwgamma->integer > 0 ) + return; + + for (i = 0; i < 256; i++) + { + table[0][i] = ( ( ( Uint16 ) red[i] ) << 8 ) | red[i]; + table[1][i] = ( ( ( Uint16 ) green[i] ) << 8 ) | green[i]; + table[2][i] = ( ( ( Uint16 ) blue[i] ) << 8 ) | blue[i]; + } + +#ifdef _WIN32 + // Win2K and newer put this odd restriction on gamma ramps... + { + OSVERSIONINFO vinfo; + + vinfo.dwOSVersionInfoSize = sizeof( vinfo ); + GetVersionEx( &vinfo ); + if( vinfo.dwMajorVersion >= 5 && vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + ri.Printf( PRINT_DEVELOPER, "performing gamma clamp.\n" ); + for( j = 0 ; j < 3 ; j++ ) + { + for( i = 0 ; i < 128 ; i++ ) + { + if( table[ j ] [ i] > ( ( 128 + i ) << 8 ) ) + table[ j ][ i ] = ( 128 + i ) << 8; + } + + if( table[ j ] [127 ] > 254 << 8 ) + table[ j ][ 127 ] = 254 << 8; + } + } + } +#endif + + // enforce constantly increasing + for (j = 0; j < 3; j++) + { + for (i = 1; i < 256; i++) + { + if (table[j][i] < table[j][i-1]) + table[j][i] = table[j][i-1]; + } + } + + if (SDL_SetWindowGammaRamp(SDL_window, table[0], table[1], table[2]) < 0) + { + ri.Printf( PRINT_DEVELOPER, "SDL_SetWindowGammaRamp() failed: %s\n", SDL_GetError() ); + } +} diff --git a/src/sdl/sdl_glimp.cpp b/src/sdl/sdl_glimp.cpp new file mode 100644 index 0000000..9ecd224 --- /dev/null +++ b/src/sdl/sdl_glimp.cpp @@ -0,0 +1,935 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include +#include +#include +#include + +#include "renderercommon/tr_common.h" +#include "qcommon/cvar.h" +#include "sys/sys_local.h" +#include "sdl_icon.h" + +typedef enum +{ + RSERR_OK, + + RSERR_INVALID_FULLSCREEN, + RSERR_INVALID_MODE, + + RSERR_UNKNOWN +} rserr_t; + +SDL_Window *SDL_window = NULL; +static SDL_GLContext SDL_glContext = NULL; + +cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obtained +cvar_t *r_allowResize; // make window resizable +cvar_t *r_centerWindow; +cvar_t *r_sdlDriver; + +void (APIENTRYP qglActiveTextureARB) (GLenum texture); +void (APIENTRYP qglClientActiveTextureARB) (GLenum texture); +void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); + +void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count); +void (APIENTRYP qglUnlockArraysEXT) (void); + +/* +=============== +GLimp_Shutdown +=============== +*/ +void GLimp_Shutdown( void ) +{ + float oldDisplayAspect = glConfig.displayAspect; + + ri.IN_Shutdown(); + + SDL_QuitSubSystem( SDL_INIT_VIDEO ); + + glConfig.displayAspect = oldDisplayAspect; +} + +/* +=============== +GLimp_Minimize + +Minimize the game so that user is back at the desktop +=============== +*/ +void GLimp_Minimize( void ) +{ + SDL_MinimizeWindow( SDL_window ); +} + + +/* +=============== +GLimp_LogComment +=============== +*/ +void GLimp_LogComment( const char *comment ) +{ +} + +/* +=============== +GLimp_CompareModes +=============== +*/ +static int GLimp_CompareModes( const void *a, const void *b ) +{ + const float ASPECT_EPSILON = 0.001f; + SDL_Rect *modeA = (SDL_Rect *)a; + SDL_Rect *modeB = (SDL_Rect *)b; + float aspectA = (float)modeA->w / (float)modeA->h; + float aspectB = (float)modeB->w / (float)modeB->h; + int areaA = modeA->w * modeA->h; + int areaB = modeB->w * modeB->h; + float aspectDiffA = fabs( aspectA - glConfig.displayAspect ); + float aspectDiffB = fabs( aspectB - glConfig.displayAspect ); + float aspectDiffsDiff = aspectDiffA - aspectDiffB; + + if( aspectDiffsDiff > ASPECT_EPSILON ) + return 1; + else if( aspectDiffsDiff < -ASPECT_EPSILON ) + return -1; + else + return areaA - areaB; +} + + +/* +=============== +GLimp_DetectAvailableModes +=============== +*/ +static void GLimp_DetectAvailableModes(void) +{ + int i, j; + char buf[ MAX_STRING_CHARS ] = { 0 }; + int numSDLModes; + SDL_Rect *modes; + int numModes = 0; + + SDL_DisplayMode windowMode; + int display = SDL_GetWindowDisplayIndex( SDL_window ); + if( display < 0 ) + { + ri.Printf( PRINT_WARNING, "Couldn't get window display index, no resolutions detected: %s\n", SDL_GetError() ); + return; + } + numSDLModes = SDL_GetNumDisplayModes( display ); + + if( SDL_GetWindowDisplayMode( SDL_window, &windowMode ) < 0 || numSDLModes <= 0 ) + { + ri.Printf( PRINT_WARNING, "Couldn't get window display mode, no resolutions detected: %s\n", SDL_GetError() ); + return; + } + + modes = (SDL_Rect*)SDL_calloc( (size_t)numSDLModes, sizeof( SDL_Rect ) ); + if ( !modes ) + { + ri.Error( ERR_FATAL, "Out of memory" ); + } + + for( i = 0; i < numSDLModes; i++ ) + { + SDL_DisplayMode mode; + + if( SDL_GetDisplayMode( display, i, &mode ) < 0 ) + continue; + + if( !mode.w || !mode.h ) + { + ri.Printf( PRINT_ALL, "Display supports any resolution\n" ); + SDL_free( modes ); + return; + } + + if( windowMode.format != mode.format ) + continue; + + // SDL can give the same resolution with different refresh rates. + // Only list resolution once. + for( j = 0; j < numModes; j++ ) + { + if( mode.w == modes[ j ].w && mode.h == modes[ j ].h ) + break; + } + + if( j != numModes ) + continue; + + modes[ numModes ].w = mode.w; + modes[ numModes ].h = mode.h; + numModes++; + } + + if( numModes > 1 ) + qsort( modes, numModes, sizeof( SDL_Rect ), GLimp_CompareModes ); + + for( i = 0; i < numModes; i++ ) + { + const char *newModeString = va( "%ux%u ", modes[ i ].w, modes[ i ].h ); + + if( strlen( newModeString ) < (int)sizeof( buf ) - strlen( buf ) ) + Q_strcat( buf, sizeof( buf ), newModeString ); + else + ri.Printf( PRINT_WARNING, "Skipping mode %ux%u, buffer too small\n", modes[ i ].w, modes[ i ].h ); + } + + if( *buf ) + { + buf[ strlen( buf ) - 1 ] = 0; + ri.Printf( PRINT_ALL, "Available modes: '%s'\n", buf ); + ri.Cvar_Set( "r_availableModes", buf ); + } + SDL_free( modes ); +} + +#define R_FAILSAFE_WIDTH 640 +#define R_FAILSAFE_HEIGHT 480 + +/* +=============== +GLimp_SetMode +=============== +*/ +static int GLimp_SetMode( bool failSafe, bool fullscreen, bool noborder, bool coreContext ) +{ + const char *glstring; + int perChannelColorBits; + int colorBits, alphaBits, depthBits, stencilBits; + int samples; + int i = 0; + SDL_Surface *icon = NULL; + Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL; + SDL_DisplayMode desktopMode; + int display = 0; + int x = SDL_WINDOWPOS_UNDEFINED, y = SDL_WINDOWPOS_UNDEFINED; + + ri.Printf( PRINT_ALL, "Initializing OpenGL display\n"); + + if ( r_allowResize->integer ) + flags |= SDL_WINDOW_RESIZABLE; + +#ifdef USE_ICON + icon = SDL_CreateRGBSurfaceFrom( + (void *)CLIENT_WINDOW_ICON.pixel_data, + CLIENT_WINDOW_ICON.width, + CLIENT_WINDOW_ICON.height, + CLIENT_WINDOW_ICON.bytes_per_pixel * 8, + CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width, +#ifdef Q3_LITTLE_ENDIAN + 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 +#else + 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF +#endif + ); +#endif + + // If a window exists, note its display index + if( SDL_window != NULL ) + { + display = SDL_GetWindowDisplayIndex( SDL_window ); + if( display < 0 ) + { + ri.Printf( PRINT_DEVELOPER, "SDL_GetWindowDisplayIndex() failed: %s\n", SDL_GetError() ); + } + } + + if( display >= 0 && SDL_GetDesktopDisplayMode( display, &desktopMode ) == 0 ) + { + glConfig.displayAspect = (float)desktopMode.w / (float)desktopMode.h; + + ri.Printf( PRINT_ALL, "Display aspect: %.3f\n", glConfig.displayAspect ); + } + else + { + Com_Memset( &desktopMode, 0, sizeof( SDL_DisplayMode ) ); + + ri.Printf( PRINT_ALL, + "Cannot determine display aspect, assuming 1.333\n" ); + } + + if( !failSafe ) + { + if( r_width->integer > 0 && r_height->integer > 0 ) + { + glConfig.vidWidth = r_width->integer; + glConfig.vidHeight = r_height->integer; + } + else if( desktopMode.h > 0 ) + { + // use desktop video resolution + glConfig.vidWidth = desktopMode.w; + glConfig.vidHeight = desktopMode.h; + } + else + { + glConfig.vidWidth = 640; + glConfig.vidHeight = 480; + ri.Printf( PRINT_ALL, + "Cannot determine display resolution, assuming 640x480\n" ); + } + + glConfig.windowAspect = (float)glConfig.vidWidth / + ( (float)glConfig.vidHeight * r_pixelAspect->value ); + } + else if( glConfig.vidWidth != R_FAILSAFE_WIDTH && + glConfig.vidHeight != R_FAILSAFE_HEIGHT ) + { + ri.Printf( PRINT_ALL, "Setting mode %dx%d failed, falling back on mode %dx%d\n", + glConfig.vidWidth, glConfig.vidHeight, R_FAILSAFE_WIDTH, R_FAILSAFE_HEIGHT ); + + glConfig.vidWidth = R_FAILSAFE_WIDTH; + glConfig.vidHeight = R_FAILSAFE_HEIGHT; + glConfig.windowAspect = 1.0f; + } + else + return RSERR_INVALID_MODE; + + ri.Printf (PRINT_ALL, "...setting mode %dx%d\n", glConfig.vidWidth, glConfig.vidHeight); + + // Center window + if( r_centerWindow->integer && !fullscreen ) + { + x = ( desktopMode.w / 2 ) - ( glConfig.vidWidth / 2 ); + y = ( desktopMode.h / 2 ) - ( glConfig.vidHeight / 2 ); + } + + // Destroy existing state if it exists + if( SDL_glContext != NULL ) + { + SDL_GL_DeleteContext( SDL_glContext ); + SDL_glContext = NULL; + } + + if( SDL_window != NULL ) + { + SDL_GetWindowPosition( SDL_window, &x, &y ); + ri.Printf( PRINT_DEVELOPER, "Existing window at %dx%d before being destroyed\n", x, y ); + SDL_DestroyWindow( SDL_window ); + SDL_window = NULL; + } + + if( fullscreen ) + { + flags |= SDL_WINDOW_FULLSCREEN; + glConfig.isFullscreen = qtrue; + } + else + { + if( noborder ) + flags |= SDL_WINDOW_BORDERLESS; + + glConfig.isFullscreen = qfalse; + } + + colorBits = r_colorbits->value; + if ((!colorBits) || (colorBits >= 32)) + colorBits = 24; + + alphaBits = r_alphabits->value; + + if (!r_depthbits->value) + depthBits = 24; + else + depthBits = r_depthbits->value; + + stencilBits = r_stencilbits->value; + samples = r_ext_multisample->value; + + for (i = 0; i < 16; i++) + { + int testColorBits, testDepthBits, testStencilBits; + + // 0 - default + // 1 - minus colorBits + // 2 - minus depthBits + // 3 - minus stencil + if ((i % 4) == 0 && i) + { + // one pass, reduce + switch (i / 4) + { + case 2 : + if (colorBits == 24) + colorBits = 16; + break; + case 1 : + if (depthBits == 24) + depthBits = 16; + else if (depthBits == 16) + depthBits = 8; + case 3 : + if (stencilBits == 24) + stencilBits = 16; + else if (stencilBits == 16) + stencilBits = 8; + } + } + + testColorBits = colorBits; + testDepthBits = depthBits; + testStencilBits = stencilBits; + + if ((i % 4) == 3) + { // reduce colorBits + if (testColorBits == 24) + testColorBits = 16; + } + + if ((i % 4) == 2) + { // reduce depthBits + if (testDepthBits == 24) + testDepthBits = 16; + else if (testDepthBits == 16) + testDepthBits = 8; + } + + if ((i % 4) == 1) + { // reduce stencilBits + if (testStencilBits == 24) + testStencilBits = 16; + else if (testStencilBits == 16) + testStencilBits = 8; + else + testStencilBits = 0; + } + + if (testColorBits == 24) + perChannelColorBits = 8; + else + perChannelColorBits = 4; + +#ifdef __sgi /* Fix for SGIs grabbing too many bits of color */ + if (perChannelColorBits == 4) + perChannelColorBits = 0; /* Use minimum size for 16-bit color */ + + /* Need alpha or else SGIs choose 36+ bit RGB mode */ + if (alphaBits < 1) + alphaBits = 1; +#endif + + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, perChannelColorBits ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, perChannelColorBits ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, perChannelColorBits ); + SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, alphaBits ); + SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, testDepthBits ); + SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, testStencilBits ); + + SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, samples ? 1 : 0 ); + SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, samples ); + + if(r_stereoEnabled->integer) + { + glConfig.stereoEnabled = qtrue; + SDL_GL_SetAttribute(SDL_GL_STEREO, 1); + } + else + { + glConfig.stereoEnabled = qfalse; + SDL_GL_SetAttribute(SDL_GL_STEREO, 0); + } + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + +#if 0 // if multisampling is enabled on X11, this causes create window to fail. + // If not allowing software GL, demand accelerated + if( !r_allowSoftwareGL->integer ) + SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 ); +#endif + + if( ( SDL_window = SDL_CreateWindow( CLIENT_WINDOW_TITLE, x, y, + glConfig.vidWidth, glConfig.vidHeight, flags ) ) == NULL ) + { + ri.Printf( PRINT_DEVELOPER, "SDL_CreateWindow failed: %s\n", SDL_GetError( ) ); + continue; + } + + if( fullscreen ) + { + SDL_DisplayMode mode; + + switch( testColorBits ) + { + case 16: mode.format = SDL_PIXELFORMAT_RGB565; break; + case 24: mode.format = SDL_PIXELFORMAT_RGB24; break; + default: ri.Printf( PRINT_DEVELOPER, "testColorBits is %d, can't fullscreen\n", testColorBits ); continue; + } + + mode.w = glConfig.vidWidth; + mode.h = glConfig.vidHeight; + mode.refresh_rate = glConfig.displayFrequency = ri.Cvar_VariableIntegerValue( "r_displayRefresh" ); + mode.driverdata = NULL; + + if( SDL_SetWindowDisplayMode( SDL_window, &mode ) < 0 ) + { + ri.Printf( PRINT_DEVELOPER, "SDL_SetWindowDisplayMode failed: %s\n", SDL_GetError( ) ); + continue; + } + } + + SDL_SetWindowIcon( SDL_window, icon ); + + if (coreContext) + { + int profileMask, majorVersion, minorVersion; + SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion); + + ri.Printf(PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n"); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL) + { + ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); + ri.Printf(PRINT_ALL, "Reverting to default context\n"); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + } + else + { + ri.Printf(PRINT_ALL, "SDL_GL_CreateContext succeeded.\n"); + + const char *renderer = (const char*)qglGetString(GL_RENDERER); + if (renderer && (strstr(renderer, "Software Renderer") || strstr(renderer, "Software Rasterizer"))) + { + ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer); + + SDL_GL_DeleteContext(SDL_glContext); + SDL_glContext = NULL; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + } + } + } + else + { + SDL_glContext = NULL; + } + + if( !SDL_glContext && ( SDL_glContext = SDL_GL_CreateContext( SDL_window ) ) == NULL ) + { + ri.Printf( PRINT_DEVELOPER, "SDL_GL_CreateContext failed: %s\n", SDL_GetError( ) ); + continue; + } + + qglClearColor( 0, 0, 0, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + SDL_GL_SwapWindow( SDL_window ); + + if( SDL_GL_SetSwapInterval( r_swapInterval->integer ) == -1 ) + { + ri.Printf( PRINT_DEVELOPER, "SDL_GL_SetSwapInterval failed: %s\n", SDL_GetError( ) ); + } + + glConfig.colorBits = testColorBits; + glConfig.depthBits = testDepthBits; + glConfig.stencilBits = testStencilBits; + + ri.Printf( PRINT_ALL, "Using %d color bits, %d depth, %d stencil display.\n", + glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + break; + } + + SDL_FreeSurface( icon ); + + if( !SDL_window ) + { + ri.Printf( PRINT_ALL, "Couldn't get a visual\n" ); + return RSERR_INVALID_MODE; + } + + GLimp_DetectAvailableModes(); + + glstring = (char *) qglGetString (GL_RENDERER); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glstring ); + + return RSERR_OK; +} + +/* +=============== +GLimp_StartDriverAndSetMode +=============== +*/ +static bool GLimp_StartDriverAndSetMode( bool failSafe, bool fullscreen, bool noborder, bool gl3Core ) +{ + if (!SDL_WasInit(SDL_INIT_VIDEO)) + { + + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + ri.Printf( PRINT_ALL, "SDL_Init( SDL_INIT_VIDEO ) FAILED (%s)\n", SDL_GetError()); + return false; + } + + const char *driverName = SDL_GetCurrentVideoDriver( ); + ri.Printf( PRINT_ALL, "SDL using driver \"%s\"\n", driverName ); + ri.Cvar_Set( "r_sdlDriver", driverName ); + } + + if (fullscreen && ri.Cvar_VariableIntegerValue( "in_nograb" ) ) + { + ri.Printf( PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n"); + ri.Cvar_Set( "r_fullscreen", "0" ); + r_fullscreen->modified = qfalse; + fullscreen = false; + } + + rserr_t err = (rserr_t)GLimp_SetMode( failSafe, fullscreen, noborder, gl3Core ); + + switch ( err ) + { + case RSERR_INVALID_FULLSCREEN: + ri.Printf( PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n" ); + return false; + case RSERR_INVALID_MODE: + ri.Printf( PRINT_ALL, "...WARNING: could not set the given mode\n" ); + return false; + default: + break; + } + + return true; +} + +static bool GLimp_HaveExtension(const char *ext) +{ + const char *ptr = Q_stristr( glConfig.extensions_string, ext ); + if (ptr == NULL) + return qfalse; + ptr += strlen(ext); + return ((*ptr == ' ') || (*ptr == '\0')); // verify it's complete string. +} + + +/* +=============== +GLimp_InitExtensions +=============== +*/ +static void GLimp_InitExtensions( void ) +{ + if ( !r_allowExtensions->integer ) + { + ri.Printf( PRINT_ALL, "* IGNORING OPENGL EXTENSIONS *\n" ); + return; + } + + ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" ); + + glConfig.textureCompression = TC_NONE; + + // GL_EXT_texture_compression_s3tc + if ( GLimp_HaveExtension( "GL_ARB_texture_compression" ) && + GLimp_HaveExtension( "GL_EXT_texture_compression_s3tc" ) ) + { + if ( r_ext_compressed_textures->value ) + { + glConfig.textureCompression = TC_S3TC_ARB; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_compression_s3tc\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_compression_s3tc\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_compression_s3tc not found\n" ); + } + + // GL_S3_s3tc ... legacy extension before GL_EXT_texture_compression_s3tc. + if (glConfig.textureCompression == TC_NONE) + { + if ( GLimp_HaveExtension( "GL_S3_s3tc" ) ) + { + if ( r_ext_compressed_textures->value ) + { + glConfig.textureCompression = TC_S3TC; + ri.Printf( PRINT_ALL, "...using GL_S3_s3tc\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_S3_s3tc\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_S3_s3tc not found\n" ); + } + } + + + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( GLimp_HaveExtension( "GL_EXT_texture_env_add" ) ) + { + if ( r_ext_texture_env_add->integer ) + { + glConfig.textureEnvAddAvailable = qtrue; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" ); + } + else + { + glConfig.textureEnvAddAvailable = qfalse; + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" ); + } + + // GL_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + if ( GLimp_HaveExtension( "GL_ARB_multitexture" ) ) + { + if ( r_ext_multitexture->value ) + { + qglMultiTexCoord2fARB = (decltype(qglMultiTexCoord2fARB)) SDL_GL_GetProcAddress( "glMultiTexCoord2fARB" ); + qglActiveTextureARB = (decltype(qglActiveTextureARB)) SDL_GL_GetProcAddress( "glActiveTextureARB" ); + qglClientActiveTextureARB = (decltype(qglClientActiveTextureARB)) SDL_GL_GetProcAddress( "glClientActiveTextureARB" ); + + if ( qglActiveTextureARB ) + { + GLint glint = 0; + qglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glint ); + glConfig.numTextureUnits = (int) glint; + if ( glConfig.numTextureUnits > 1 ) + { + ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } + else + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + ri.Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n" ); + } + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); + } + + // GL_EXT_compiled_vertex_array + if ( GLimp_HaveExtension( "GL_EXT_compiled_vertex_array" ) ) + { + if ( r_ext_compiled_vertex_array->value ) + { + ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); + qglLockArraysEXT = ( void ( APIENTRY * )( GLint, GLint ) ) SDL_GL_GetProcAddress( "glLockArraysEXT" ); + qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) SDL_GL_GetProcAddress( "glUnlockArraysEXT" ); + if (!qglLockArraysEXT || !qglUnlockArraysEXT) + { + ri.Error (ERR_FATAL, "bad getprocaddress"); + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); + } + + glConfig.textureFilterAnisotropic = qfalse; + if ( GLimp_HaveExtension( "GL_EXT_texture_filter_anisotropic" ) ) + { + if ( r_ext_texture_filter_anisotropic->integer ) { + qglGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint *)&glConfig.maxAnisotropy ); + if ( glConfig.maxAnisotropy <= 0 ) { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not properly supported!\n" ); + glConfig.maxAnisotropy = 0; + } + else + { + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_filter_anisotropic (max: %i)\n", glConfig.maxAnisotropy ); + glConfig.textureFilterAnisotropic = qtrue; + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_filter_anisotropic\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not found\n" ); + } +} + +/* +=============== +GLimp_Init + +This routine is responsible for initializing the OS specific portions +of OpenGL +=============== +*/ +void GLimp_Init(bool coreContext) +{ + ri.Printf( PRINT_DEVELOPER, "Glimp_Init( )\n" ); + + r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH ); + r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM ); + r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE | CVAR_LATCH ); + +#ifndef DEBUG + if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) ) + { + ri.Cvar_Set( "r_width", va( "%d", R_FAILSAFE_WIDTH ) ); + ri.Cvar_Set( "r_height", va( "%d", R_FAILSAFE_HEIGHT ) ); + ri.Cvar_Set( "r_fullscreen", "0" ); + ri.Cvar_Set( "r_centerWindow", "0" ); + ri.Cvar_Set( "com_abnormalExit", "0" ); + } +#endif + + ri.Sys_GLimpInit( ); + + // Create the window and set up the context + if( GLimp_StartDriverAndSetMode( false, r_fullscreen->integer, r_noborder->integer, coreContext ) ) + goto success; + + // Try again, this time in a platform specific "safe mode" + ri.Sys_GLimpSafeInit( ); + + if( GLimp_StartDriverAndSetMode( false, r_fullscreen->integer, false, coreContext ) ) + goto success; + + // Finally, try the default screen resolution + if( GLimp_StartDriverAndSetMode( true, r_fullscreen->integer, false, coreContext ) ) + goto success; + + // Nothing worked, give up + ri.Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem" ); + +success: + // These values force the UI to disable driver selection + glConfig.driverType = GLDRV_ICD; + glConfig.hardwareType = GLHW_GENERIC; + + // Only using SDL_SetWindowBrightness to determine if hardware gamma is supported + glConfig.deviceSupportsGamma = (qboolean)(!r_ignorehwgamma->integer && SDL_SetWindowBrightness( SDL_window, 1.0f ) >= 0); + + // get our config strings + Q_strncpyz( glConfig.vendor_string, (char *) qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) ); + Q_strncpyz( glConfig.renderer_string, (char *) qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) ); + if (*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n') + glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0; + Q_strncpyz( glConfig.version_string, (char *) qglGetString (GL_VERSION), sizeof( glConfig.version_string ) ); + if (qglGetString(GL_EXTENSIONS)) + Q_strncpyz( glConfig.extensions_string, (char *) qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); + else + Q_strncpyz( glConfig.extensions_string, "Not available (core context, fixme)", sizeof( glConfig.extensions_string ) ); + + // initialize extensions + GLimp_InitExtensions( ); + + ri.Cvar_Get( "r_availableModes", "", CVAR_ROM ); + + // This depends on SDL_INIT_VIDEO, hence having it here + ri.IN_Init( SDL_window ); +} + + +/* +=============== +GLimp_EndFrame + +Responsible for doing a swapbuffers +=============== +*/ +void GLimp_EndFrame( void ) +{ + // don't flip if drawing to front buffer + if ( Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) + { + SDL_GL_SwapWindow( SDL_window ); + } + + if( r_fullscreen->modified ) + { + int fullscreen; + bool needToToggle; + bool sdlToggled = false; + + // Find out the current state + fullscreen = !!( SDL_GetWindowFlags( SDL_window ) & SDL_WINDOW_FULLSCREEN ); + + if( r_fullscreen->integer && ri.Cvar_VariableIntegerValue( "in_nograb" ) ) + { + ri.Printf( PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n"); + ri.Cvar_Set( "r_fullscreen", "0" ); + r_fullscreen->modified = false; + } + + // Is the state we want different from the current state? + needToToggle = !!r_fullscreen->integer != fullscreen; + + if( needToToggle ) + { + sdlToggled = SDL_SetWindowFullscreen( SDL_window, r_fullscreen->integer ) >= 0; + + // SDL_WM_ToggleFullScreen didn't work, so do it the slow way + if( !sdlToggled ) + ri.Cmd_ExecuteText(EXEC_APPEND, "vid_restart\n"); + + ri.IN_Restart( ); + } + + r_fullscreen->modified = false; + } +} diff --git a/src/sdl/sdl_icon.h b/src/sdl/sdl_icon.h new file mode 100644 index 0000000..03f7420 --- /dev/null +++ b/src/sdl/sdl_icon.h @@ -0,0 +1,138 @@ +/* GIMP RGBA C-Source image dump (sdl_icon.h) */ + +static const struct { + unsigned int width; + unsigned int height; + unsigned int bytes_per_pixel; /* 3:RGB, 4:RGBA */ + unsigned char pixel_data[32 * 32 * 4 + 1]; +} CLIENT_WINDOW_ICON = { + 32, 32, 4, + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\17\17\34\17\17" + "\17U\17\17\17""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\30\30\30q\2\2\2\306\5\5\5\377\4\4\4\343\33\33\33" + "\252\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\31\31" + "\31\216\7\7\7\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\377\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0%%%9\0\0" + "\0\0\0\0\0\0\10\10\10q\16\16\16q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\11\11\11\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\13\13\13q\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14q\15\15\15\343\16\16\16" + """9\0\0\0\0\12\12\12\252\10\10\10\377\30\30\30U\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\3\3\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\252\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\12\12\12\216\3\3\3\377\2\2\2\252\0\0\0" + "\0\0\0\0\0\37\37\37""9\3\3\3\377\3\3\3\306\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\343\15\15\15""9\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14q\3\3\3\377\2\2\2\377\5\5\5""9\0\0\0" + "\0\0\0\0\0\0\0\0\0\16\16\16\306\2\2\2\377\2\2\2\306\5\5\5\34\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36\252\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\13\13\13\306\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\13\13\13\216\3\3\3\377\4\4\4\377\15\15\15U\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\17\17\34\14\14\14\306\1\1\1\377\2\2\2\306" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36\252\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "U\0\0\0\0\0\0\0\0(((9\16\16\16\252\3\3\3\377\0\0\0\377\16\16\16\306\36\36" + "\36\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\306\0\0\0\377" + "\3\3\3\306\3\3\3\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36q\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\6\6\6\343\31\31" + "\31""9\0\0\0\0\0\0\0\0\0\0\0\216\0\0\0\377\2\2\2\377\12\12\12\306\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\26\26U\7\7\7" + "\377\0\0\0\377\3\3\3\306\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\3\3\3\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\6\6\6\252\0" + "\0\0\0\15\15\15""9\6\6\6\252\0\0\0\377\0\0\0\377\12\12\12\306\16\16\16\34" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\6\6\6q\1\1\1\377\0\0\0\377\4\4\4\306\5\5\5\34\0\0\0\0\0\0\0\0\22\22\22\34" + "\32\32\32\306\1\1\1\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\21\21\21\216\33\33\33\306\3\3\3\377\0\0\0\377\1\1\1\377\11\11\11\306\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\3\3\3\216\1\1\1\377\1\1\1\377\4\4\4\306\0\0\0\0\0\0" + "\0\0\14\14\14U\4\4\4\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\4\4\4\306\3\3\3\377\0\0\0\377\0\0\0\377\11\11\11\306\15\15\15" + "\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\5\5\252\0\0\0\377\0\0\0\377\3\3\3\377" + "\5\5\5\377\3\3\3\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\1\1\1\343\12\12\12\252\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\16\16\16""9\7\7\7\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\2\2\2""9\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2q\3\3\3\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\20\20\20\216\5\5\5\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\306\0\0\0\252\0\0\0\252\0\0\0\252\0\0\0\252\0\0\0\252" + "\0\0\0\252\12\12\12\252\36\36\36""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\15\15\15U\6\6" + "\6\306\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\1\1\1\377\0\0\0\252\0\0\0\252" + "\0\0\0\252\0\0\0\252\12\12\12\252\36\36\36""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14""9\4\4\4\252" + "\4\4\4\377\1\1\1\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\216\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\31\31\31U\20\20\20\306\5\5\5\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\12\12\12\343\26" + "\26\26""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\377\0\0\0\377\5\5\5\377\5\5\5\377" + "\0\0\0U\6\6\6\306\1\1\1\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\24\24\24" + """9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\"\"\"\252###\252\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\21\21\21""9\7\7\7\343\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\20\20\20\306" + "\30\30\30\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\7\7\7\252\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\377\22\22" + "\22\306\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\23\23\23U\6\6\6\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\22\22\22\306\36\36\36\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\14\14\14\306\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\4\4\4\216\3\3\3\216\2\2" + "\2\377\5\5\5\377\17\17\17U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\2\2\2\377\4\4\4\377\1\1\1\252\1\1\1\252\4\4\4\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\11\11\11q\0\0\0\0\0\0\0\0\6\6\6q\2" + "\2\2\377\4\4\4\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14" + "\14q\0\0\0\377\6\6\6\216\0\0\0\0\0\0\0\0\15\15\15U\2\2\2\306\0\0\0\377\0" + "\0\0\377\1\1\1\306\7\7\7U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\5\5\216\1\1\1" + "\377\3\3\3q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\3\3\252\2\2\2\377\5" + "\5\5U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\31\31\31""9\31\31\31\252\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\26\26""9\13\13\13\343\5\5\5\377" + "\30\30\30\216\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2\252\6\6\6\377\21\21\21" + "U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\26\26""9\10\10\10\377\14\14\14\377\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\32\32\32U\11\11\11\377\0\0\0\252\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2""9\12\12\12\343\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\32\32\32U\11\11\11\377\33\33\33\252\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0%%%9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\32\32\32""9\12\12\12" + "\343\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\17\17""9\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0", +}; + diff --git a/src/sdl/sdl_input.cpp b/src/sdl/sdl_input.cpp new file mode 100644 index 0000000..1e1056a --- /dev/null +++ b/src/sdl/sdl_input.cpp @@ -0,0 +1,1336 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include +#include +#include + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include "client/client.h" +#include "sys/sys_local.h" + +static cvar_t *in_keyboardDebug = NULL; + +static SDL_GameController *gamepad = NULL; +static SDL_Joystick *stick = NULL; + +static bool mouseAvailable = false; +static bool mouseActive = false; + +static cvar_t *in_mouse = NULL; +static cvar_t *in_nograb; + +static cvar_t *in_joystick = NULL; +static cvar_t *in_joystickThreshold = NULL; +static cvar_t *in_joystickNo = NULL; +static cvar_t *in_joystickUseAnalog = NULL; + +static int vidRestartTime = 0; +static int in_eventTime = 0; + +static SDL_Window *SDL_window = NULL; + +#define CTRL(a) ((a)-'a'+1) + +/* +=============== +IN_PrintKey +=============== +*/ +static void IN_PrintKey( const SDL_Keysym *keysym, keyNum_t key, bool down ) +{ + if( down ) + Com_Printf( "+ " ); + else + Com_Printf( " " ); + + Com_Printf( "Scancode: 0x%02x(%s) Sym: 0x%02x(%s)", + keysym->scancode, SDL_GetScancodeName( keysym->scancode ), + keysym->sym, SDL_GetKeyName( keysym->sym ) ); + + if( keysym->mod & KMOD_LSHIFT ) Com_Printf( " KMOD_LSHIFT" ); + if( keysym->mod & KMOD_RSHIFT ) Com_Printf( " KMOD_RSHIFT" ); + if( keysym->mod & KMOD_LCTRL ) Com_Printf( " KMOD_LCTRL" ); + if( keysym->mod & KMOD_RCTRL ) Com_Printf( " KMOD_RCTRL" ); + if( keysym->mod & KMOD_LALT ) Com_Printf( " KMOD_LALT" ); + if( keysym->mod & KMOD_RALT ) Com_Printf( " KMOD_RALT" ); + if( keysym->mod & KMOD_LGUI ) Com_Printf( " KMOD_LGUI" ); + if( keysym->mod & KMOD_RGUI ) Com_Printf( " KMOD_RGUI" ); + if( keysym->mod & KMOD_NUM ) Com_Printf( " KMOD_NUM" ); + if( keysym->mod & KMOD_CAPS ) Com_Printf( " KMOD_CAPS" ); + if( keysym->mod & KMOD_MODE ) Com_Printf( " KMOD_MODE" ); + if( keysym->mod & KMOD_RESERVED ) Com_Printf( " KMOD_RESERVED" ); + + Com_Printf( " Q:0x%02x(%s)\n", key, Key_KeynumToString( key ) ); +} + +#define MAX_CONSOLE_KEYS 16 + +/* +=============== +IN_IsConsoleKey + +TODO: If the SDL_Scancode situation improves, use it instead of + both of these methods +=============== +*/ +static bool IN_IsConsoleKey( keyNum_t key, int character ) +{ + typedef struct consoleKey_s + { + enum + { + QUAKE_KEY, + CHARACTER + } type; + + union + { + keyNum_t key; + int character; + } u; + } consoleKey_t; + + static consoleKey_t consoleKeys[ MAX_CONSOLE_KEYS ]; + static int numConsoleKeys = 0; + int i; + + // Only parse the variable when it changes + if( cl_consoleKeys->modified ) + { + char *text_p, *token; + + cl_consoleKeys->modified = qfalse; + text_p = cl_consoleKeys->string; + numConsoleKeys = 0; + + while( numConsoleKeys < MAX_CONSOLE_KEYS ) + { + consoleKey_t *c = &consoleKeys[ numConsoleKeys ]; + int charCode = 0; + + token = COM_Parse( &text_p ); + if( !token[ 0 ] ) + break; + + if( strlen( token ) == 4 ) + charCode = Com_HexStrToInt( token ); + + if( charCode > 0 ) + { + c->type = consoleKey_s::CHARACTER; + c->u.character = charCode; + } + else + { + c->type = consoleKey_s::QUAKE_KEY; + c->u.key = (keyNum_t)Key_StringToKeynum( token ); + + // 0 isn't a key + if( c->u.key <= 0 ) + continue; + } + + numConsoleKeys++; + } + } + + // If the character is the same as the key, prefer the character + if( key == character ) + key = (keyNum_t)0; + + for( i = 0; i < numConsoleKeys; i++ ) + { + consoleKey_t *c = &consoleKeys[ i ]; + + switch( c->type ) + { + case consoleKey_s::QUAKE_KEY: + if( key && c->u.key == key ) + return true; + break; + + case consoleKey_s::CHARACTER: + if( c->u.character == character ) + return true; + break; + } + } + + return false; +} + +/* +=============== +IN_TranslateSDLToQ3Key +=============== +*/ +static keyNum_t IN_TranslateSDLToQ3Key( SDL_Keysym *keysym, bool down ) +{ + keyNum_t key = (keyNum_t)0; + + if( keysym->scancode >= SDL_SCANCODE_1 && keysym->scancode <= SDL_SCANCODE_0 ) + { + // Always map the number keys as such even if they actually map + // to other characters (eg, "1" is "&" on an AZERTY keyboard). + // This is required for SDL before 2.0.6, except on Windows + // which already had this behavior. + if( keysym->scancode == SDL_SCANCODE_0 ) + key = static_cast('0'); + else + key = static_cast('1' + keysym->scancode - SDL_SCANCODE_1); + } + else if( keysym->sym >= SDLK_SPACE && keysym->sym < SDLK_DELETE ) + { + // These happen to match the ASCII chars + key = (keyNum_t)keysym->sym; + } + else + { + switch( keysym->sym ) + { + case SDLK_PAGEUP: key = K_PGUP; break; + case SDLK_KP_9: key = K_KP_PGUP; break; + case SDLK_PAGEDOWN: key = K_PGDN; break; + case SDLK_KP_3: key = K_KP_PGDN; break; + case SDLK_KP_7: key = K_KP_HOME; break; + case SDLK_HOME: key = K_HOME; break; + case SDLK_KP_1: key = K_KP_END; break; + case SDLK_END: key = K_END; break; + case SDLK_KP_4: key = K_KP_LEFTARROW; break; + case SDLK_LEFT: key = K_LEFTARROW; break; + case SDLK_KP_6: key = K_KP_RIGHTARROW; break; + case SDLK_RIGHT: key = K_RIGHTARROW; break; + case SDLK_KP_2: key = K_KP_DOWNARROW; break; + case SDLK_DOWN: key = K_DOWNARROW; break; + case SDLK_KP_8: key = K_KP_UPARROW; break; + case SDLK_UP: key = K_UPARROW; break; + case SDLK_ESCAPE: key = K_ESCAPE; break; + case SDLK_KP_ENTER: key = K_KP_ENTER; break; + case SDLK_RETURN: key = K_ENTER; break; + case SDLK_TAB: key = K_TAB; break; + case SDLK_F1: key = K_F1; break; + case SDLK_F2: key = K_F2; break; + case SDLK_F3: key = K_F3; break; + case SDLK_F4: key = K_F4; break; + case SDLK_F5: key = K_F5; break; + case SDLK_F6: key = K_F6; break; + case SDLK_F7: key = K_F7; break; + case SDLK_F8: key = K_F8; break; + case SDLK_F9: key = K_F9; break; + case SDLK_F10: key = K_F10; break; + case SDLK_F11: key = K_F11; break; + case SDLK_F12: key = K_F12; break; + case SDLK_F13: key = K_F13; break; + case SDLK_F14: key = K_F14; break; + case SDLK_F15: key = K_F15; break; + + case SDLK_BACKSPACE: key = K_BACKSPACE; break; + case SDLK_KP_PERIOD: key = K_KP_DEL; break; + case SDLK_DELETE: key = K_DEL; break; + case SDLK_PAUSE: key = K_PAUSE; break; + + case SDLK_LSHIFT: + case SDLK_RSHIFT: key = K_SHIFT; break; + + case SDLK_LCTRL: + case SDLK_RCTRL: key = K_CTRL; break; + +#ifdef __APPLE__ + case SDLK_RGUI: + case SDLK_LGUI: key = K_COMMAND; break; +#else + case SDLK_RGUI: + case SDLK_LGUI: key = K_SUPER; break; +#endif + + case SDLK_RALT: + case SDLK_LALT: key = K_ALT; break; + + case SDLK_KP_5: key = K_KP_5; break; + case SDLK_INSERT: key = K_INS; break; + case SDLK_KP_0: key = K_KP_INS; break; + case SDLK_KP_MULTIPLY: key = K_KP_STAR; break; + case SDLK_KP_PLUS: key = K_KP_PLUS; break; + case SDLK_KP_MINUS: key = K_KP_MINUS; break; + case SDLK_KP_DIVIDE: key = K_KP_SLASH; break; + + case SDLK_MODE: key = K_MODE; break; + case SDLK_HELP: key = K_HELP; break; + case SDLK_PRINTSCREEN: key = K_PRINT; break; + case SDLK_SYSREQ: key = K_SYSREQ; break; + case SDLK_MENU: key = K_MENU; break; + case SDLK_APPLICATION: key = K_MENU; break; + case SDLK_POWER: key = K_POWER; break; + case SDLK_UNDO: key = K_UNDO; break; + case SDLK_SCROLLLOCK: key = K_SCROLLOCK; break; + case SDLK_NUMLOCKCLEAR: key = K_KP_NUMLOCK; break; + case SDLK_CAPSLOCK: key = K_CAPSLOCK; break; + + default: + if( !( keysym->sym & SDLK_SCANCODE_MASK ) && keysym->scancode <= 95 ) + { + // Map Unicode characters to 95 world keys using the key's scan code. + // FIXME: There aren't enough world keys to cover all the scancodes. + // Maybe create a map of scancode to quake key at start up and on + // key map change; allocate world key numbers as needed similar + // to SDL 1.2. + key = static_cast(K_WORLD_0 + (int)keysym->scancode); + } + break; + } + } + + if( in_keyboardDebug->integer ) + IN_PrintKey( keysym, key, down ); + + if( IN_IsConsoleKey( key, 0 ) ) + { + // Console keys can't be bound or generate characters + key = K_CONSOLE; + } + + return key; +} + +/* +=============== +IN_GobbleMotionEvents +=============== +*/ +static void IN_GobbleMotionEvents( void ) +{ + SDL_Event dummy[ 1 ]; + int val = 0; + + // Gobble any mouse motion events + SDL_PumpEvents( ); + while( ( val = SDL_PeepEvents( dummy, 1, SDL_GETEVENT, + SDL_MOUSEMOTION, SDL_MOUSEMOTION ) ) > 0 ) { } + + if ( val < 0 ) + Com_Printf( "IN_GobbleMotionEvents failed: %s\n", SDL_GetError( ) ); +} + +/* +=============== +IN_GetUIMousePosition +=============== +*/ +static void IN_GetUIMousePosition( int *x, int *y ) +{ + if( cls.ui ) + { + int pos = VM_Call( cls.ui, UI_MOUSE_POSITION ); + *x = pos & 0xFFFF; + *y = ( pos >> 16 ) & 0xFFFF; + + *x = cls.glconfig.vidWidth * *x / 640; + *y = cls.glconfig.vidHeight * *y / 480; + } + else + { + *x = cls.glconfig.vidWidth / 2; + *y = cls.glconfig.vidHeight / 2; + } +} + +/* +=============== +IN_SetUIMousePosition +=============== +*/ +static void IN_SetUIMousePosition( int x, int y ) +{ + if( cls.ui ) + { + x = x * 640 / cls.glconfig.vidWidth; + y = y * 480 / cls.glconfig.vidHeight; + VM_Call( cls.ui, UI_SET_MOUSE_POSITION, x, y ); + } +} + +/* +=============== +IN_ActivateMouse +=============== +*/ +static void IN_ActivateMouse( void ) +{ + if (!mouseAvailable || !SDL_WasInit( SDL_INIT_VIDEO ) ) + return; + + if( !mouseActive ) + { + SDL_SetRelativeMouseMode( SDL_TRUE ); + SDL_SetWindowGrab( SDL_window, SDL_TRUE ); + + IN_GobbleMotionEvents( ); + } + + // in_nograb makes no sense in fullscreen mode + if( !cls.glconfig.isFullscreen ) + { + if( in_nograb->modified || !mouseActive ) + { + if( in_nograb->integer ) + { + SDL_SetRelativeMouseMode( SDL_FALSE ); + SDL_SetWindowGrab( SDL_window, SDL_FALSE ); + } + else + { + SDL_SetRelativeMouseMode( SDL_TRUE ); + SDL_SetWindowGrab( SDL_window, SDL_TRUE ); + } + in_nograb->modified = qfalse; + } + } + + mouseActive = true; +} + +/* +=============== +IN_DeactivateMouse +=============== +*/ +static void IN_DeactivateMouse( void ) +{ + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) + return; + + // Always show the cursor when the mouse is disabled, + // but not when fullscreen + if( !cls.glconfig.isFullscreen ) + { + if( ( Key_GetCatcher( ) == KEYCATCH_UI ) && + SDL_GetWindowFlags( SDL_window ) & SDL_WINDOW_MOUSE_FOCUS ) + SDL_ShowCursor( 0 ); + else + SDL_ShowCursor( 1 ); + } + + if( !mouseAvailable ) + return; + + if( mouseActive ) + { + IN_GobbleMotionEvents( ); + + SDL_SetWindowGrab( SDL_window, SDL_FALSE ); + SDL_SetRelativeMouseMode( SDL_FALSE ); + + // Don't warp the mouse unless the cursor is within the window + if( SDL_GetWindowFlags( SDL_window ) & SDL_WINDOW_MOUSE_FOCUS && cls.uiInterface != 2 ) + { + int x, y; + IN_GetUIMousePosition( &x, &y ); + SDL_WarpMouseInWindow( SDL_window, x, y ); + } + + mouseActive = false; + } +} + +// We translate axes movement into keypresses +static int joy_keys[16] = { + K_LEFTARROW, K_RIGHTARROW, + K_UPARROW, K_DOWNARROW, + K_JOY17, K_JOY18, + K_JOY19, K_JOY20, + K_JOY21, K_JOY22, + K_JOY23, K_JOY24, + K_JOY25, K_JOY26, + K_JOY27, K_JOY28 +}; + +// translate hat events into keypresses +// the 4 highest buttons are used for the first hat ... +static int hat_keys[16] = { + K_JOY29, K_JOY30, + K_JOY31, K_JOY32, + K_JOY25, K_JOY26, + K_JOY27, K_JOY28, + K_JOY21, K_JOY22, + K_JOY23, K_JOY24, + K_JOY17, K_JOY18, + K_JOY19, K_JOY20 +}; + + +struct +{ + bool buttons[SDL_CONTROLLER_BUTTON_MAX + 1]; // +1 because old max was 16, current SDL_CONTROLLER_BUTTON_MAX is 15 + unsigned int oldaxes; + int oldaaxes[MAX_JOYSTICK_AXIS]; + unsigned int oldhats; +} stick_state; + + +/* +=============== +IN_InitJoystick +=============== +*/ +static void IN_InitJoystick( void ) +{ + int i = 0; + int total = 0; + char buf[16384] = ""; + + if (gamepad) + SDL_GameControllerClose(gamepad); + + if (stick != NULL) + SDL_JoystickClose(stick); + + stick = NULL; + gamepad = NULL; + memset(&stick_state, '\0', sizeof (stick_state)); + + // SDL 2.0.4 requires SDL_INIT_JOYSTICK to be initialized separately from + // SDL_INIT_GAMECONTROLLER for SDL_JoystickOpen() to work correctly, + // despite https://wiki.libsdl.org/SDL_Init (retrieved 2016-08-16) + // indicating SDL_INIT_JOYSTICK should be initialized automatically. + if (!SDL_WasInit(SDL_INIT_JOYSTICK)) + { + Com_DPrintf("Calling SDL_Init(SDL_INIT_JOYSTICK)...\n"); + if (SDL_Init(SDL_INIT_JOYSTICK) != 0) + { + Com_DPrintf("SDL_Init(SDL_INIT_JOYSTICK) failed: %s\n", SDL_GetError()); + return; + } + Com_DPrintf("SDL_Init(SDL_INIT_JOYSTICK) passed.\n"); + } + + if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER)) + { + Com_DPrintf("Calling SDL_Init(SDL_INIT_GAMECONTROLLER)...\n"); + if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) + { + Com_DPrintf("SDL_Init(SDL_INIT_GAMECONTROLLER) failed: %s\n", SDL_GetError()); + return; + } + Com_DPrintf("SDL_Init(SDL_INIT_GAMECONTROLLER) passed.\n"); + } + + total = SDL_NumJoysticks(); + Com_DPrintf("%d possible joysticks\n", total); + + // Print list and build cvar to allow ui to select joystick. + for (i = 0; i < total; i++) + { + Q_strcat(buf, sizeof(buf), SDL_JoystickNameForIndex(i)); + Q_strcat(buf, sizeof(buf), "\n"); + } + + Cvar_Get( "in_availableJoysticks", buf, CVAR_ROM ); + + if( !in_joystick->integer ) { + Com_DPrintf( "Joystick is not active.\n" ); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + return; + } + + in_joystickNo = Cvar_Get( "in_joystickNo", "0", CVAR_ARCHIVE ); + if( in_joystickNo->integer < 0 || in_joystickNo->integer >= total ) + Cvar_Set( "in_joystickNo", "0" ); + + in_joystickUseAnalog = Cvar_Get( "in_joystickUseAnalog", "0", CVAR_ARCHIVE ); + + stick = SDL_JoystickOpen( in_joystickNo->integer ); + + if (stick == NULL) { + Com_DPrintf( "No joystick opened: %s\n", SDL_GetError() ); + return; + } + + if (SDL_IsGameController(in_joystickNo->integer)) + gamepad = SDL_GameControllerOpen(in_joystickNo->integer); + + Com_DPrintf( "Joystick %d opened\n", in_joystickNo->integer ); + Com_DPrintf( "Name: %s\n", SDL_JoystickNameForIndex(in_joystickNo->integer) ); + Com_DPrintf( "Axes: %d\n", SDL_JoystickNumAxes(stick) ); + Com_DPrintf( "Hats: %d\n", SDL_JoystickNumHats(stick) ); + Com_DPrintf( "Buttons: %d\n", SDL_JoystickNumButtons(stick) ); + Com_DPrintf( "Balls: %d\n", SDL_JoystickNumBalls(stick) ); + Com_DPrintf( "Use Analog: %s\n", in_joystickUseAnalog->integer ? "Yes" : "No" ); + Com_DPrintf( "Is gamepad: %s\n", gamepad ? "Yes" : "No" ); + + SDL_JoystickEventState(SDL_QUERY); + SDL_GameControllerEventState(SDL_QUERY); +} + +/* +=============== +IN_ShutdownJoystick +=============== +*/ +static void IN_ShutdownJoystick( void ) +{ + if ( !SDL_WasInit( SDL_INIT_GAMECONTROLLER ) ) + return; + + if ( !SDL_WasInit( SDL_INIT_JOYSTICK ) ) + return; + + if (gamepad) + { + SDL_GameControllerClose(gamepad); + gamepad = NULL; + } + + if (stick) + { + SDL_JoystickClose(stick); + stick = NULL; + } + + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +} + + +static bool KeyToAxisAndSign(int keynum, int *outAxis, int *outSign) +{ + if (!keynum) + return false; + + const char* bind = Key_GetBinding(keynum); + + if (!bind || *bind != '+') + return false; + + *outSign = 0; + + if (Q_stricmp(bind, "+forward") == 0) + { + *outAxis = j_forward_axis->integer; + *outSign = j_forward->value > 0.0f ? 1 : -1; + } + else if (Q_stricmp(bind, "+back") == 0) + { + *outAxis = j_forward_axis->integer; + *outSign = j_forward->value > 0.0f ? -1 : 1; + } + else if (Q_stricmp(bind, "+moveleft") == 0) + { + *outAxis = j_side_axis->integer; + *outSign = j_side->value > 0.0f ? -1 : 1; + } + else if (Q_stricmp(bind, "+moveright") == 0) + { + *outAxis = j_side_axis->integer; + *outSign = j_side->value > 0.0f ? 1 : -1; + } + else if (Q_stricmp(bind, "+lookup") == 0) + { + *outAxis = j_pitch_axis->integer; + *outSign = j_pitch->value > 0.0f ? -1 : 1; + } + else if (Q_stricmp(bind, "+lookdown") == 0) + { + *outAxis = j_pitch_axis->integer; + *outSign = j_pitch->value > 0.0f ? 1 : -1; + } + else if (Q_stricmp(bind, "+left") == 0) + { + *outAxis = j_yaw_axis->integer; + *outSign = j_yaw->value > 0.0f ? 1 : -1; + } + else if (Q_stricmp(bind, "+right") == 0) + { + *outAxis = j_yaw_axis->integer; + *outSign = j_yaw->value > 0.0f ? -1 : 1; + } + else if (Q_stricmp(bind, "+moveup") == 0) + { + *outAxis = j_up_axis->integer; + *outSign = j_up->value > 0.0f ? 1 : -1; + } + else if (Q_stricmp(bind, "+movedown") == 0) + { + *outAxis = j_up_axis->integer; + *outSign = j_up->value > 0.0f ? -1 : 1; + } + + return *outSign != 0; +} + +/* +=============== +IN_GamepadMove +=============== +*/ +static void IN_GamepadMove( void ) +{ + int i; + int translatedAxes[MAX_JOYSTICK_AXIS]; + bool translatedAxesSet[MAX_JOYSTICK_AXIS]; + + SDL_GameControllerUpdate(); + + // check buttons + for (i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) + { + bool pressed = SDL_GameControllerGetButton(gamepad, (SDL_GameControllerButton)(SDL_CONTROLLER_BUTTON_A + i)); + if (pressed != stick_state.buttons[i]) + { + Com_QueueEvent(in_eventTime, SE_KEY, K_PAD0_A + i, pressed, 0, NULL); + stick_state.buttons[i] = pressed; + } + } + + // must defer translated axes until all real axes are processed + // must be done this way to prevent a later mapped axis from zeroing out a previous one + if (in_joystickUseAnalog->integer) + { + for (i = 0; i < MAX_JOYSTICK_AXIS; i++) + { + translatedAxes[i] = 0; + translatedAxesSet[i] = false; + } + } + + // check axes + for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) + { + int axis = SDL_GameControllerGetAxis(gamepad, (SDL_GameControllerAxis)(SDL_CONTROLLER_AXIS_LEFTX + i)); + int oldAxis = stick_state.oldaaxes[i]; + + // Smoothly ramp from dead zone to maximum value + float f = ((float)abs(axis) / 32767.0f - in_joystickThreshold->value) / (1.0f - in_joystickThreshold->value); + + if (f < 0.0f) + f = 0.0f; + + axis = (int)(32767 * ((axis < 0) ? -f : f)); + + if (axis != oldAxis) + { + const int negMap[SDL_CONTROLLER_AXIS_MAX] = { K_PAD0_LEFTSTICK_LEFT, K_PAD0_LEFTSTICK_UP, K_PAD0_RIGHTSTICK_LEFT, K_PAD0_RIGHTSTICK_UP, 0, 0 }; + const int posMap[SDL_CONTROLLER_AXIS_MAX] = { K_PAD0_LEFTSTICK_RIGHT, K_PAD0_LEFTSTICK_DOWN, K_PAD0_RIGHTSTICK_RIGHT, K_PAD0_RIGHTSTICK_DOWN, K_PAD0_LEFTTRIGGER, K_PAD0_RIGHTTRIGGER }; + + bool posAnalog = false, negAnalog = false; + int negKey = negMap[i]; + int posKey = posMap[i]; + + if (in_joystickUseAnalog->integer) + { + int posAxis = 0, posSign = 0, negAxis = 0, negSign = 0; + + // get axes and axes signs for keys if available + posAnalog = KeyToAxisAndSign(posKey, &posAxis, &posSign); + negAnalog = KeyToAxisAndSign(negKey, &negAxis, &negSign); + + // positive to negative/neutral -> keyup if axis hasn't yet been set + if (posAnalog && !translatedAxesSet[posAxis] && oldAxis > 0 && axis <= 0) + { + translatedAxes[posAxis] = 0; + translatedAxesSet[posAxis] = true; + } + + // negative to positive/neutral -> keyup if axis hasn't yet been set + if (negAnalog && !translatedAxesSet[negAxis] && oldAxis < 0 && axis >= 0) + { + translatedAxes[negAxis] = 0; + translatedAxesSet[negAxis] = true; + } + + // negative/neutral to positive -> keydown + if (posAnalog && axis > 0) + { + translatedAxes[posAxis] = axis * posSign; + translatedAxesSet[posAxis] = true; + } + + // positive/neutral to negative -> keydown + if (negAnalog && axis < 0) + { + translatedAxes[negAxis] = -axis * negSign; + translatedAxesSet[negAxis] = true; + } + } + + // keyups first so they get overridden by keydowns later + + // positive to negative/neutral -> keyup + if (!posAnalog && posKey && oldAxis > 0 && axis <= 0) + Com_QueueEvent(in_eventTime, SE_KEY, posKey, false, 0, NULL); + + // negative to positive/neutral -> keyup + if (!negAnalog && negKey && oldAxis < 0 && axis >= 0) + Com_QueueEvent(in_eventTime, SE_KEY, negKey, false, 0, NULL); + + // negative/neutral to positive -> keydown + if (!posAnalog && posKey && oldAxis <= 0 && axis > 0) + Com_QueueEvent(in_eventTime, SE_KEY, posKey, true, 0, NULL); + + // positive/neutral to negative -> keydown + if (!negAnalog && negKey && oldAxis >= 0 && axis < 0) + Com_QueueEvent(in_eventTime, SE_KEY, negKey, true, 0, NULL); + + stick_state.oldaaxes[i] = axis; + } + } + + // set translated axes + if (in_joystickUseAnalog->integer) + { + for (i = 0; i < MAX_JOYSTICK_AXIS; i++) + { + if (translatedAxesSet[i]) + Com_QueueEvent(in_eventTime, SE_JOYSTICK_AXIS, i, translatedAxes[i], 0, NULL); + } + } +} + + +/* +=============== +IN_JoyMove +=============== +*/ +static void IN_JoyMove( void ) +{ + unsigned int axes = 0; + unsigned int hats = 0; + int total = 0; + int i = 0; + + if (gamepad) + { + IN_GamepadMove(); + return; + } + + if (!stick) + return; + + SDL_JoystickUpdate(); + + // update the ball state. + total = SDL_JoystickNumBalls(stick); + if (total > 0) + { + int balldx = 0; + int balldy = 0; + for (i = 0; i < total; i++) + { + int dx = 0; + int dy = 0; + SDL_JoystickGetBall(stick, i, &dx, &dy); + balldx += dx; + balldy += dy; + } + if (balldx || balldy) + { + // !!! FIXME: is this good for stick balls, or just mice? + // Scale like the mouse input... + if (abs(balldx) > 1) + balldx *= 2; + if (abs(balldy) > 1) + balldy *= 2; + Com_QueueEvent(in_eventTime, SE_MOUSE, balldx, balldy, 0, NULL); + } + } + + // now query the stick buttons... + total = SDL_JoystickNumButtons(stick); + if (total > 0) + { + if (total > ARRAY_LEN(stick_state.buttons)) + total = ARRAY_LEN(stick_state.buttons); + for (i = 0; i < total; i++) + { + bool pressed = (SDL_JoystickGetButton(stick, i) != 0); + if (pressed != stick_state.buttons[i]) + { + Com_QueueEvent(in_eventTime, SE_KEY, K_JOY1 + i, pressed, 0, NULL ); + stick_state.buttons[i] = pressed; + } + } + } + + // look at the hats... + total = SDL_JoystickNumHats(stick); + if (total > 0) + { + if (total > 4) total = 4; + for (i = 0; i < total; i++) + { + ((Uint8 *)&hats)[i] = SDL_JoystickGetHat(stick, i); + } + } + + // update hat state + if (hats != stick_state.oldhats) + { + for( i = 0; i < 4; i++ ) { + if( ((Uint8 *)&hats)[i] != ((Uint8 *)&stick_state.oldhats)[i] ) { + // release event + switch( ((Uint8 *)&stick_state.oldhats)[i] ) { + case SDL_HAT_UP: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 0], false, 0, NULL ); + break; + case SDL_HAT_RIGHT: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 1], false, 0, NULL ); + break; + case SDL_HAT_DOWN: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 2], false, 0, NULL ); + break; + case SDL_HAT_LEFT: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 3], false, 0, NULL ); + break; + case SDL_HAT_RIGHTUP: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 0], false, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 1], false, 0, NULL ); + break; + case SDL_HAT_RIGHTDOWN: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 2], false, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 1], false, 0, NULL ); + break; + case SDL_HAT_LEFTUP: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 0], false, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 3], false, 0, NULL ); + break; + case SDL_HAT_LEFTDOWN: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 2], false, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 3], false, 0, NULL ); + break; + default: + break; + } + // press event + switch( ((Uint8 *)&hats)[i] ) { + case SDL_HAT_UP: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 0], true, 0, NULL ); + break; + case SDL_HAT_RIGHT: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 1], true, 0, NULL ); + break; + case SDL_HAT_DOWN: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 2], true, 0, NULL ); + break; + case SDL_HAT_LEFT: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 3], true, 0, NULL ); + break; + case SDL_HAT_RIGHTUP: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 0], true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 1], true, 0, NULL ); + break; + case SDL_HAT_RIGHTDOWN: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 2], true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 1], true, 0, NULL ); + break; + case SDL_HAT_LEFTUP: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 0], true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 3], true, 0, NULL ); + break; + case SDL_HAT_LEFTDOWN: + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 2], true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, hat_keys[4*i + 3], true, 0, NULL ); + break; + default: + break; + } + } + } + } + + // save hat state + stick_state.oldhats = hats; + + // finally, look at the axes... + total = SDL_JoystickNumAxes(stick); + if (total > 0) + { + if (in_joystickUseAnalog->integer) + { + if (total > MAX_JOYSTICK_AXIS) total = MAX_JOYSTICK_AXIS; + for (i = 0; i < total; i++) + { + Sint16 axis = SDL_JoystickGetAxis(stick, i); + float f = ( (float) abs(axis) ) / 32767.0f; + + if( f < in_joystickThreshold->value ) axis = 0; + + if ( axis != stick_state.oldaaxes[i] ) + { + Com_QueueEvent(in_eventTime, SE_JOYSTICK_AXIS, i, axis, 0, NULL ); + stick_state.oldaaxes[i] = axis; + } + } + } + else + { + if (total > 16) total = 16; + for (i = 0; i < total; i++) + { + Sint16 axis = SDL_JoystickGetAxis(stick, i); + float f = ( (float) axis ) / 32767.0f; + if( f < -in_joystickThreshold->value ) { + axes |= ( 1 << ( i * 2 ) ); + } else if( f > in_joystickThreshold->value ) { + axes |= ( 1 << ( ( i * 2 ) + 1 ) ); + } + } + } + } + + /* Time to update axes state based on old vs. new. */ + if (axes != stick_state.oldaxes) + { + for( i = 0; i < 16; i++ ) { + if( ( axes & ( 1 << i ) ) && !( stick_state.oldaxes & ( 1 << i ) ) ) { + Com_QueueEvent(in_eventTime, SE_KEY, joy_keys[i], true, 0, NULL ); + } + + if( !( axes & ( 1 << i ) ) && ( stick_state.oldaxes & ( 1 << i ) ) ) { + Com_QueueEvent(in_eventTime, SE_KEY, joy_keys[i], false, 0, NULL ); + } + } + } + + /* Save for future generations. */ + stick_state.oldaxes = axes; +} + +/* +=============== +IN_ProcessEvents +=============== +*/ +static void IN_ProcessEvents( void ) +{ + SDL_Event e; + keyNum_t key = (keyNum_t)0; + static keyNum_t lastKeyDown = (keyNum_t)0; + + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) + return; + + while( SDL_PollEvent( &e ) ) + { + switch( e.type ) + { + case SDL_KEYDOWN: + if ( e.key.repeat && Key_GetCatcher( ) == 0 ) + break; + + if( ( key = IN_TranslateSDLToQ3Key( &e.key.keysym, true ) ) ) + Com_QueueEvent(in_eventTime, SE_KEY, key, true, 0, NULL ); + + if( key == K_BACKSPACE ) + Com_QueueEvent(in_eventTime, SE_CHAR, CTRL('h'), 0, 0, NULL ); + else if( keys[K_CTRL].down && key >= 'a' && key <= 'z' ) + Com_QueueEvent(in_eventTime, SE_CHAR, CTRL(key), 0, 0, NULL ); + + lastKeyDown = key; + break; + + case SDL_KEYUP: + if( ( key = IN_TranslateSDLToQ3Key( &e.key.keysym, false ) ) ) + Com_QueueEvent(in_eventTime, SE_KEY, key, false, 0, NULL ); + + lastKeyDown = (keyNum_t)0; + break; + + case SDL_TEXTINPUT: + if( lastKeyDown != K_CONSOLE ) + { + char *c = e.text.text; + + // Quick and dirty UTF-8 to UTF-32 conversion + while( *c ) + { + int utf32 = 0; + + if( ( *c & 0x80 ) == 0 ) + utf32 = *c++; + else if( ( *c & 0xE0 ) == 0xC0 ) // 110x xxxx + { + utf32 |= ( *c++ & 0x1F ) << 6; + utf32 |= ( *c++ & 0x3F ); + } + else if( ( *c & 0xF0 ) == 0xE0 ) // 1110 xxxx + { + utf32 |= ( *c++ & 0x0F ) << 12; + utf32 |= ( *c++ & 0x3F ) << 6; + utf32 |= ( *c++ & 0x3F ); + } + else if( ( *c & 0xF8 ) == 0xF0 ) // 1111 0xxx + { + utf32 |= ( *c++ & 0x07 ) << 18; + utf32 |= ( *c++ & 0x3F ) << 12; + utf32 |= ( *c++ & 0x3F ) << 6; + utf32 |= ( *c++ & 0x3F ); + } + else + { + Com_DPrintf( "Unrecognised UTF-8 lead byte: 0x%x\n", (unsigned int)*c ); + c++; + } + + if( utf32 != 0 ) + { + if( IN_IsConsoleKey( (keyNum_t)0, utf32 ) ) + { + Com_QueueEvent(in_eventTime, SE_KEY, K_CONSOLE, true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, K_CONSOLE, false, 0, NULL ); + } + else + Com_QueueEvent(in_eventTime, SE_CHAR, utf32, 0, 0, NULL ); + } + } + } + break; + + case SDL_MOUSEMOTION: + if( mouseActive ) + { + if( !e.motion.xrel && !e.motion.yrel ) + break; + Com_QueueEvent(in_eventTime, SE_MOUSE, e.motion.xrel, e.motion.yrel, 0, NULL ); + } + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + int b; + switch( e.button.button ) + { + case 1: b = K_MOUSE1; break; + case 2: b = K_MOUSE3; break; + case 3: b = K_MOUSE2; break; + case 4: b = K_MOUSE4; break; + case 5: b = K_MOUSE5; break; + default: b = K_AUX1 + ( e.button.button - 8 ) % 16; break; + } + Com_QueueEvent(in_eventTime, SE_KEY, b, + ( e.type == SDL_MOUSEBUTTONDOWN ? true : false ), 0, NULL ); + } + break; + + case SDL_MOUSEWHEEL: + if( e.wheel.y > 0 ) + { + Com_QueueEvent(in_eventTime, SE_KEY, K_MWHEELUP, true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, K_MWHEELUP, false, 0, NULL ); + } + else if( e.wheel.y < 0 ) + { + Com_QueueEvent(in_eventTime, SE_KEY, K_MWHEELDOWN, true, 0, NULL ); + Com_QueueEvent(in_eventTime, SE_KEY, K_MWHEELDOWN, false, 0, NULL ); + } + break; + + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + if (in_joystick->integer) + IN_InitJoystick(); + break; + + case SDL_QUIT: + Cbuf_ExecuteText(EXEC_NOW, "quit \"Closed window\"\n"); + break; + + case SDL_WINDOWEVENT: + switch( e.window.event ) + { + case SDL_WINDOWEVENT_RESIZED: + { + int width, height; + + width = e.window.data1; + height = e.window.data2; + + // ignore this event on fullscreen + if ( cls.glconfig.isFullscreen ) + break; + + // check if size actually changed + if( cls.glconfig.vidWidth == width && cls.glconfig.vidHeight == height ) + { + break; + } + + Cvar_SetValue( "r_width", width ); + Cvar_SetValue( "r_height", height ); + + // Wait until user stops dragging for 1 second, so + // we aren't constantly recreating the GL context while + // he tries to drag... + vidRestartTime = Sys_Milliseconds( ) + 1000; + } + break; + + case SDL_WINDOWEVENT_MINIMIZED: Cvar_SetValue( "com_minimized", 1 ); break; + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_MAXIMIZED: Cvar_SetValue( "com_minimized", 0 ); break; + case SDL_WINDOWEVENT_FOCUS_LOST: Cvar_SetValue( "com_unfocused", 1 ); break; + case SDL_WINDOWEVENT_FOCUS_GAINED: Cvar_SetValue( "com_unfocused", 0 ); break; + } + break; + + default: + break; + } + } +} + +/* +=============== +IN_Frame +=============== +*/ +void IN_Frame( void ) +{ + bool loading; + bool cursorShowing; + int x, y; + + IN_JoyMove( ); + + // If not DISCONNECTED (main menu) or ACTIVE (in game), we're loading + loading = ( clc.state != CA_DISCONNECTED && clc.state != CA_ACTIVE ); + cursorShowing = Key_GetCatcher( ) & KEYCATCH_UI; + + if( !cls.glconfig.isFullscreen && ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) + { + // Console is down in windowed mode + IN_DeactivateMouse( ); + } + else if( !cls.glconfig.isFullscreen && loading ) + { + // Loading in windowed mode + IN_DeactivateMouse( ); + } + else if( !cls.glconfig.isFullscreen && cursorShowing && cls.uiInterface != 2 ) + { + // Use WM cursor when not fullscreen + IN_DeactivateMouse( ); + } + else if( !( SDL_GetWindowFlags( SDL_window ) & SDL_WINDOW_INPUT_FOCUS ) ) + { + // Window not got focus + IN_DeactivateMouse( ); + } + else + IN_ActivateMouse( ); + + if( !mouseActive && cls.uiInterface != 2 ) + { + SDL_GetMouseState( &x, &y ); + IN_SetUIMousePosition( x, y ); + } + + IN_ProcessEvents( ); + + // Set event time for next frame to earliest possible time an event could happen + in_eventTime = Sys_Milliseconds(); + + // In case we had to delay actual restart of video system + if( ( vidRestartTime != 0 ) && ( vidRestartTime < Sys_Milliseconds( ) ) ) + { + vidRestartTime = 0; + Cbuf_AddText( "vid_restart\n" ); + } +} + +/* +=============== +IN_Init +=============== +*/ +void IN_Init( void *windowData ) +{ + int appState; + + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) + { + Com_Error( ERR_FATAL, "IN_Init called before SDL_Init( SDL_INIT_VIDEO )" ); + return; + } + + SDL_window = (SDL_Window *)windowData; + + Com_DPrintf( "\n------- Input Initialization -------\n" ); + + in_keyboardDebug = Cvar_Get( "in_keyboardDebug", "0", CVAR_ARCHIVE ); + + // mouse variables + in_mouse = Cvar_Get( "in_mouse", "1", CVAR_ARCHIVE ); + in_nograb = Cvar_Get( "in_nograb", "0", CVAR_ARCHIVE ); + + in_joystick = Cvar_Get( "in_joystick", "0", CVAR_ARCHIVE|CVAR_LATCH ); + in_joystickThreshold = Cvar_Get( "joy_threshold", "0.15", CVAR_ARCHIVE ); + + SDL_StartTextInput( ); + + mouseAvailable = ( in_mouse->value != 0 ); + IN_DeactivateMouse( ); + + appState = SDL_GetWindowFlags( SDL_window ); + Cvar_SetValue( "com_unfocused", !( appState & SDL_WINDOW_INPUT_FOCUS ) ); + Cvar_SetValue( "com_minimized", appState & SDL_WINDOW_MINIMIZED ); + + IN_InitJoystick( ); + Com_DPrintf( "------------------------------------\n" ); +} + +/* +=============== +IN_Shutdown +=============== +*/ +void IN_Shutdown( void ) +{ + SDL_StopTextInput( ); + + IN_DeactivateMouse( ); + mouseAvailable = false; + + IN_ShutdownJoystick( ); + + SDL_window = NULL; +} + +/* +=============== +IN_Restart +=============== +*/ +void IN_Restart( void ) +{ + IN_ShutdownJoystick( ); + IN_Init( SDL_window ); +} diff --git a/src/sdl/sdl_snd.cpp b/src/sdl/sdl_snd.cpp new file mode 100644 index 0000000..4689cc5 --- /dev/null +++ b/src/sdl/sdl_snd.cpp @@ -0,0 +1,298 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include +#include + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include "client/snd_local.h" +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" + +bool snd_inited = false; + +cvar_t *s_sdlBits; +cvar_t *s_sdlSpeed; +cvar_t *s_sdlChannels; +cvar_t *s_sdlDevSamps; +cvar_t *s_sdlMixSamps; + +/* The audio callback. All the magic happens here. */ +static int dmapos = 0; +static int dmasize = 0; + +/* +=============== +SNDDMA_AudioCallback +=============== +*/ +static void SNDDMA_AudioCallback(void *userdata, Uint8 *stream, int len) +{ + int pos = (dmapos * (dma.samplebits/8)); + if (pos >= dmasize) + dmapos = pos = 0; + + if (!snd_inited) /* shouldn't happen, but just in case... */ + { + memset(stream, '\0', len); + return; + } + else + { + int tobufend = dmasize - pos; /* bytes to buffer's end. */ + int len1 = len; + int len2 = 0; + + if (len1 > tobufend) + { + len1 = tobufend; + len2 = len - len1; + } + memcpy(stream, dma.buffer + pos, len1); + if (len2 <= 0) + dmapos += (len1 / (dma.samplebits/8)); + else /* wraparound? */ + { + memcpy(stream+len1, dma.buffer, len2); + dmapos = (len2 / (dma.samplebits/8)); + } + } + + if (dmapos >= dmasize) + dmapos = 0; +} + +static struct +{ + Uint16 enumFormat; + const char* stringFormat; +} formatToStringTable[ ] = +{ + { AUDIO_U8, "AUDIO_U8" }, + { AUDIO_S8, "AUDIO_S8" }, + { AUDIO_U16LSB, "AUDIO_U16LSB" }, + { AUDIO_S16LSB, "AUDIO_S16LSB" }, + { AUDIO_U16MSB, "AUDIO_U16MSB" }, + { AUDIO_S16MSB, "AUDIO_S16MSB" } +}; + +static int formatToStringTableSize = ARRAY_LEN( formatToStringTable ); + +/* +=============== +SNDDMA_PrintAudiospec +=============== +*/ +static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec) +{ + int i; + const char *fmt = NULL; + + Com_Printf("%s:\n", str); + + for( i = 0; i < formatToStringTableSize; i++ ) { + if( spec->format == formatToStringTable[ i ].enumFormat ) { + fmt = formatToStringTable[ i ].stringFormat; + } + } + + if( fmt ) { + Com_Printf( " Format: %s\n", fmt ); + } else { + Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n"); + } + + Com_Printf( " Freq: %d\n", (int) spec->freq ); + Com_Printf( " Samples: %d\n", (int) spec->samples ); + Com_Printf( " Channels: %d\n", (int) spec->channels ); +} + +/* +=============== +SNDDMA_Init +=============== +*/ +bool SNDDMA_Init(void) +{ + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + int tmp; + + if (snd_inited) + return true; + + if (!s_sdlBits) { + s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE); + s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE); + s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE); + s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE); + s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE); + } + + Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " ); + + if (!SDL_WasInit(SDL_INIT_AUDIO)) + { + if (SDL_Init(SDL_INIT_AUDIO) != 0) + { + Com_Printf( "FAILED (%s)\n", SDL_GetError( ) ); + return false; + } + } + + Com_Printf( "OK\n" ); + + Com_Printf( "SDL audio driver is \"%s\".\n", SDL_GetCurrentAudioDriver( ) ); + + memset(&desired, '\0', sizeof (desired)); + memset(&obtained, '\0', sizeof (obtained)); + + tmp = ((int) s_sdlBits->value); + if ((tmp != 16) && (tmp != 8)) + tmp = 16; + + desired.freq = (int) s_sdlSpeed->value; + if(!desired.freq) desired.freq = 22050; + desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8); + + // I dunno if this is the best idea, but I'll give it a try... + // should probably check a cvar for this... + if (s_sdlDevSamps->value) + desired.samples = s_sdlDevSamps->value; + else + { + // just pick a sane default. + if (desired.freq <= 11025) + desired.samples = 256; + else if (desired.freq <= 22050) + desired.samples = 512; + else if (desired.freq <= 44100) + desired.samples = 1024; + else + desired.samples = 2048; // (*shrug*) + } + + desired.channels = (int) s_sdlChannels->value; + desired.callback = SNDDMA_AudioCallback; + + if (SDL_OpenAudio(&desired, &obtained) == -1) + { + Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained); + + // dma.samples needs to be big, or id's mixer will just refuse to + // work at all; we need to keep it significantly bigger than the + // amount of SDL callback samples, and just copy a little each time + // the callback runs. + // 32768 is what the OSS driver filled in here on my system. I don't + // know if it's a good value overall, but at least we know it's + // reasonable...this is why I let the user override. + tmp = s_sdlMixSamps->value; + if (!tmp) + tmp = (obtained.samples * obtained.channels) * 10; + + if (tmp & (tmp - 1)) // not a power of two? Seems to confuse something. + { + int val = 1; + while (val < tmp) + val <<= 1; + + tmp = val; + } + + dmapos = 0; + dma.samplebits = obtained.format & 0xFF; // first byte of format is bits. + dma.channels = obtained.channels; + dma.samples = tmp; + dma.submission_chunk = 1; + dma.speed = obtained.freq; + dmasize = (dma.samples * (dma.samplebits/8)); + dma.buffer = (byte*)calloc(1, dmasize); + + Com_Printf("Starting SDL audio callback...\n"); + SDL_PauseAudio(0); // start callback. + + Com_Printf("SDL audio initialized.\n"); + snd_inited = true; + return true; +} + +/* +=============== +SNDDMA_GetDMAPos +=============== +*/ +int SNDDMA_GetDMAPos(void) +{ + return dmapos; +} + +/* +=============== +SNDDMA_Shutdown +=============== +*/ +void SNDDMA_Shutdown(void) +{ + Com_Printf("Closing SDL audio device...\n"); + SDL_PauseAudio(1); + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + free(dma.buffer); + dma.buffer = NULL; + dmapos = dmasize = 0; + snd_inited = false; + Com_Printf("SDL audio device shut down.\n"); +} + +/* +=============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +=============== +*/ +void SNDDMA_Submit(void) +{ + SDL_UnlockAudio(); +} + +/* +=============== +SNDDMA_BeginPainting +=============== +*/ +void SNDDMA_BeginPainting (void) +{ + SDL_LockAudio(); +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt new file mode 100644 index 0000000..98f3a23 --- /dev/null +++ b/src/server/CMakeLists.txt @@ -0,0 +1,114 @@ + +# +## .dMMMb dMMMMMP dMMMMb dMP dMP dMMMMMP dMMMMb +## dMP" VP dMP dMP.dMP dMP dMP dMP dMP.dMP +## VMMMb dMMMP dMMMMK" dMP dMP dMMMP dMMMMK" +## dP .dMP dMP dMP"AMF YMvAP" dMP dMP"AMF +## VMMMP" dMMMMMP dMP dMP VP" dMMMMMP dMP dMP +# + +add_definitions( + -DDEDICATED + -DUSE_LOCAL_HEADERS + -DPRODUCT_VERSION="1.2.0 pre-release" + -DUSE_VOIP + -DNDEBUG + ) + +set(EXTERNAL_DIR ${CMAKE_SOURCE_DIR}/external) +set(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) +if(APPLE) +set(APPLE_SOURCES ${PARENT_DIR}/sys/sys_osx.mm) +endif(APPLE) + +add_executable( + tremded + # + server.h + # + sv_ccmds.cpp + sv_client.cpp + sv_game.cpp + sv_init.cpp + sv_main.cpp + sv_net_chan.cpp + sv_snapshot.cpp + sv_world.cpp + # + ${PARENT_DIR}/qcommon/cm_load.cpp + ${PARENT_DIR}/qcommon/cm_patch.cpp + ${PARENT_DIR}/qcommon/cm_polylib.cpp + ${PARENT_DIR}/qcommon/cm_test.cpp + ${PARENT_DIR}/qcommon/cm_trace.cpp + ${PARENT_DIR}/qcommon/cmd.cpp + ${PARENT_DIR}/qcommon/common.cpp + ${PARENT_DIR}/qcommon/crypto.cpp + ${PARENT_DIR}/qcommon/cvar.cpp + ${PARENT_DIR}/qcommon/files.cpp + ${PARENT_DIR}/qcommon/huffman.cpp + ${PARENT_DIR}/qcommon/huffman.h + ${PARENT_DIR}/qcommon/ioapi.cpp + ${PARENT_DIR}/qcommon/md4.cpp + ${PARENT_DIR}/qcommon/msg.h + ${PARENT_DIR}/qcommon/msg.cpp + ${PARENT_DIR}/qcommon/net.h + ${PARENT_DIR}/qcommon/net_chan.cpp + ${PARENT_DIR}/qcommon/net_ip.cpp + ${PARENT_DIR}/qcommon/parse.cpp + ${PARENT_DIR}/qcommon/q3_lauxlib.cpp + ${PARENT_DIR}/qcommon/q_math.c + ${PARENT_DIR}/qcommon/q_shared.c + ${PARENT_DIR}/qcommon/unzip.cpp + ${PARENT_DIR}/qcommon/vm.cpp + ${PARENT_DIR}/qcommon/vm_interpreted.cpp + ${PARENT_DIR}/qcommon/vm_x86.cpp + # + ${PARENT_DIR}/null/null_client.cpp + ${PARENT_DIR}/null/null_input.cpp + ${PARENT_DIR}/null/null_snddma.cpp + # + ${PARENT_DIR}/asm/snapvector.c + # + ${PARENT_DIR}/sys/con_log.cpp + ${PARENT_DIR}/sys/con_tty.cpp + ${PARENT_DIR}/sys/sys_main.cpp + ${PARENT_DIR}/sys/sys_unix.cpp + ${PARENT_DIR}/sys/sys_shared.h + ${APPLE_SOURCES} + # + ${EXTERNAL_DIR}/zlib/adler32.c + ${EXTERNAL_DIR}/zlib/crc32.c + ${EXTERNAL_DIR}/zlib/inffast.c + ${EXTERNAL_DIR}/zlib/inflate.c + ${EXTERNAL_DIR}/zlib/inftrees.c + ${EXTERNAL_DIR}/zlib/zutil.c + ) + +if(APPLE) + # FIXME Prefixed with "lua" to prevent cmake from doing "-l-framework Cocoa" + set(FRAMEWORKS "-framework Cocoa -framework Security -framework OpenAL -framework IOKit") +else(APPLE) + if(UNIX) + set(SYSLIBS dl rt) + endif(UNIX) +endif(APPLE) + +target_link_libraries( + tremded + # + lua + script_api + nettle + zlib + ${FRAMEWORKS} + ${SYSLIBS} + ) + +include_directories( + ${PARENT_DIR}/script + ${EXTERNAL_DIR}/lua-5.3.3/include + ${EXTERNAL_DIR}/sol + ${EXTERNAL_DIR}/script/rapidjson + ${EXTERNAL_DIR}/nettle-3.3 + ${EXTERNAL_DIR}/zlib + ) diff --git a/src/server/server.h b/src/server/server.h new file mode 100644 index 0000000..a07b66c --- /dev/null +++ b/src/server/server.h @@ -0,0 +1,529 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2012-2018 ET:Legacy team +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// server.h + +#ifndef SERVER_H +#define SERVER_H 1 + +#include "game/g_public.h" +#include "qcommon/cmd.h" +#include "qcommon/crypto.h" +#include "qcommon/cvar.h" +#include "qcommon/files.h" +#include "qcommon/huffman.h" +#include "qcommon/msg.h" +#include "qcommon/net.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "qcommon/vm.h" +#include "sys/sys_shared.h" + +//============================================================================= + +#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! +#define CS_WARMUP 5 // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + +// server attack protection +#define SVP_IOQ3 0x0001 ///< 1 - ioQuake3 way +#define SVP_OWOLF 0x0002 ///< 2 - OpenWolf way +#define SVP_CONSOLE 0x0004 ///< 4 - console print + +#define MAX_ENT_CLUSTERS 16 + +#ifdef USE_VOIP +#define VOIP_QUEUE_LENGTH 64 +struct voipServerPacket_t { + int generation; + int sequence; + int frames; + int len; + int sender; + int flags; + byte data[4000]; +}; +#endif // USE_VOIP + +struct svEntity_t { + struct worldSector_t *worldSector; + svEntity_t *nextEntityInWorldSector; + + entityState_t baseline; // for delta compression of initial sighting + int numClusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int lastCluster; // if all the clusters don't fit in clusternums + int areanum, areanum2; + int snapshotCounter; // used to prevent double adding from portal views +}; + +enum serverState_t { + SS_DEAD, // no map loaded + SS_LOADING, // spawning level entities + SS_GAME // actively running +}; + +struct configString_t { + char *s; + bool restricted; // if true, don't send to clientList + clientList_t clientList; +}; + +struct server_t { + serverState_t state; + bool restarting; // if true, send configstring changes during SS_LOADING + int serverId; // changes each server start + int restartedServerId; // serverId before a map_restart + int checksumFeed; // the feed key that we use to compute the pure checksum strings + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // the serverId associated with the current checksumFeed (always <= serverId) + int checksumFeedServerId; + int snapshotCounter; // incremented for each snapshot built + int timeResidual; // <= 1000 / sv_frame->value + int nextFrameTime; // when time > nextFrameTime, process world + configString_t configstrings[MAX_CONFIGSTRINGS]; + svEntity_t svEntities[MAX_GENTITIES]; + + char *entityParsePoint; // used during game VM init + + // the game virtual machine will update these on init and changes + sharedEntity_t *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + playerState_t *gameClients; + int gameClientSize; // will be > sizeof(playerState_t) due to game private data + + int restartTime; + int time; + + vm_t *gvm; // game virtual machine +}; + +struct clientSnapshot_t { + int areabytes; + byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + playerState_t ps; + int num_entities; + int first_entity; // into the circular sv_packet_entities[] + // the entities MUST be in increasing state number + // order, otherwise the delta compression will fail + int messageSent; // time the message was transmitted + int messageAcked; // time the message was acked + int messageSize; // used to rate drop packets +}; + +enum clientState_t { + CS_FREE, // can be reused for a new connection + CS_ZOMBIE, // client has been disconnected, but don't reuse + // connection for a couple seconds + CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet + CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd + CS_ACTIVE // client is fully in game +}; + +struct netchan_buffer_t { + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + netchan_buffer_t *next; +}; + +struct client_t { + clientState_t state; + char userinfo[MAX_INFO_STRING]; // name, etc + char userinfobuffer[MAX_INFO_STRING]; ///< used for buffering of user info + + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet + int reliableAcknowledge; // last acknowledged reliable message + int reliableSent; // last sent reliable message, not necesarily acknowledged yet + int messageAcknowledge; + + int gamestateMessageNum; // netchan->outgoingSequence of gamestate + int challenge; + + usercmd_t lastUsercmd; + int lastMessageNum; // for delta compression + int lastClientCommand; // reliable client message sequence + char lastClientCommandString[MAX_STRING_CHARS]; + sharedEntity_t *gentity; // SV_GentityNum(clientnum) + char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + + // downloading + char downloadName[MAX_QPATH]; // if not empty string, we are downloading + fileHandle_t download; // file being downloaded + int downloadSize; // total bytes (can't use EOF because of paks) + int downloadCount; // bytes sent + int downloadClientBlock; // last block we sent to the client, awaiting ack + int downloadCurrentBlock; // current block number + int downloadXmitBlock; // last block we xmited + unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks + int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; + bool downloadEOF; // We have sent the EOF block + int downloadSendTime; // time we last got an ack from the client + + int deltaMessage; // frame last client usercmd message + int nextReliableTime; // svs.time when another reliable command will be allowed + int nextReliableUserTime; // svs.time when another userinfo change will be allowed + int lastPacketTime; // svs.time when packet was last received + int lastConnectTime; // svs.time when connection started + int lastSnapshotTime; // svs.time of last sent snapshot + bool rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec + int timeoutCount; // must timeout a few frames in a row so debugging doesn't break + clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here + int ping; + int rate; // bytes / second + int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked + int pureAuthentic; + bool gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all + netchan_t netchan; + // TTimo + // queuing outgoing fragmented messages to send them properly, without udp packet bursts + // in case large fragmented messages are stacking up + // buffer them into this queue, and hand them out to netchan as needed + netchan_buffer_t *netchan_start_queue; + netchan_buffer_t **netchan_end_queue; + + char fingerprint[SHA256_DIGEST_SIZE * 2 + 1]; + +#ifdef USE_VOIP + bool hasVoip; + bool muteAllVoip; + bool ignoreVoipFromClient[MAX_CLIENTS]; + voipServerPacket_t *voipPacket[VOIP_QUEUE_LENGTH]; + size_t queuedVoipPackets; + int queuedVoipIndex; +#endif + + int oldServerTime; + bool csUpdated[MAX_CONFIGSTRINGS]; +}; + +//============================================================================= +#define STATFRAMES 200 ///< 5 seconds - assumed we run 40 fps + +/** + * @struct svstats_t + * @brief + */ +struct svstats_t { + double active; + double idle; + int count; + + double latched_active; + double latched_idle; + + float cpu; + float avg; +}; + +// MAX_CHALLENGES is made large to prevent a denial +// of service attack that could cycle all of them +// out before legitimate users connected +#define MAX_CHALLENGES 2048 +// Allow a certain amount of challenges to have the same IP address +// to make it a bit harder to DOS one single IP address from connecting +// while not allowing a single ip to grab all challenge resources +#define MAX_CHALLENGES_MULTI (MAX_CHALLENGES / 2) + +#define AUTHORIZE_TIMEOUT 5000 + +struct challenge_t { + netadr_t adr; + int challenge; + char challenge2[33]; + int clientChallenge; // challenge number coming from the client + int time; // time the last packet was sent to the autherize server + int pingTime; // time the challenge response was sent to client + int firstTime; // time the adr was first used, for authorize timeout checks + bool wasrefused; + bool connected; +}; + +/** + * @struct receipt_t + * @brief + */ +struct receipt_t { + netadr_t adr; + int time; +}; + +/** + * @def MAX_INFO_RECEIPTS + * @brief the maximum number of getstatus+getinfo responses that we send in + * a two second time period. + */ +#define MAX_INFO_RECEIPTS 48 + +/** + * @struct tempBan_s + * @typedef tempBan_t + * @brief + */ +struct tempBan_t { + netadr_t adr; + int endtime; +}; + +#define MAX_TEMPBAN_ADDRESSES MAX_CLIENTS + +#define SERVER_PERFORMANCECOUNTER_FRAMES 600 +#define SERVER_PERFORMANCECOUNTER_SAMPLES 6 + +// this structure will be cleared only when the game dll changes +struct serverStatic_t { + bool initialized; // sv_init has completed + + int time; // will be strictly increasing across level changes + + int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() + + client_t *clients; // [sv_maxclients->integer]; + int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_SNAPSHOT_ENTITIES + int nextSnapshotEntities; // next snapshotEntities to use + entityState_t *snapshotEntities; // [numSnapshotEntities] + int nextHeartbeatTime; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + receipt_t infoReceipts[MAX_INFO_RECEIPTS]; + netadr_t redirectAddress; // for rcon return messages + + netadr_t authorizeAddress; // for rcon return messages + + int sampleTimes[SERVER_PERFORMANCECOUNTER_SAMPLES]; + int currentSampleIndex; + int totalFrameTime; + int currentFrameIndex; + int serverLoad; + svstats_t stats; +}; + +//============================================================================= + +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map + +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_rconPassword; +extern cvar_t *sv_privatePassword; +extern cvar_t *sv_allowDownload; +extern cvar_t *sv_maxclients; + +extern cvar_t *sv_privateClients; +extern cvar_t *sv_hostname; +extern cvar_t *sv_masters[3][MAX_MASTER_SERVERS]; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showloss; +extern cvar_t *sv_padPackets; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_minRate; +extern cvar_t *sv_maxRate; +extern cvar_t *sv_dlRate; +extern cvar_t *sv_minPing; +extern cvar_t *sv_maxPing; +extern cvar_t *sv_pure; +extern cvar_t *sv_lanForceRate; +extern cvar_t *sv_banFile; + +extern cvar_t *sv_protect; +extern cvar_t *sv_protectLog; + +#ifdef USE_VOIP +extern cvar_t *sv_voip; +extern cvar_t *sv_voipProtocol; +#endif + +extern cvar_t *sv_rsaAuth; + +extern cvar_t *sv_schachtmeisterPort; + +//=========================================================== + +// +// sv_main.c +// +struct leakyBucket_t { + netadrtype_t type; + + union { + byte _4[4]; + byte _6[16]; + } ipv; + + int lastTime; + signed char burst; + + long hash; + + leakyBucket_t *prev, *next; +}; + +extern leakyBucket_t outboundLeakyBucket; + +bool SVC_RateLimit(leakyBucket_t *bucket, int burst, int period); +bool SVC_RateLimitAddress(netadr_t from, int burst, int period); + +void SV_FinalMessage(const char *message); +void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +void SV_AddOperatorCommands(void); +void SV_RemoveOperatorCommands(void); + +void SV_MasterShutdown(void); +int SV_RateMsec(client_t *client); + +// +// sv_init.c +// +void SV_SetConfigstring(int index, const char *val); +void SV_GetConfigstring(int index, char *buffer, int bufferSize); +void SV_SetConfigstringRestrictions(int index, const clientList_t *clientList); +void SV_UpdateConfigstrings(client_t *client); + +void SV_SetUserinfo(int index, const char *val); +void SV_GetUserinfo(int index, char *buffer, int bufferSize); + +void SV_ChangeMaxClients(void); +void SV_SpawnServer(char *server); +void SV_WriteAttackLog(const char *log); + +#ifdef NDEBUG +#define SV_WriteAttackLogD(x) +#else +#define SV_WriteAttackLogD(x) SV_WriteAttackLog(x) +#endif + +// +// sv_client.c +// +void SV_GetChallenge(netadr_t from); + +void SV_DirectConnect(netadr_t from); + +void SV_ExecuteClientMessage(client_t *cl, msg_t *msg); +void SV_UserinfoChanged(client_t *cl); + +void SV_ClientEnterWorld(client_t *client, usercmd_t *cmd); +void SV_FreeClient(client_t *client); +void SV_DropClient(client_t *drop, const char *reason); + +void SV_ExecuteClientCommand(client_t *cl, const char *s, bool clientOK); +void SV_ClientThink(client_t *cl, usercmd_t *cmd); + +int SV_WriteDownloadToClient(client_t *cl, msg_t *msg); +int SV_SendDownloadMessages(void); +int SV_SendQueuedMessages(void); + +// +// sv_ccmds.c +// +void SV_Heartbeat_f(void); + +// +// sv_snapshot.c +// +void SV_AddServerCommand(client_t *client, const char *cmd); +void SV_UpdateServerCommandsToClient(client_t *client, msg_t *msg); +void SV_WriteFrameToClient(client_t *client, msg_t *msg); +void SV_SendMessageToClient(msg_t *msg, client_t *client); +void SV_SendClientMessages(void); +void SV_SendClientSnapshot(client_t *client); + +// +// sv_game.c +// +int SV_NumForGentity(sharedEntity_t *ent); +sharedEntity_t *SV_GentityNum(int num); +playerState_t *SV_GameClientNum(int num); +svEntity_t *SV_SvEntityForGentity(sharedEntity_t *gEnt); +sharedEntity_t *SV_GEntityForSvEntity(svEntity_t *svEnt); +void SV_InitGameProgs(void); +void SV_ShutdownGameProgs(void); +void SV_RestartGameProgs(void); +bool SV_inPVS(const vec3_t p1, const vec3_t p2); + +//============================================================ +// +// high level object sorting to reduce interaction tests +// + +void SV_ClearWorld(void); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEntity(sharedEntity_t *ent); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself + +void SV_LinkEntity(sharedEntity_t *ent); +// Needs to be called any time an entity changes origin, mins, maxs, +// or solid. Automatically unlinks if needed. +// sets ent->r.absmin and ent->r.absmax +// sets ent->leafnums[] for pvs determination even if the entity +// is not solid + +clipHandle_t SV_ClipHandleForEntity(const sharedEntity_t *ent); + +void SV_SectorList_f(void); + +int SV_AreaEntities(const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount); +// fills in a table of entity numbers with entities that have bounding boxes +// that intersect the given area. It is possible for a non-axial bmodel +// to be returned that doesn't actually intersect the area on an exact +// test. +// returns the number of pointers filled in +// The world entity is never returned in this list. + +int SV_PointContents(const vec3_t p, int passEntityNum); +// returns the CONTENTS_* value from the world and all entities at the given point. + +void SV_Trace(trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, + int contentmask, traceType_t type); +// mins and maxs are relative + +// if the entire move stays in a solid volume, trace.allsolid will be set, +// trace.startsolid will be set, and trace.fraction will be 0 + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE) + +void SV_ClipToEntity(trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int entityNum, int contentmask, traceType_t type); +// clip to a specific entity + +// +// sv_net_chan.c +// +void SV_Netchan_Transmit(client_t *client, msg_t *msg); +int SV_Netchan_TransmitNextFragment(client_t *client); +bool SV_Netchan_Process(client_t *client, msg_t *msg); +void SV_Netchan_FreeQueue(client_t *client); + +#endif diff --git a/src/server/sv_admin.cpp b/src/server/sv_admin.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/server/sv_admin.h b/src/server/sv_admin.h new file mode 100644 index 0000000..b261457 --- /dev/null +++ b/src/server/sv_admin.h @@ -0,0 +1,61 @@ +// +// This file is part of Tremulous. +// Copyright © 2017 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . +// + +#pragma once + +#include + +using std::string; + +typedef char fingerprint_t[64]; +typedef char guid_t[33]; +typedef char name_t[MAX_NAME_LENGTH]; +typedef char err_t[MAX_STRING_CHARS]; + +struct AdminFlag { + unsigned id; + name_t name; +}; + +struct AdminLevel { + name_t name; + admin_flags_t flags; + unsigned level; +}; + +struct Admin { + bool flag(const name_t flagname) + { } + + bool deny(const name_t flagname) + { } + + guid_t guid; + name_t name; + +private: + admin_flags_t flags; + admin_flags_t denied; + unsigned level; +}; + +class AdminMgr { + bool add(Admin&); + bool remove(Admin&); +} diff --git a/src/server/sv_ccmds.cpp b/src/server/sv_ccmds.cpp new file mode 100644 index 0000000..5c8902d --- /dev/null +++ b/src/server/sv_ccmds.cpp @@ -0,0 +1,441 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2012-2018 ET:Legacy team +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include +#include "server.h" + +/* +=============================================================================== + +OPERATOR CONSOLE ONLY COMMANDS + +These commands can only be entered from stdin or by a remote operator datagram +=============================================================================== +*/ + +/* +================== +SV_Map_f + +Restart the server on a different map +================== +*/ +static void SV_Map_f( void ) { + const char *cmd; + const char *map; + bool cheat; + char expanded[MAX_QPATH]; + char mapname[MAX_QPATH]; + int a; + int i; + + map = Cmd_Argv(1); + if ( !map ) { + return; + } + + // make sure the level exists before trying to change, so that + // a typo at the server console won't end the game + Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map); + if ( FS_ReadFile (expanded, NULL) == -1 ) { + Com_Printf ("Can't find map %s\n", expanded); + return; + } + + cmd = Cmd_Argv(0); + if ( !Q_stricmp( cmd, "devmap" ) ) { + cheat = true; + } else { + cheat = false; + } + + // save the map name here cause on a map restart we reload the autogen.cfg + // and thus nuke the arguments of the map command + Q_strncpyz(mapname, map, sizeof(mapname)); + + // start up the map + SV_SpawnServer( mapname ); + + // set the cheat value + // if the level was started with "map ", then + // cheats will not be allowed. If started with "devmap " + // then cheats will be allowed + if ( cheat ) { + Cvar_Set( "sv_cheats", "1" ); + } else { + Cvar_Set( "sv_cheats", "0" ); + } + + // This forces the local master server IP address cache + // to be updated on sending the next heartbeat + for( a = 0; a < 3; ++a ) + for( i = 0; i < MAX_MASTER_SERVERS; i++ ) + sv_masters[ a ][ i ]->modified = true; +} + +/* +================ +SV_MapRestart_f + +Completely restarts a level, but doesn't send a new gamestate to the clients. +This allows fair starts with variable load times. +================ +*/ +static void SV_MapRestart_f( void ) { + int i; + client_t *client; + char *denied; + int delay; + + // make sure we aren't restarting twice in the same frame + if ( com_frameTime == sv.serverId ) { + return; + } + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( sv.restartTime ) { + return; + } + + if (Cmd_Argc() > 1 ) { + delay = atoi( Cmd_Argv(1) ); + } + else { + delay = 0; + } + if( delay && !Cvar_VariableValue("g_doWarmup") ) { + sv.restartTime = sv.time + delay * 1000; + SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) ); + return; + } + + // check for changes in variables that can't just be restarted + // check for maxclients change + if ( sv_maxclients->modified ) { + char mapname[MAX_QPATH]; + + Com_Printf( "variable change -- restarting.\n" ); + // restart the map the slow way + Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); + + SV_SpawnServer( mapname ); + return; + } + + // toggle the server bit so clients can detect that a + // map_restart has happened + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // generate a new serverid + // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart + sv.serverId = com_frameTime; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // if a map_restart occurs while a client is changing maps, we need + // to give them the correct time so that when they finish loading + // they don't violate the backwards time check in cl_cgame.c + for (i=0 ; iinteger ; i++) { + if (svs.clients[i].state == CS_PRIMED) { + svs.clients[i].oldServerTime = sv.restartTime; + } + } + + // reset all the vm data in place without changing memory allocation + // note that we do NOT set sv.state = SS_LOADING, so configstrings that + // had been changed from their default values will generate broadcast updates + sv.state = SS_LOADING; + sv.restarting = true; + + SV_RestartGameProgs(); + + // run a few frames to allow everything to settle + for (i = 0; i < 3; i++) + { + VM_Call (sv.gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; + } + + sv.state = SS_GAME; + sv.restarting = false; + + // connect and begin all the clients + for (i=0 ; iinteger ; i++) { + client = &svs.clients[i]; + + // send the new gamestate to all connected clients + if ( client->state < CS_CONNECTED) { + continue; + } + + // add the map_restart command + SV_AddServerCommand( client, "map_restart\n" ); + + // connect the client again, without the firstTime flag + denied = (char*)VM_ExplicitArgPtr( sv.gvm, VM_Call( sv.gvm, GAME_CLIENT_CONNECT, i, false ) ); + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( client, denied ); + Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i ); + continue; + } + + if(client->state == CS_ACTIVE) + SV_ClientEnterWorld(client, &client->lastUsercmd); + else + { + // If we don't reset client->lastUsercmd and are restarting during map load, + // the client will hang because we'll use the last Usercmd from the previous map, + // which is wrong obviously. + SV_ClientEnterWorld(client, NULL); + } + } + + // run another frame to allow things to look at all the players + VM_Call (sv.gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; +} + + +//=============================================================== + +/** + * @brief SV_Status_f + */ +static void SV_Status_f(void) { + int i; + client_t *cl; + playerState_t *ps; + const char *s; + int ping; + unsigned int maxNameLength; + + // make sure server is running + if (!com_sv_running->integer) { + Com_Printf("Server is not running.\n"); + return; + } + + Com_Printf("cpu server utilization: %i %%\n" + "avg response time : %i ms\n" + "server time : %i\n" + "internal time : %i\n" + "map : %s\n\n" + "num score ping name lastmsg address qport rate lastConnectTime\n" + "--- ----- ---- ----------------------------------- ------- --------------------- ----- ----- ---------------\n", + ( int ) svs.stats.cpu, + ( int ) svs.stats.avg, + svs.time, + Sys_Milliseconds(), + sv_mapname->string); + + for (i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { + Com_Printf("%3i ", i); + ps = SV_GameClientNum(i); + Com_Printf("%5i ", ps->persistant[PERS_SCORE]); + + if (cl->state == CS_CONNECTED) { + Com_Printf("CNCT "); + } else if (cl->state == CS_ZOMBIE) { + Com_Printf("ZMBI "); + } else { + ping = cl->ping < 9999 ? cl->ping : 9999; + Com_Printf("%4i ", ping); + } + + s = NET_AdrToString(cl->netchan.remoteAddress); + + // extend the name length by couting extra color characters to keep well formated output + maxNameLength = sizeof(cl->name) + (strlen(cl->name) - Q_PrintStrlen(cl->name)) + 1; + + Com_Printf("%-*s %7i %-21s %5i %5i %i\n", maxNameLength, rc(cl->name), svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate, svs.time - cl->lastConnectTime); + } + + Com_Printf("\n"); +} + + +/* +================== +SV_Heartbeat_f + +Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer +================== +*/ +void SV_Heartbeat_f( void ) { + svs.nextHeartbeatTime = -9999999; +} + + +/* +=========== +SV_Serverinfo_f + +Examine the serverinfo string +=========== +*/ +static void SV_Serverinfo_f( void ) { + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + Com_Printf ("Server info settings:\n"); + Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) ); +} + + +/* +=========== +SV_Systeminfo_f + +Examine the systeminfo string +=========== +*/ +static void SV_Systeminfo_f( void ) { + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + Com_Printf ("System info settings:\n"); + Info_Print ( Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); +} + + +/* +================= +SV_KillServer +================= +*/ +static void SV_KillServer_f( void ) { + SV_Shutdown( "killserver" ); +} + +static void SV_SMQ_f( void ) { + static qboolean schmResolved = qfalse; + static netadr_t schmAddress; + + if ( !schmResolved ) { + schmResolved = qtrue; + NET_StringToAdr( "127.0.0.1", &schmAddress, NA_IP ); + schmAddress.port = 1337; + } + + if ( sv_schachtmeisterPort->modified && + sv_schachtmeisterPort->integer >= 1 && sv_schachtmeisterPort->integer <= 65535 ) + { + schmAddress.port = htons(sv_schachtmeisterPort->integer); + } + + if ( Cmd_Argc() >= 3 && !Q_stricmp( Cmd_Argv( 1 ), "ipa" ) ) { // compatibility with out-of-date crapware conceived in the future + NET_OutOfBandPrint( NS_SERVER, schmAddress, "sm2query %s", Cmd_ArgsFrom( 2 ) ); + Com_Printf( "^3query [^7sm2query %s^3]\n", Cmd_ArgsFrom( 2 ) ); // DELME + } else { + char args[ MAX_STRING_CHARS ]; + char *p; + int s, i; + + p = args; + s = sizeof( args ); + + for ( i = 1; i < Cmd_Argc(); ++i ) + { + int l; + Com_sprintf( p, s, " \"%s\"", Cmd_Argv( i ) ); + l = strlen( p ); + s -= l; + p += l; + } + + NET_OutOfBandPrint( NS_SERVER, schmAddress, "sm2query%s", args ); + Com_Printf( "^3query [^7sm2query%s^3]\n", args ); // DELME + } +} + +//=========================================================== + +/* +================== +SV_CompleteMapName +================== +*/ +static void SV_CompleteMapName( char *args, int argNum ) { + if( argNum == 2 ) { + Field_CompleteFilename( "maps", "bsp", true, false ); + } +} + +/* +================== +SV_AddOperatorCommands +================== +*/ +void SV_AddOperatorCommands( void ) { + static bool initialized = false; + + if ( initialized ) { + return; + } + initialized = true; + + Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); + Cmd_AddCommand ("status", SV_Status_f); + Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); + Cmd_AddCommand ("systeminfo", SV_Systeminfo_f); + Cmd_AddCommand ("map_restart", SV_MapRestart_f); + Cmd_AddCommand ("sectorlist", SV_SectorList_f); + Cmd_AddCommand ("map", SV_Map_f); + Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName ); + Cmd_AddCommand ("devmap", SV_Map_f); + Cmd_SetCommandCompletionFunc( "devmap", SV_CompleteMapName ); + Cmd_AddCommand ("killserver", SV_KillServer_f); + Cmd_AddCommand ("smq", SV_SMQ_f); +} + +/* +================== +SV_RemoveOperatorCommands +================== +*/ +void SV_RemoveOperatorCommands( void ) { +#if 0 + // removing these won't let the server start again + Cmd_RemoveCommand ("heartbeat"); + Cmd_RemoveCommand ("serverinfo"); + Cmd_RemoveCommand ("systeminfo"); + Cmd_RemoveCommand ("map_restart"); + Cmd_RemoveCommand ("sectorlist"); +#endif +} diff --git a/src/server/sv_client.cpp b/src/server/sv_client.cpp new file mode 100644 index 0000000..0a54a32 --- /dev/null +++ b/src/server/sv_client.cpp @@ -0,0 +1,1949 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2012-2018 ET:Legacy team +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// sv_client.c -- server code for dealing with clients + +#include "server.h" + +static void SV_CloseDownload( client_t *cl ); + +/* +================= +SV_RSA_VerifySignature + +Verifies the signature of data and on success it returns the sha256 +fingerprint of the public key. pubkey, signature, and fingerprint +are all base 16 encoded strings. + +Returns true on success +================= +*/ +static bool SV_RSA_VerifySignature( const char *pubkey, const char *signature, const char *data, char *fingerprint ) +{ + struct rsa_public_key public_key; + struct sha256_ctx sha256_hash; + uint8_t buf[RSA_STRING_LENGTH]; + int err; + mpz_t n; + + if ( !*pubkey || !*signature ) + return false; + + // load public key + rsa_public_key_init( &public_key ); + mpz_set_ui( public_key.e, RSA_PUBLIC_EXPONENT ); + err = mpz_set_str( public_key.n, pubkey, 16 ); + if ( err ) { + rsa_public_key_clear( &public_key ); + return false; + } + + err = !rsa_public_key_prepare( &public_key ); + if ( err ) { + rsa_public_key_clear( &public_key ); + return false; + } + + // load signature + mpz_init( n ); + err = mpz_set_str( n, signature, 16 ); + if ( err ) { + mpz_clear( n ); + rsa_public_key_clear( &public_key ); + return false; + } + + // hash data + sha256_init( &sha256_hash ); + sha256_update( &sha256_hash, strlen(data), (uint8_t *) data); + + if ( !rsa_sha256_verify( &public_key, &sha256_hash, n ) ) { + mpz_clear( n ); + rsa_public_key_clear( &public_key ); + return false; + } + + // VERIFIED, save the sha256 fingerprint of the key + nettle_mpz_get_str_256( sizeof(buf), buf, public_key.n ); + + sha256_update( &sha256_hash, sizeof(buf), buf ); + sha256_digest( &sha256_hash, SHA256_DIGEST_SIZE, buf ); + + nettle_mpz_set_str_256_u( n, SHA256_DIGEST_SIZE, buf ); + mpz_get_str( fingerprint, 16, n ); + + mpz_clear( n ); + rsa_public_key_clear( &public_key ); + return true; +} + +/* +================= +SV_GetChallenge + +A "getchallenge" OOB command has been received +Returns a challenge number that can be used +in a subsequent connectResponse command. +We do this to prevent denial of service attacks that +flood the server with invalid connection IPs. With a +challenge, they must give a valid IP address. + +If we are authorizing, a challenge request will cause a packet +to be sent to the authorize server. + +When an authorizeip is returned, a challenge response will be +sent to that ip. + +ioquake3: we added a possibility for clients to add a challenge +to their packets, to make it more difficult for malicious servers +to hi-jack client connections. +Also, the auth stuff is completely disabled for com_standalone games +as well as IPv6 connections, since there is no way to use the +v4-only auth server for these new types of connections. +================= +*/ +void SV_GetChallenge(netadr_t from) +{ + int i; + int oldest; + int oldestTime; + int oldestClientTime; + int clientChallenge; + challenge_t *challenge; + bool wasfound = false; + byte buf[16]; + mpz_t n; + + if (sv_protect->integer & SVP_IOQ3) + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + { + Com_DPrintf( "SV_GetChallenge: rate limit from %s exceeded, dropping request\n", + // Prevent using getchallenge as an amplifier + NET_AdrToString( from ) ); + if (SVC_RateLimitAddress(from, 10, 1000)) + return; + { + } + SV_WriteAttackLog(va("SV_GetChallenge: rate limit from %s exceeded, dropping request\n", + NET_AdrToString(from))); + return; + } + + + // Allow getchallenge to be DoSed relatively easily, but prevent + // Allow getchallenge to be DoSed relatively easily, but prevent + // excess outbound bandwidth usage when being flooded inbound + // excess outbound bandwidth usage when being flooded inbound + if ( SVC_RateLimit( &outboundLeakyBucket, 10, 100 ) ) { + if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) + Com_DPrintf( "SV_GetChallenge: rate limit exceeded, dropping request\n" ); + { + return; + SV_WriteAttackLog("SV_GetChallenge: rate limit exceeded, dropping request\n"); + return; + } + } + } + + oldest = 0; + oldestClientTime = oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + clientChallenge = atoi(Cmd_Argv(1)); + + for(i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) + { + if(!challenge->connected && NET_CompareAdr(from, challenge->adr)) + { + wasfound = true; + + if(challenge->time < oldestClientTime) + oldestClientTime = challenge->time; + } + + if(wasfound && i >= MAX_CHALLENGES_MULTI) + { + i = MAX_CHALLENGES; + break; + } + + if(challenge->time < oldestTime) + { + oldestTime = challenge->time; + oldest = i; + } + } + + if (i == MAX_CHALLENGES) + { + // this is the first time this client has asked for a challenge + challenge = &svs.challenges[oldest]; + challenge->clientChallenge = clientChallenge; + challenge->adr = from; + challenge->firstTime = svs.time; + challenge->connected = false; + + if ( sv_rsaAuth->integer ) { + Sys_CryptoRandomBytes( buf, sizeof(buf) ); + nettle_mpz_init_set_str_256_u( n, sizeof(buf), buf ); + mpz_get_str( challenge->challenge2, 16, n ); + mpz_clear( n ); + } + } + + // always generate a new challenge number, so the client cannot circumvent sv_maxping + challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time; + challenge->wasrefused = false; + challenge->time = svs.time; + challenge->pingTime = svs.time; + + if ( sv_rsaAuth->integer ) { + NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %d %d %d %s", + challenge->challenge, clientChallenge, PROTOCOL_VERSION, challenge->challenge2 ); + } + else { + NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %d %d %d", + challenge->challenge, clientChallenge, PROTOCOL_VERSION ); + } +} + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ +void SV_DirectConnect( netadr_t from ) { + char userinfo[MAX_INFO_STRING]; + int i; + client_t *cl, *newcl; + client_t temp; + sharedEntity_t *ent; + int clientNum; + int version; + int qport; + int challenge; + char *password; + int startIndex; + intptr_t denied; + int count; + const char *ip; + char *challenge2; + bool challenge2Verified = false; + + Com_DPrintf ("SVC_DirectConnect ()\n"); + + // Prevent using connect as an amplifier + if (sv_protect->integer & SVP_IOQ3) + { + if(SVC_RateLimitAddress(from, 10, 1000)) + { + SV_WriteAttackLog(va("Bad direct connect - rate limit from %s exceeded, dropping request\n", + NET_AdrToString(from))); + return; + } + } + + Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); + + version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); + if ( version != PROTOCOL_VERSION && version != 70 && version != 69 ) { + NET_OutOfBandPrint(NS_SERVER, from, "print\nServer uses either protocol version %i, 70 or 69 " + "(yours is %i).\n", PROTOCOL_VERSION, version); + Com_DPrintf(" rejected connect from version %i\n", version); + return; + } + + challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); + qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); + + // quick reject + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + if (( svs.time - cl->lastConnectTime) + < (sv_reconnectlimit->integer * 1000)) { + Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); + return; + } + break; + } + } + + // don't let "ip" overflow userinfo string + if ( NET_IsLocalAddress (from) ) + ip = "localhost"; + else + ip = (char *)NET_AdrToString( from ); + if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { + NET_OutOfBandPrint( NS_SERVER, from, + "print\nUserinfo string length exceeded. " + "Try removing setu cvars from your config.\n" ); + return; + } + Info_SetValueForKey( userinfo, "ip", ip ); + + // see if the challenge is valid (LAN clients don't need to challenge) + if (!NET_IsLocalAddress(from)) + { + int ping; + challenge_t *challengeptr; + + for (i=0; iinteger ) + { + challenge2 = Info_ValueForKey( userinfo, "challenge2" ); + if ( !Q_stricmp( challenge2, svs.challenges[i].challenge2 ) ) + { + challenge2Verified = true; + } + } + + challengeptr = &svs.challenges[i]; + + if(challengeptr->wasrefused) + { + // Return silently, so that error messages written by the server keep being displayed. + return; + } + + ping = svs.time - challengeptr->pingTime; + + // never reject a LAN client based on ping + if ( !Sys_IsLANAddress( from ) ) + { + if ( sv_minPing->value && ping < sv_minPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); + Com_DPrintf ("Client %i rejected on a too low ping\n", i); + challengeptr->wasrefused = true; + return; + } + if ( sv_maxPing->value && ping > sv_maxPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); + Com_DPrintf ("Client %i rejected on a too high ping\n", i); + challengeptr->wasrefused = true; + return; + } + } + + Com_Printf("Client %i connecting with %i challenge ping\n", i, ping); + challengeptr->connected = true; + } + + // ignore any fingerprint set by the client + char fingerprint[SHA256_DIGEST_SIZE * 2 + 1]; + Info_RemoveKey(userinfo, "fingerprint"); + fingerprint[0] = '\0'; + + if ( sv_rsaAuth->integer && (NET_IsLocalAddress(from) || challenge2Verified) ) + { + if ( SV_RSA_VerifySignature(Cmd_Argv(2), Cmd_Argv(3), Cmd_Argv(1), fingerprint) ) + { + if( strlen(fingerprint) + strlen(userinfo) + 13 >= MAX_INFO_STRING ) + { + NET_OutOfBandPrint( NS_SERVER, from, "print\nUserinfo string length exceeded.\n" ); + return; + } + Info_SetValueForKey( userinfo, "fingerprint", fingerprint ); + Com_DPrintf( "Public key fingerprint: %s\n", fingerprint ); + } + } + + newcl = &temp; + ::memset(newcl, 0, sizeof(client_t)); + + // if there is already a slot for this ip, reuse it + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) + { + if ( cl->state == CS_FREE ) + continue; + + if ( NET_CompareBaseAdr(from, cl->netchan.remoteAddress) + && (cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port) ) + { + Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); + newcl = cl; + + // this doesn't work because it nukes the players userinfo + // disconnect the client from the game first so any flags the + // player might have are dropped + // VM_Call( sv.gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); + goto gotnewcl; + } + } + + // find a client slot + // if "sv_privateClients" is set > 0, then that number + // of client slots will be reserved for connections that + // have "password" set to the value of "sv_privatePassword" + // Info requests will report the maxclients as if the private + // slots didn't exist, to prevent people from trying to connect + // to a full server. + // This is to allow us to reserve a couple slots here on our + // servers so we can play without having to kick people. + + // check for privateClient password + password = Info_ValueForKey( userinfo, "password" ); + if ( *password && !strcmp( password, sv_privatePassword->string ) ) { + startIndex = 0; + } else { + // skip past the reserved slots + startIndex = sv_privateClients->integer; + } + + newcl = NULL; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if (cl->state == CS_FREE) { + newcl = cl; + break; + } + } + + if ( !newcl ) { + if ( NET_IsLocalAddress( from ) ) { + Com_Error( ERR_FATAL, "server is full on local connect" ); + return; + } + else { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full\n" ); + Com_DPrintf ("Rejected a connection.\n"); + return; + } + } + + // we got a newcl, so reset the reliableSequence and reliableAcknowledge + cl->reliableAcknowledge = 0; + cl->reliableSequence = 0; + +gotnewcl: + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + *newcl = temp; + clientNum = newcl - svs.clients; + ent = SV_GentityNum( clientNum ); + newcl->gentity = ent; + + Cvar_Set( va( "sv_clAltProto%i", clientNum ), ( version == 69 ? "2" : version == 70 ? "1" : "0" ) ); + + // save the challenge + newcl->challenge = challenge; + + // save the address + Netchan_Setup((version == 69 ? 2 : version == 70 ? 1 : 0), NS_SERVER, &newcl->netchan, from, qport, challenge); + // init the netchan queue + newcl->netchan_end_queue = &newcl->netchan_start_queue; + + // save the fingerprint + Q_strncpyz( newcl->fingerprint, fingerprint, sizeof(newcl->fingerprint) ); + + // save the userinfo + Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); + + // get the game a chance to reject this connection or modify the userinfo + denied = VM_Call( sv.gvm, GAME_CLIENT_CONNECT, clientNum, true ); // firstTime = true + if ( denied ) { + // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call + char *str = (char*)VM_ExplicitArgPtr( sv.gvm, denied ); + + NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", str ); + Com_DPrintf ("Game rejected a connection: %s.\n", str); + return; + } + + SV_UserinfoChanged( newcl ); + + // send the connect packet to the client + NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %d", challenge); + + Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); + + newcl->state = CS_CONNECTED; + newcl->lastSnapshotTime = 0; + newcl->lastPacketTime = svs.time; + newcl->lastConnectTime = svs.time; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + newcl->gamestateMessageNum = -1; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( svs.clients[i].state >= CS_CONNECTED ) + count++; + } + if ( count == 1 || count == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + +/* +===================== +SV_FreeClient + +Destructor for data allocated in a client structure +===================== +*/ +void SV_FreeClient(client_t *client) +{ +#ifdef USE_VOIP + int index; + + for(index = client->queuedVoipIndex; index < client->queuedVoipPackets; index++) + { + index %= ARRAY_LEN(client->voipPacket); + + Z_Free(client->voipPacket[index]); + } + + client->queuedVoipPackets = 0; +#endif + + SV_Netchan_FreeQueue(client); + SV_CloseDownload(client); +} + +/* +===================== +SV_DropClient + +Called when the player is totally leaving the server, either willingly +or unwillingly. This is NOT called if the entire server is quiting +or crashing -- SV_FinalMessage() will handle that +===================== +*/ +void SV_DropClient( client_t *drop, const char *reason ) { + int i; + challenge_t *challenge; + + if ( drop->state == CS_ZOMBIE ) { + return; // already dropped + } + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { + ::memset(challenge, 0, sizeof(*challenge)); + break; + } + } + + // Free all allocated data on the client structure + SV_FreeClient(drop); + + // tell everyone why they got dropped + SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason ); + + // call the prog function for removing a client + // this will remove the body, among other things + VM_Call( sv.gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients ); + + // add the disconnect command + SV_SendServerCommand( drop, "disconnect \"%s\"", reason); + + // nuke user info + SV_SetUserinfo( drop - svs.clients, "" ); + + Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); + drop->state = CS_ZOMBIE; // become free in a few seconds + + // if this was the last client on the server, send a heartbeat + // to the master so it is known the server is empty + // send a heartbeat now so the master will get up to date info + // if there is already a slot for this ip, reuse it + for (i=0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + break; + } + } + if ( i == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + +extern char alternateInfos[2][2][BIG_INFO_STRING]; + +/* +================ +SV_SendClientGameState + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each new map load. + +It will be resent if the client acknowledges a later message but has +the wrong gamestate. +================ +*/ +static void SV_SendClientGameState( client_t *client ) { + int start; + entityState_t *base, nullstate; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + const char *configstring; + + Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); + Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); + client->state = CS_PRIMED; + client->pureAuthentic = 0; + client->gotCP = false; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + client->gamestateMessageNum = client->netchan.outgoingSequence; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the gamestate + MSG_WriteByte( &msg, svc_gamestate ); + MSG_WriteLong( &msg, client->reliableSequence ); + + // write the configstrings + for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { + if ( start <= CS_SYSTEMINFO && client->netchan.alternateProtocol != 0 ) { + configstring = alternateInfos[start][ client->netchan.alternateProtocol - 1 ]; + } else { + configstring = sv.configstrings[start].s; + } + + if (configstring[0]) { + MSG_WriteByte( &msg, svc_configstring ); + MSG_WriteShort( &msg, start ); + MSG_WriteBigString( &msg, configstring ); + } + } + + // write the baselines + ::memset( &nullstate, 0, sizeof( nullstate ) ); + for ( start = 0 ; start < MAX_GENTITIES; start++ ) { + base = &sv.svEntities[start].baseline; + if ( !base->number ) { + continue; + } + MSG_WriteByte( &msg, svc_baseline ); + MSG_WriteDeltaEntity( client->netchan.alternateProtocol, &msg, &nullstate, base, true ); + } + + MSG_WriteByte( &msg, svc_EOF ); + + MSG_WriteLong( &msg, client - svs.clients); + + // write the checksum feed + MSG_WriteLong( &msg, sv.checksumFeed); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + + +/* +================== +SV_ClientEnterWorld +================== +*/ +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { + int clientNum; + sharedEntity_t *ent; + + Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); + client->state = CS_ACTIVE; + + // resend all configstrings using the cs commands since these are + // no longer sent when the client is CS_PRIMED + SV_UpdateConfigstrings( client ); + + // set up the entity for the client + clientNum = client - svs.clients; + ent = SV_GentityNum( clientNum ); + ent->s.number = clientNum; + client->gentity = ent; + + client->deltaMessage = -1; + client->lastSnapshotTime = 0; // generate a snapshot immediately + + if(cmd) + memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd)); + else + memset(&client->lastUsercmd, '\0', sizeof(client->lastUsercmd)); + + // call the game begin function + VM_Call( sv.gvm, GAME_CLIENT_BEGIN, client - svs.clients ); +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ + +/* +================== +SV_CloseDownload + +clear/free any download vars +================== +*/ +static void SV_CloseDownload( client_t *cl ) { + int i; + + // EOF + if (cl->download) { + FS_FCloseFile( cl->download ); + } + cl->download = 0; + *cl->downloadName = 0; + + // Free the temporary buffer space + for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { + if (cl->downloadBlocks[i]) { + Z_Free(cl->downloadBlocks[i]); + cl->downloadBlocks[i] = NULL; + } + } + +} + +/* +================== +SV_StopDownload_f + +Abort a download if in progress +================== +*/ +static void SV_StopDownload_f( client_t *cl ) { + if (*cl->downloadName) + Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", (int) (cl - svs.clients), cl->downloadName ); + + SV_CloseDownload( cl ); +} + +/* +================== +SV_DoneDownload_f + +Downloads are finished +================== +*/ +static void SV_DoneDownload_f( client_t *cl ) { + if ( cl->state == CS_ACTIVE ) + return; + + Com_DPrintf( "clientDownload: %s Done\n", cl->name); + // resend the game state to update any clients that entered during the download + SV_SendClientGameState(cl); +} + +/* +================== +SV_NextDownload_f + +The argument will be the last acknowledged block from the client, it should be +the same as cl->downloadClientBlock +================== +*/ +static void SV_NextDownload_f( client_t *cl ) +{ + int block = atoi( Cmd_Argv(1) ); + + if (block == cl->downloadClientBlock) { + Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", (int) (cl - svs.clients), block ); + + // Find out if we are done. A zero-length block indicates EOF + if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) { + Com_Printf( "clientDownload: %d : file \"%s\" completed\n", (int) (cl - svs.clients), cl->downloadName ); + SV_CloseDownload( cl ); + return; + } + + cl->downloadSendTime = svs.time; + cl->downloadClientBlock++; + return; + } + // We aren't getting an acknowledge for the correct block, drop the client + // FIXME: this is bad... the client will never parse the disconnect message + // because the cgame isn't loaded yet + SV_DropClient( cl, "broken download" ); +} + +/* +================== +SV_BeginDownload_f +================== +*/ +static void SV_BeginDownload_f( client_t *cl ) { + + // Kill any existing download + SV_CloseDownload( cl ); + + // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open + // the file itself + Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) ); +} + +/* +================== +SV_WriteDownloadToClient + +Check to see if the client wants a file, open it if needed and start pumping the client +Fill up msg with data, return number of download blocks added +================== +*/ +int SV_WriteDownloadToClient(client_t *cl, msg_t *msg) +{ + int curindex; + int unreferenced = 1; + char errorMessage[1024]; + char pakbuf[MAX_QPATH], *pakptr; + int numRefPaks; + + if (!*cl->downloadName) + return 0; // Nothing being downloaded + + if(!cl->download) + { + // Chop off filename extension. + Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); + pakptr = strrchr(pakbuf, '.'); + + if(pakptr) + { + *pakptr = '\0'; + + // Check for pk3 filename extension + if(!Q_stricmp(pakptr + 1, "pk3")) + { + const char *referencedPaks = FS_ReferencedPakNames( cl->netchan.alternateProtocol == 2 ); + + // Check whether the file appears in the list of referenced + // paks to prevent downloading of arbitrary files. + Cmd_TokenizeStringIgnoreQuotes(referencedPaks); + numRefPaks = Cmd_Argc(); + + for(curindex = 0; curindex < numRefPaks; curindex++) + { + if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf)) + { + unreferenced = 0; + break; + } + } + } + } + + cl->download = 0; + + // We open the file here + if ( !(sv_allowDownload->integer & DLF_ENABLE) || + (sv_allowDownload->integer & DLF_NO_UDP) || + unreferenced || + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { + // cannot auto-download file + if(unreferenced) + { + Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", (int) (cl - svs.clients), cl->downloadName); + Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName); + } + else if ( !(sv_allowDownload->integer & DLF_ENABLE) || + (sv_allowDownload->integer & DLF_NO_UDP) ) { + + Com_Printf("clientDownload: %d : \"%s\" download disabled\n", (int) (cl - svs.clients), cl->downloadName); + if (sv_pure->integer) { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "You will need to get this file elsewhere before you " + "can connect to this pure server.\n", cl->downloadName); + } else { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "The server you are connecting to is not a pure server, " + "set autodownload to No in your settings and you might be " + "able to join the game anyway.\n", cl->downloadName); + } + } else { + // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? + // if the pk3 is referenced, it must have been found somewhere in the filesystem + Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", (int) (cl - svs.clients), cl->downloadName); + Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); + } + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, 0 ); // client is expecting block zero + MSG_WriteLong( msg, -1 ); // illegal file size + MSG_WriteString( msg, errorMessage ); + + *cl->downloadName = 0; + + if(cl->download) + FS_FCloseFile(cl->download); + + return 1; + } + + Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); + + // Init + cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; + cl->downloadCount = 0; + cl->downloadEOF = false; + } + + // Perform any reads that we need to + while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && + cl->downloadSize != cl->downloadCount) { + + curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); + + if (!cl->downloadBlocks[curindex]) + cl->downloadBlocks[curindex] = (unsigned char*)Z_Malloc(MAX_DOWNLOAD_BLKSIZE); + + cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); + + if (cl->downloadBlockSize[curindex] < 0) { + // EOF right now + cl->downloadCount = cl->downloadSize; + break; + } + + cl->downloadCount += cl->downloadBlockSize[curindex]; + + // Load in next block + cl->downloadCurrentBlock++; + } + + // Check to see if we have eof condition and add the EOF block + if (cl->downloadCount == cl->downloadSize + && !cl->downloadEOF + && cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) + { + cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; + cl->downloadCurrentBlock++; + + cl->downloadEOF = true; // We have added the EOF block + } + + if (cl->downloadClientBlock == cl->downloadCurrentBlock) + return 0; // Nothing to transmit + + // Write out the next section of the file, if we have already reached our window, + // automatically start retransmitting + if (cl->downloadXmitBlock == cl->downloadCurrentBlock) + { + // We have transmitted the complete window, should we start resending? + if (svs.time - cl->downloadSendTime > 1000) + cl->downloadXmitBlock = cl->downloadClientBlock; + else + return 0; + } + + // Send current block + curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); + + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, cl->downloadXmitBlock ); + + // block zero is special, contains file size + if ( cl->downloadXmitBlock == 0 ) + MSG_WriteLong( msg, cl->downloadSize ); + + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); + + // Write the block + if(cl->downloadBlockSize[curindex]) + MSG_WriteData(msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex]); + + Com_DPrintf( "clientDownload: %d : writing block %d\n", (int) (cl - svs.clients), cl->downloadXmitBlock ); + + // Move on to the next block + // It will get sent with next snap shot. The rate will keep us in line. + cl->downloadXmitBlock++; + cl->downloadSendTime = svs.time; + + return 1; +} + +/* +================== +SV_SendQueuedMessages + +Send one round of fragments, or queued messages to all clients that have data pending. +Return the shortest time interval for sending next packet to client +================== +*/ + +int SV_SendQueuedMessages(void) +{ + int i, retval = -1, nextFragT; + client_t *cl; + + for(i=0; i < sv_maxclients->integer; i++) + { + cl = &svs.clients[i]; + + if(cl->state) + { + nextFragT = SV_RateMsec(cl); + + if(!nextFragT) + nextFragT = SV_Netchan_TransmitNextFragment(cl); + + if(nextFragT >= 0 && (retval == -1 || retval > nextFragT)) + retval = nextFragT; + } + } + + return retval; +} + + +/* +================== +SV_SendDownloadMessages + +Send one round of download messages to all clients +================== +*/ + +int SV_SendDownloadMessages(void) +{ + int i, numDLs = 0, retval; + client_t *cl; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + for(i=0; i < sv_maxclients->integer; i++) + { + cl = &svs.clients[i]; + + if(cl->state && *cl->downloadName) + { + MSG_Init(&msg, msgBuffer, sizeof(msgBuffer)); + MSG_WriteLong(&msg, cl->lastClientCommand); + + retval = SV_WriteDownloadToClient(cl, &msg); + + if(retval) + { + MSG_WriteByte(&msg, svc_EOF); + SV_Netchan_Transmit(cl, &msg); + numDLs += retval; + } + } + } + + return numDLs; +} + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately FIXME: move to game? +================= +*/ +static void SV_Disconnect_f( client_t *cl ) { + SV_DropClient( cl, "disconnected" ); +} + +/* +================= +SV_VerifyPaks_f + +If we are pure, disconnect the client if they do no meet the following conditions: + +1. the first two checksums match our view of cgame and ui +2. there are no any additional checksums that we do not have + +This routine would be a bit simpler with a goto but i abstained + +================= +*/ +static void SV_VerifyPaks_f( client_t *cl ) { + int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg; + int nClientChkSum[1024]; + int nServerChkSum[1024]; + const char *pPaks, *pArg; + bool bGood = true; + + // if we are pure, we "expect" the client to load certain things from + // certain pk3 files, namely we want the client to have loaded the + // ui and cgame that we think should be loaded based on the pure setting + // + if ( sv_pure->integer != 0 ) { + + nChkSum1 = nChkSum2 = 0; + // we run the game, so determine which cgame and ui the client "should" be running + bGood = (FS_FileIsInPAK_A((cl->netchan.alternateProtocol == 2), "vm/cgame.qvm", &nChkSum1) == 1); + if (bGood) + bGood = (FS_FileIsInPAK_A((cl->netchan.alternateProtocol == 2), "vm/ui.qvm", &nChkSum2) == 1); + + nClientPaks = Cmd_Argc(); + + // start at arg 2 ( skip serverId cl_paks ) + nCurArg = 1; + + pArg = Cmd_Argv(nCurArg++); + if(!pArg) { + bGood = false; + } + else + { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore + // since serverId is a frame count, it always goes up + if (atoi(pArg) < sv.checksumFeedServerId) + { + Com_DPrintf("ignoring outdated cp command from client %s\n", cl->name); + return; + } + } + + // we basically use this while loop to avoid using 'goto' :) + while (bGood) { + + // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums" + // numChecksums is encoded + if (nClientPaks < 6) { + bGood = false; + break; + } + // verify first to be the cgame checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) { + bGood = false; + break; + } + // verify the second to be the ui checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) { + bGood = false; + break; + } + // should be sitting at the delimeter now + pArg = Cmd_Argv(nCurArg++); + if (*pArg != '@') { + bGood = false; + break; + } + // store checksums since tokenization is not re-entrant + for (i = 0; nCurArg < nClientPaks; i++) { + nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++)); + } + + // store number to compare against (minus one cause the last is the number of checksums) + nClientPaks = i - 1; + + // make sure none of the client check sums are the same + // so the client can't send 5 the same checksums + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nClientPaks; j++) { + if (i == j) + continue; + if (nClientChkSum[i] == nClientChkSum[j]) { + bGood = false; + break; + } + } + if (bGood == false) + break; + } + if (bGood == false) + break; + + // get the pure checksums of the pk3 files loaded by the server + pPaks = FS_LoadedPakPureChecksums(cl->netchan.alternateProtocol == 2); + Cmd_TokenizeString( pPaks ); + nServerPaks = Cmd_Argc(); + if (nServerPaks > 1024) + nServerPaks = 1024; + + for (i = 0; i < nServerPaks; i++) { + nServerChkSum[i] = atoi(Cmd_Argv(i)); + } + + // check if the client has provided any pure checksums of pk3 files not loaded by the server + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nServerPaks; j++) { + if (nClientChkSum[i] == nServerChkSum[j]) { + break; + } + } + if (j >= nServerPaks) { + bGood = false; + break; + } + } + if ( bGood == false ) { + break; + } + + // check if the number of checksums was correct + nChkSum1 = sv.checksumFeed; + for (i = 0; i < nClientPaks; i++) { + nChkSum1 ^= nClientChkSum[i]; + } + nChkSum1 ^= nClientPaks; + if (nChkSum1 != nClientChkSum[nClientPaks]) { + bGood = false; + break; + } + + // break out + break; + } + + cl->gotCP = true; + + if (bGood) { + cl->pureAuthentic = 1; + } + else { + cl->pureAuthentic = 0; + cl->lastSnapshotTime = 0; + cl->state = CS_ACTIVE; + SV_SendClientSnapshot( cl ); + SV_SendServerCommand( cl, "disconnect \"Unpure Client. " + "You may need to enable in-game downloads " + "to connect to this server (set " + "cl_allowDownload 1)\"" ); + SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); + } + } +} + +/* +================= +SV_ResetPureClient_f +================= +*/ +static void SV_ResetPureClient_f( client_t *cl ) { + cl->pureAuthentic = 0; + cl->gotCP = false; +} + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C friendly form. +================= +*/ +void SV_UserinfoChanged( client_t *cl ) { + char *val; + const char *ip; + int i; + int len; + + // name for C code + Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); + + // rate command + + // if the client is on the same subnet as the server and we aren't running an + // internet public server, assume they don't need a rate choke + if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) { + cl->rate = 99999; // lans should not rate limit + } else { + val = Info_ValueForKey (cl->userinfo, "rate"); + if (strlen(val)) { + i = atoi(val); + cl->rate = i; + if (cl->rate < 1000) { + cl->rate = 1000; + } else if (cl->rate > 90000) { + cl->rate = 90000; + } + } else { + cl->rate = 3000; + } + } + val = Info_ValueForKey (cl->userinfo, "handicap"); + if (strlen(val)) { + i = atoi(val); + if (i<=0 || i>100 || strlen(val) > 4) { + Info_SetValueForKey( cl->userinfo, "handicap", "100" ); + } + } + + // snaps command + val = Info_ValueForKey (cl->userinfo, "snaps"); + + if(strlen(val)) + { + i = atoi(val); + + if(i < 1) + i = 1; + else if(i > sv_fps->integer) + i = sv_fps->integer; + + i = 1000 / i; + } + else + i = 50; + + if(i != cl->snapshotMsec) + { + // Reset last sent snapshot so we avoid desync between server frame time and snapshot send time + cl->lastSnapshotTime = 0; + cl->snapshotMsec = i; + } + +#ifdef USE_VOIP + val = Info_ValueForKey(cl->userinfo, "cl_voipProtocol"); + cl->hasVoip = !Q_stricmp( val, "opus" ); +#endif + + // TTimo + // maintain the IP information + // the banning code relies on this being consistently present + if( NET_IsLocalAddress(cl->netchan.remoteAddress) ) + ip = "localhost"; + else + ip = (char*)NET_AdrToString( cl->netchan.remoteAddress ); + + val = Info_ValueForKey( cl->userinfo, "ip" ); + if( val[0] ) + len = strlen( ip ) - strlen( val ) + strlen( cl->userinfo ); + else + len = strlen( ip ) + 4 + strlen( cl->userinfo ); + + if( len >= MAX_INFO_STRING ) + SV_DropClient( cl, "userinfo string length exceeded" ); + else + Info_SetValueForKey( cl->userinfo, "ip", ip ); + + val = Info_ValueForKey( cl->userinfo, "fingerprint" ); + if( val[0] ) + len = strlen(cl->fingerprint) - strlen(val) + strlen(cl->userinfo); + else + len = strlen(cl->fingerprint) + 13 + strlen(cl->userinfo); + + if( len >= MAX_INFO_STRING ) + SV_DropClient( cl, "userinfo string length exceeded" ); + else + Info_SetValueForKey( cl->userinfo, "fingerprint", cl->fingerprint ); +} + + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static void SV_UpdateUserinfo_f( client_t *cl ) { + Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); + + SV_UserinfoChanged( cl ); + // call prog code to allow overrides + VM_Call( sv.gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); +} + + +#ifdef USE_VOIP +static void SV_UpdateVoipIgnore(client_t *cl, const char *idstr, bool ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) { + cl->ignoreVoipFromClient[id] = ignore; + } + } +} + +/* +================== +SV_Voip_f +================== +*/ +static void SV_Voip_f( client_t *cl ) +{ + const char *cmd = Cmd_Argv(1); + if (strcmp(cmd, "ignore") == 0) { + SV_UpdateVoipIgnore(cl, Cmd_Argv(2), true); + } else if (strcmp(cmd, "unignore") == 0) { + SV_UpdateVoipIgnore(cl, Cmd_Argv(2), false); + } else if (strcmp(cmd, "muteall") == 0) { + cl->muteAllVoip = true; + } else if (strcmp(cmd, "unmuteall") == 0) { + cl->muteAllVoip = false; + } +} +#endif + + +typedef struct { + const char *name; + void (*func)( client_t *cl ); +} ucmd_t; + +static ucmd_t ucmds[] = { + {"userinfo", SV_UpdateUserinfo_f}, + {"disconnect", SV_Disconnect_f}, + {"cp", SV_VerifyPaks_f}, + {"vdr", SV_ResetPureClient_f}, + {"download", SV_BeginDownload_f}, + {"nextdl", SV_NextDownload_f}, + {"stopdl", SV_StopDownload_f}, + {"donedl", SV_DoneDownload_f}, + +#ifdef USE_VOIP + {"voip", SV_Voip_f}, +#endif + + {NULL, NULL} +}; + +/* +================== +SV_ExecuteClientCommand + +Also called by bot code +================== +*/ +void SV_ExecuteClientCommand( client_t *cl, const char *s, bool clientOK ) { + ucmd_t *u; + bool bProcessed = false; + + Cmd_TokenizeString( s ); + + // see if it is a server level command + for (u=ucmds ; u->name ; u++) { + if (!strcmp (Cmd_Argv(0), u->name) ) { + u->func( cl ); + bProcessed = true; + break; + } + } + + if (clientOK) { + // pass unknown strings to the game + if (!u->name && sv.state == SS_GAME && (cl->state == CS_ACTIVE || cl->state == CS_PRIMED)) { + VM_Call( sv.gvm, GAME_CLIENT_COMMAND, cl - svs.clients ); + } + } + else if (!bProcessed) + Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv(0) ); +} + +/* +=============== +SV_ClientCommand +=============== +*/ +static bool SV_ClientCommand( client_t *cl, msg_t *msg ) { + int seq; + const char *s; + bool clientOk = true; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed it + if ( cl->lastClientCommand >= seq ) { + return true; + } + + Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); + + // drop the connection if we have somehow lost commands + if ( seq > cl->lastClientCommand + 1 ) { + Com_Printf( "Client %s lost %i clientCommands\n", cl->name, + seq - cl->lastClientCommand + 1 ); + SV_DropClient( cl, "Lost reliable commands" ); + return false; + } + + // malicious users may try using too many string commands + // to lag other players. If we decide that we want to stall + // the command, we will stop processing the rest of the packet, + // including the usercmd. This causes flooders to lag themselves + // but not other people + // We don't do this when the client hasn't been active yet since it's + // normal to spam a lot of commands when downloading +#if 0 // flood protection in game for trem + if ( !com_cl_running->integer && + cl->state >= CS_ACTIVE && + sv_floodProtect->integer && + svs.time < cl->nextReliableTime ) { + // ignore any other text messages from this client but let them keep playing + // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept + clientOk = false; + } +#endif + + // don't allow another command for one second + cl->nextReliableTime = svs.time + 1000; + + SV_ExecuteClientCommand( cl, s, clientOk ); + + cl->lastClientCommand = seq; + Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s); + + return true; // continue procesing +} + + +//================================================================================== + + +/* +================== +SV_ClientThink + +Also called by bot code +================== +*/ +void SV_ClientThink (client_t *cl, usercmd_t *cmd) { + cl->lastUsercmd = *cmd; + + if ( cl->state != CS_ACTIVE ) { + return; // may have been kicked during the last usercmd + } + + VM_Call( sv.gvm, GAME_CLIENT_THINK, cl - svs.clients ); +} + +/* +================== +SV_UserMove + +The message usually contains all the movement commands +that were in the last three packets, so that the information +in dropped packets can be recovered. + +On very fast clients, there may be multiple usercmd packed into +each of the backup packets. +================== +*/ +static void SV_UserMove( client_t *cl, msg_t *msg, bool delta ) { + int i, key; + int cmdCount; + usercmd_t nullcmd; + usercmd_t cmds[MAX_PACKET_USERCMDS]; + usercmd_t *cmd, *oldcmd; + + if ( delta ) { + cl->deltaMessage = cl->messageAcknowledge; + } else { + cl->deltaMessage = -1; + } + + cmdCount = MSG_ReadByte( msg ); + + if ( cmdCount < 1 ) { + Com_Printf( "cmdCount < 1\n" ); + return; + } + + if ( cmdCount > MAX_PACKET_USERCMDS ) { + Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); + return; + } + + // use the checksum feed in the key + key = sv.checksumFeed; + // also use the message acknowledge + key ^= cl->messageAcknowledge; + // also use the last acknowledged server command in the key + key ^= MSG_HashKey(cl->netchan.alternateProtocol, cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); + + ::memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + for ( i = 0 ; i < cmdCount ; i++ ) { + cmd = &cmds[i]; + MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd ); + oldcmd = cmd; + } + + // save time for ping calculation + cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; + + // TTimo + // catch the no-cp-yet situation before SV_ClientEnterWorld + // if CS_ACTIVE, then it's time to trigger a new gamestate emission + // if not, then we are getting remaining parasite usermove commands, which we should ignore + if (sv_pure->integer != 0 && cl->pureAuthentic == 0 && !cl->gotCP) { + if (cl->state == CS_ACTIVE) + { + // we didn't get a cp yet, don't assume anything and just send the gamestate all over again + Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name); + SV_SendClientGameState( cl ); + } + return; + } + + // if this is the first usercmd we have received + // this gamestate, put the client into the world + if ( cl->state == CS_PRIMED ) { + SV_ClientEnterWorld( cl, &cmds[0] ); + // the moves can be processed normaly + } + + // a bad cp command was sent, drop the client + if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { + SV_DropClient( cl, "Cannot validate pure client!"); + return; + } + + if ( cl->state != CS_ACTIVE ) { + cl->deltaMessage = -1; + return; + } + + // usually, the first couple commands will be duplicates + // of ones we have previously received, but the servertimes + // in the commands will cause them to be immediately discarded + for ( i = 0 ; i < cmdCount ; i++ ) { + // if this is a cmd from before a map_restart ignore it + if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) { + continue; + } + // extremely lagged or cmd from before a map_restart + //if ( cmds[i].serverTime > svs.time + 3000 ) { + // continue; + //} + // don't execute if this is an old cmd which is already executed + // these old cmds are included when cl_packetdup > 0 + if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { + continue; + } + SV_ClientThink (cl, &cmds[ i ]); + } +} + + +#ifdef USE_VOIP +/* +================== +SV_ShouldIgnoreVoipSender + +Blocking of voip packets based on source client +================== +*/ + +static bool SV_ShouldIgnoreVoipSender(const client_t *cl) +{ + if (!sv_voip->integer) + return true; // VoIP disabled on this server. + else if (!cl->hasVoip) // client doesn't have VoIP support?! + return true; + + // !!! FIXME: implement player blacklist. + + return false; // don't ignore. +} + +static void SV_UserVoip(client_t *cl, msg_t *msg, bool ignoreData) +{ + int sender, generation, sequence, frames; + uint8_t recips[(MAX_CLIENTS + 7) / 8]; + int recip1 = 0, recip2 = 0, recip3 = 0; // silence warning + int flags = 0; + byte encoded[sizeof(cl->voipPacket[0]->data)]; + client_t *client = NULL; + voipServerPacket_t *packet = NULL; + int i; + + sender = cl - svs.clients; + generation = MSG_ReadByte(msg); + sequence = MSG_ReadLong(msg); + frames = MSG_ReadByte(msg); + if (cl->netchan.alternateProtocol == 0) { + MSG_ReadData(msg, recips, sizeof(recips)); + flags = MSG_ReadByte(msg); + } else { + recip1 = MSG_ReadLong(msg); + recip2 = MSG_ReadLong(msg); + recip3 = MSG_ReadLong(msg); + } + size_t packetsize = MSG_ReadShort(msg); + + if (msg->readcount > msg->cursize) + return; // short/invalid packet, bail. + + if (packetsize > sizeof(encoded)) { // overlarge packet? + size_t bytesleft = packetsize; + while (bytesleft) { + size_t br = bytesleft; + if (br > sizeof(encoded)) + br = sizeof(encoded); + MSG_ReadData(msg, encoded, br); + bytesleft -= br; + } + return; // overlarge packet, bail. + } + + MSG_ReadData(msg, encoded, packetsize); + + if (ignoreData || SV_ShouldIgnoreVoipSender(cl)) + return; // Blacklisted, disabled, etc. + + // !!! FIXME: see if we read past end of msg... + + // !!! FIXME: reject if not opus data. + // !!! FIXME: decide if this is bogus data? + + // decide who needs this VoIP packet sent to them... + for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + if (client->state != CS_ACTIVE) + continue; // not in the game yet, don't send to this guy. + else if (i == sender) + continue; // don't send voice packet back to original author. + else if (!client->hasVoip) + continue; // no VoIP support, or unsupported protocol + else if (client->muteAllVoip) + continue; // client is ignoring everyone. + else if (client->ignoreVoipFromClient[sender]) + continue; // client is ignoring this talker. + else if (*cl->downloadName) // !!! FIXME: possible to DoS? + continue; // no VoIP allowed if downloading, to save bandwidth. + + if (cl->netchan.alternateProtocol == 0) { + if(Com_IsVoipTarget(recips, sizeof(recips), i)) + flags |= VOIP_DIRECT; + else + flags &= ~VOIP_DIRECT; + } else { + if (i < 31 && (recip1 & (1 << (i - 0))) == 0) + continue; // not addressed to this player. + else if (i >= 31 && i < 62 && (recip2 & (1 << (i - 31))) == 0) + continue; // not addressed to this player. + else if (i >= 62 && (recip3 & (1 << (i - 62))) == 0) + continue; // not addressed to this player. + + flags |= VOIP_DIRECT; + } + + if (!(flags & (VOIP_SPATIAL | VOIP_DIRECT))) + continue; // not addressed to this player. + + // Transmit this packet to the client. + if (client->queuedVoipPackets >= ARRAY_LEN(client->voipPacket)) { + Com_Printf("Too many VoIP packets queued for client #%d\n", i); + continue; // no room for another packet right now. + } + + packet = (voipServerPacket_t*)Z_Malloc(sizeof(*packet)); + packet->sender = sender; + packet->frames = frames; + packet->len = packetsize; + packet->generation = generation; + packet->sequence = sequence; + packet->flags = flags; + memcpy(packet->data, encoded, packetsize); + + client->voipPacket[(client->queuedVoipIndex + client->queuedVoipPackets) % ARRAY_LEN(client->voipPacket)] = packet; + client->queuedVoipPackets++; + } +} +#endif + + + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { + int c; + int serverId; + + MSG_Bitstream(msg); + + serverId = MSG_ReadLong( msg ); + cl->messageAcknowledge = MSG_ReadLong( msg ); + + if (cl->messageAcknowledge < 0) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + return; + } + + cl->reliableAcknowledge = MSG_ReadLong( msg ); + + // NOTE: when the client message is fux0red the acknowledgement numbers + // can be out of range, this could cause the server to send thousands of server + // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient + if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + cl->reliableAcknowledge = cl->reliableSequence; + return; + } + // if this is a usercmd from a previous gamestate, + // ignore it or retransmit the current gamestate + // + // if the client was downloading, let it stay at whatever serverId and + // gamestate it was at. This allows it to keep downloading even when + // the gamestate changes. After the download is finished, we'll + // notice and send it a new game state + // + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 + // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" + // but we still need to read the next message to move to next download or send gamestate + // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else + if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { + if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart + // they just haven't caught the map_restart yet + Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); + return; + } + // if we can tell that the client has dropped the last + // gamestate we sent them, resend it + if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { + Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); + SV_SendClientGameState( cl ); + } + return; + } + + // this client has acknowledged the new gamestate so it's + // safe to start sending it the real time again + if( cl->oldServerTime && serverId == sv.serverId ){ + Com_DPrintf( "%s acknowledged gamestate\n", cl->name ); + cl->oldServerTime = 0; + } + + // read optional clientCommand strings + do { + c = MSG_ReadByte( msg ); + + if ( cl->netchan.alternateProtocol != 0 ) { + // See if this is an extension command after the EOF, which means we + // got data that a legacy server should ignore. + if ( c == clc_EOF && MSG_LookaheadByte( msg ) == clc_voipSpeex ) { + MSG_ReadByte( msg ); // throw the clc_extension byte away. + c = MSG_ReadByte( msg ); // something legacy servers can't do! + if ( c == clc_voipSpeex + 1 ) { + c = clc_voipSpeex; + } + // sometimes you get a clc_extension at end of stream...dangling + // bits in the huffman decoder giving a bogus value? + if ( c == -1 ) { + c = clc_EOF; + } + } + + if ( c == svc_voipSpeex ) { + c = svc_voipSpeex + 1; + } else if ( c == svc_voipSpeex + 1 ) { + c = svc_voipSpeex; + } + } + + if ( c == clc_EOF ) { + break; + } + + if ( c != clc_clientCommand ) { + break; + } + if ( !SV_ClientCommand( cl, msg ) ) { + return; // we couldn't execute it because of the flood protection + } + if (cl->state == CS_ZOMBIE) { + return; // disconnect command + } + } while ( 1 ); + + // skip legacy speex voip data + if ( c == clc_voipSpeex ) { +#ifdef USE_VOIP + SV_UserVoip( cl, msg, true ); + c = MSG_ReadByte( msg ); +#endif + } + + // read optional voip data + if ( c == clc_voipOpus ) { +#ifdef USE_VOIP + SV_UserVoip( cl, msg, false ); + c = MSG_ReadByte( msg ); +#endif + } + + // read the usercmd_t + if ( c == clc_move ) { + SV_UserMove( cl, msg, true ); + } else if ( c == clc_moveNoDelta ) { + SV_UserMove( cl, msg, false ); + } else if ( c != clc_EOF ) { + Com_Printf( "WARNING: bad command byte for client %i\n", (int) (cl - svs.clients) ); + } +// if ( msg->readcount != msg->cursize ) { +// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); +// } +} diff --git a/src/server/sv_game.cpp b/src/server/sv_game.cpp new file mode 100644 index 0000000..23e5212 --- /dev/null +++ b/src/server/sv_game.cpp @@ -0,0 +1,602 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +// sv_game.c -- interface to the game dll + +#include "server.h" + +// these functions must be used instead of pointer arithmetic, because +// the game allocates gentities with private information after the server shared part +int SV_NumForGentity( sharedEntity_t *ent ) { + int num; + + num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; + + return num; +} + +sharedEntity_t *SV_GentityNum( int num ) { + sharedEntity_t *ent; + + ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(num)); + + return ent; +} + +playerState_t *SV_GameClientNum( int num ) { + playerState_t *ps; + + ps = (playerState_t *)((byte *)sv.gameClients + sv.gameClientSize*(num)); + + return ps; +} + +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ) { + if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + return &sv.svEntities[ gEnt->s.number ]; +} + +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { + int num; + + num = svEnt - sv.svEntities; + return SV_GentityNum( num ); +} + +/* +=============== +SV_GameSendServerCommand + +Sends a command string to a client +=============== +*/ +void SV_GameSendServerCommand( int clientNum, const char *text ) { + if ( clientNum == -1 ) { + SV_SendServerCommand( NULL, "%s", text ); + } else { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + } +} + + +/* +=============== +SV_GameDropClient + +Disconnects the client with a message +=============== +*/ +void SV_GameDropClient( int clientNum, const char *reason ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_DropClient( svs.clients + clientNum, reason ); +} + + +/* +================= +SV_SetBrushModel + +sets mins and maxs for inline bmodels +================= +*/ +void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { + clipHandle_t h; + vec3_t mins, maxs; + + if (!name) { + Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); + } + + if (name[0] != '*') { + Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name ); + } + + + ent->s.modelindex = atoi( name + 1 ); + + h = CM_InlineModel( ent->s.modelindex ); + CM_ModelBounds( h, mins, maxs ); + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + ent->r.bmodel = qtrue; + + ent->r.contents = -1; // we don't know exactly what is in the brushes + + SV_LinkEntity( ent ); // FIXME: remove +} + + + +/* +================= +SV_inPVS + +Also checks portalareas so that doors block sight +================= +*/ +bool SV_inPVS (const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + int area1, area2; + byte *mask; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + + if ( mask && !(mask[cluster>>3] & (1<<(cluster&7))) ) + return false; + + if (!CM_AreasConnected (area1, area2)) + return false; // a door blocks sight + + return true; +} + + +/* +================= +SV_inPVSIgnorePortals + +Does NOT check portalareas +================= +*/ +bool SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + byte *mask; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + return false; + + return true; +} + + +/* +======================== +SV_AdjustAreaPortalState +======================== +*/ +void SV_AdjustAreaPortalState( sharedEntity_t *ent, bool open ) { + svEntity_t *svEnt; + + svEnt = SV_SvEntityForGentity( ent ); + if ( svEnt->areanum2 == -1 ) { + return; + } + CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open ); +} + + +/* +================== +SV_EntityContact +================== +*/ +bool SV_EntityContact( vec3_t mins, vec3_t maxs, const sharedEntity_t *gEnt, traceType_t type ) { + const float *origin, *angles; + clipHandle_t ch; + trace_t trace; + + // check for exact collision + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + ch = SV_ClipHandleForEntity( gEnt ); + CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, + ch, -1, origin, angles, type ); + + return trace.startsolid; +} + + +/* +=============== +SV_GetServerinfo + +=============== +*/ +void SV_GetServerinfo( char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize ); + } + Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize ); +} + +/* +=============== +SV_LocateGameData + +=============== +*/ +void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGameClient ) { + sv.gentities = gEnts; + sv.gentitySize = sizeofGEntity_t; + sv.num_entities = numGEntities; + + sv.gameClients = clients; + sv.gameClientSize = sizeofGameClient; +} + + +/* +=============== +SV_GetUsercmd + +=============== +*/ +void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum ); + } + *cmd = svs.clients[clientNum].lastUsercmd; +} + +//============================================== + +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +/* +==================== +SV_GameSystemCalls + +The module is making a system call +==================== +*/ +intptr_t SV_GameSystemCalls( intptr_t *args ) { + switch( args[0] ) + { + case G_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + case G_ERROR: + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + case G_MILLISECONDS: + return Sys_Milliseconds(); + case G_CVAR_REGISTER: + Cvar_Register( (vmCvar_t*)VMA(1), (const char*)VMA(2), (const char*)VMA(3), args[4] ); + return 0; + case G_CVAR_UPDATE: + Cvar_Update( (vmCvar_t*)VMA(1) ); + return 0; + case G_CVAR_SET: + Cvar_SetSafe( (const char *)VMA(1), (const char *)VMA(2) ); + return 0; + case G_CVAR_VARIABLE_INTEGER_VALUE: + return Cvar_VariableIntegerValue( (const char *)VMA(1) ); + case G_CVAR_VARIABLE_STRING_BUFFER: + Cvar_VariableStringBuffer( (const char*)VMA(1), (char*)VMA(2), args[3] ); + return 0; + case G_ARGC: + return Cmd_Argc(); + case G_ARGV: + Cmd_ArgvBuffer( args[1], (char*)VMA(2), args[3] ); + return 0; + case G_SEND_CONSOLE_COMMAND: + Cbuf_ExecuteText( args[1], (const char*)VMA(2) ); + return 0; + + case G_FS_FOPEN_FILE: + return FS_FOpenFileByMode( (const char*)VMA(1), (fileHandle_t*)VMA(2), (FS_Mode)args[3] ); + case G_FS_READ: + FS_Read( VMA(1), args[2], args[3] ); + return 0; + case G_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case G_FS_FCLOSE_FILE: + FS_FCloseFile( args[1] ); + return 0; + case G_FS_GETFILELIST: + return FS_GetFileList( (const char*)VMA(1), (const char*)VMA(2), (char*)VMA(3), args[4] ); + case G_FS_GETFILTEREDFILES: + return FS_GetFilteredFiles( (const char*)VMA(1), (const char*)VMA(2), (char*)VMA(3), (char*)VMA(4), args[5] ); + case G_FS_SEEK: + return FS_Seek( args[1], args[2], (FS_Origin)args[3] ); + case G_LOCATE_GAME_DATA: + SV_LocateGameData( (sharedEntity_t*)VMA(1), args[2], args[3], (playerState_t*)VMA(4), args[5] ); + return 0; + case G_DROP_CLIENT: + SV_GameDropClient( args[1], (const char*)VMA(2) ); + return 0; + case G_SEND_SERVER_COMMAND: + SV_GameSendServerCommand( args[1], (const char*)VMA(2) ); + return 0; + case G_LINKENTITY: + SV_LinkEntity( (sharedEntity_t*)VMA(1) ); + return 0; + case G_UNLINKENTITY: + SV_UnlinkEntity( (sharedEntity_t*)VMA(1) ); + return 0; + case G_ENTITIES_IN_BOX: + return SV_AreaEntities( (const vec_t*)VMA(1), (const vec_t*)VMA(2), (int*)VMA(3), args[4] ); + case G_ENTITY_CONTACT: + return SV_EntityContact( (vec_t*)VMA(1), (vec_t*)VMA(2), (const sharedEntity_t*)VMA(3), TT_AABB ); + case G_ENTITY_CONTACTCAPSULE: + return SV_EntityContact( (vec_t*)VMA(1), (vec_t*)VMA(2), (const sharedEntity_t*)VMA(3), TT_CAPSULE ); + case G_TRACE: + SV_Trace( (trace_t*)VMA(1), (const vec_t*)VMA(2), (vec_t*)VMA(3), (vec_t*)VMA(4), (const vec_t*)VMA(5), args[6], args[7], TT_AABB ); + return 0; + case G_TRACECAPSULE: + SV_Trace( (trace_t*)VMA(1), (const vec_t*)VMA(2), (vec_t*)VMA(3), (vec_t*)VMA(4), (const vec_t*)VMA(5), args[6], args[7], TT_CAPSULE ); + return 0; + case G_POINT_CONTENTS: + return SV_PointContents( (const vec_t*)VMA(1), args[2] ); + case G_SET_BRUSH_MODEL: + SV_SetBrushModel( (sharedEntity_t*)VMA(1), (const char*)VMA(2) ); + return 0; + case G_IN_PVS: + return SV_inPVS( (const vec_t*)VMA(1), (const vec_t*)VMA(2) ); + case G_IN_PVS_IGNORE_PORTALS: + return SV_inPVSIgnorePortals( (const vec_t*)VMA(1), (const vec_t*)VMA(2) ); + + case G_SET_CONFIGSTRING: + SV_SetConfigstring( args[1], (const char*)VMA(2) ); + return 0; + case G_GET_CONFIGSTRING: + SV_GetConfigstring( args[1], (char*)VMA(2), args[3] ); + return 0; + case G_SET_CONFIGSTRING_RESTRICTIONS: + SV_SetConfigstringRestrictions( args[1], (clientList_t*)VMA(2) ); + return 0; + case G_SET_USERINFO: + SV_SetUserinfo( args[1], (const char*)VMA(2) ); + return 0; + case G_GET_USERINFO: + SV_GetUserinfo( args[1], (char*)VMA(2), args[3] ); + return 0; + case G_GET_SERVERINFO: + SV_GetServerinfo( (char*)VMA(1), args[2] ); + return 0; + case G_ADJUST_AREA_PORTAL_STATE: + SV_AdjustAreaPortalState( (sharedEntity_t*)VMA(1), (bool)args[2] ); + return 0; + case G_AREAS_CONNECTED: + return CM_AreasConnected( args[1], args[2] ); + + case G_GET_USERCMD: + SV_GetUsercmd( args[1], (usercmd_t*)VMA(2) ); + return 0; + case G_GET_ENTITY_TOKEN: + { + const char *s; + + s = COM_Parse( &sv.entityParsePoint ); + Q_strncpyz( (char*)VMA(1), s, args[2] ); + if ( !sv.entityParsePoint && !s[0] ) { + return false; + } else { + return true; + } + } + + case G_REAL_TIME: + return Com_RealTime( (qtime_t*)VMA(1) ); + case G_SNAPVECTOR: + Q_SnapVector( (vec_t*)VMA(1) ); + return 0; + + case G_SEND_GAMESTAT: + return 0; + + //==================================== + + case G_PARSE_ADD_GLOBAL_DEFINE: + return Parse_AddGlobalDefine( (char*)VMA(1) ); + case G_PARSE_LOAD_SOURCE: + return Parse_LoadSourceHandle( (const char*)VMA(1) ); + case G_PARSE_FREE_SOURCE: + return Parse_FreeSourceHandle( args[1] ); + case G_PARSE_READ_TOKEN: + return Parse_ReadTokenHandle( args[1], (pc_token_t*)VMA(2) ); + case G_PARSE_SOURCE_FILE_AND_LINE: + return Parse_SourceFileAndLine( args[1], (char*)VMA(2), (int*)VMA(3) ); + + case G_ADDCOMMAND: + Cmd_AddCommand( (const char*)VMA(1), NULL ); + return 0; + case G_REMOVECOMMAND: + Cmd_RemoveCommand( (const char*)VMA(1) ); + return 0; + + case TRAP_MEMSET: + ::memset( VMA(1), args[2], args[3] ); + return 0; + + case TRAP_MEMCPY: + ::memcpy( VMA(1), VMA(2), args[3] ); + return 0; + + case TRAP_STRNCPY: + ::strncpy( (char*)VMA(1), (const char*)VMA(2), args[3] ); + return args[1]; + + case TRAP_SIN: + return FloatAsInt( sin( VMF(1) ) ); + + case TRAP_COS: + return FloatAsInt( cos( VMF(1) ) ); + + case TRAP_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + + case TRAP_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + + case TRAP_MATRIXMULTIPLY: + { + // XXX C++ is made this annoying + float (&in1)[3][3] = *reinterpret_cast(VMA(1)); + float (&in2)[3][3] = *reinterpret_cast(VMA(2)); + float (&in3)[3][3] = *reinterpret_cast(VMA(3)); + MatrixMultiply( in1, in2, in3 ); + return 0; + } + + case TRAP_ANGLEVECTORS: + AngleVectors( (const vec_t*)VMA(1), (vec_t*)VMA(2), (vec_t*)VMA(3), (vec_t*)VMA(4) ); + return 0; + + case TRAP_PERPENDICULARVECTOR: + PerpendicularVector( (vec_t*)VMA(1), (const vec_t*)VMA(2) ); + return 0; + + case TRAP_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + + case TRAP_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + + default: + Com_Error( ERR_DROP, "Bad game system trap: %ld", (long int) args[0] ); + } + return 0; +} + +/* +=============== +SV_ShutdownGameProgs + +Called every time a map changes +=============== +*/ +void SV_ShutdownGameProgs( void ) { + if ( !sv.gvm ) { + return; + } + VM_Call( sv.gvm, GAME_SHUTDOWN, false ); + VM_Free( sv.gvm ); + sv.gvm = NULL; +} + +/* +================== +SV_InitGameVM + +Called for both a full init and a restart +================== +*/ +static void SV_InitGameVM( bool restart ) { + int i; + + // start the entity parsing at the beginning + sv.entityParsePoint = CM_EntityString(); + + // clear all gentity pointers that might still be set from + // a previous level + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522 + // now done before GAME_INIT call + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + svs.clients[i].gentity = NULL; + } + + // use the current msec count for a random seed + // init for this gamestate + VM_Call (sv.gvm, GAME_INIT, sv.time, Com_Milliseconds(), restart); +} + + + +/* +=================== +SV_RestartGameProgs + +Called on a map_restart, but not on a normal map change +=================== +*/ +void SV_RestartGameProgs( void ) { + if ( !sv.gvm ) { + return; + } + VM_Call( sv.gvm, GAME_SHUTDOWN, true ); + + // do a restart instead of a free + sv.gvm = VM_Restart(sv.gvm, true); + if ( !sv.gvm ) { + Com_Error( ERR_FATAL, "VM_Restart on game failed" ); + } + + SV_InitGameVM( true ); +} + + +/* +=============== +SV_InitGameProgs + +Called on a normal map change, not on a map_restart +=============== +*/ +void SV_InitGameProgs( void ) { + // load the dll or bytecode + sv.gvm = VM_Create( "game", SV_GameSystemCalls, (vmInterpret_t)Cvar_VariableValue( "vm_game" ) ); + if ( !sv.gvm ) { + Com_Error( ERR_FATAL, "VM_Create on game failed" ); + } + + SV_InitGameVM( false ); +} + + +/* +==================== +SV_GameCommand + +See if the current console command is claimed by the game +==================== +*/ +bool SV_GameCommand( void ) { + if ( sv.state != SS_GAME ) { + return false; + } + + return (bool)VM_Call( sv.gvm, GAME_CONSOLE_COMMAND ); +} diff --git a/src/server/sv_init.cpp b/src/server/sv_init.cpp new file mode 100644 index 0000000..8c7729e --- /dev/null +++ b/src/server/sv_init.cpp @@ -0,0 +1,1004 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2012-2018 ET:Legacy team +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "server.h" + +#include "qcommon/cvar.h" + +// Attack log file is started when server is init (!= sv_running 1!) +// we even log attacks when the server is waiting for rcon and doesn't run a map +int attHandle = 0; // server attack log file handle + +char alternateInfos[2][2][BIG_INFO_STRING]; + +/* +=============== +SV_SendConfigstring + +Creates and sends the server command necessary to update the CS index for the +given client +=============== +*/ +static void SV_SendConfigstring(client_t *client, int i) +{ + const char *configstring; + int maxChunkSize = MAX_STRING_CHARS - 24; + int len; + + if (sv.configstrings[i].restricted && + Com_ClientListContains(&sv.configstrings[i].clientList, client - svs.clients)) + { + // Send a blank config string for this client if it's listed + SV_SendServerCommand(client, "cs %i \"\"\n", i); + return; + } + + if (i <= CS_SYSTEMINFO && client->netchan.alternateProtocol != 0) + { + configstring = alternateInfos[i][client->netchan.alternateProtocol - 1]; + } + else + { + configstring = sv.configstrings[i].s; + } + + len = strlen(configstring); + + if (len >= maxChunkSize) + { + int sent = 0; + int remaining = len; + const char *cmd; + char buf[MAX_STRING_CHARS]; + + while (remaining > 0) + { + if (sent == 0) + { + cmd = "bcs0"; + } + else if (remaining < maxChunkSize) + { + cmd = "bcs2"; + } + else + { + cmd = "bcs1"; + } + Q_strncpyz(buf, &configstring[sent], maxChunkSize); + + SV_SendServerCommand(client, "%s %i \"%s\"\n", cmd, i, buf); + + sent += (maxChunkSize - 1); + remaining -= (maxChunkSize - 1); + } + } + else + { + // standard cs, just send it + SV_SendServerCommand(client, "cs %i \"%s\"\n", i, configstring); + } +} + +/* +=============== +SV_UpdateConfigstrings + +Called when a client goes from CS_PRIMED to CS_ACTIVE. Updates all +Configstring indexes that have changed while the client was in CS_PRIMED +=============== +*/ +void SV_UpdateConfigstrings(client_t *client) +{ + for (int i = 0; i < MAX_CONFIGSTRINGS; i++) + { + // if the CS hasn't changed since we went to CS_PRIMED, ignore + if (!client->csUpdated[i]) continue; + + // do not always send server info to all clients + if (i == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO)) + { + continue; + } + + SV_SendConfigstring(client, i); + client->csUpdated[i] = false; + } +} + +/* +=============== +SV_SetConfigstring + +=============== +*/ +void SV_SetConfigstring(int idx, const char *val) +{ + bool modified[3] = {false, false, false}; + int i; + client_t *client; + + if (idx < 0 || idx >= MAX_CONFIGSTRINGS) + { + Com_Error(ERR_DROP, "SV_SetConfigstring: bad idx %i", idx); + } + + if (!val) + { + val = ""; + } + + if (idx <= CS_SYSTEMINFO) + { + for (i = 1; i < 3; ++i) + { + char info[BIG_INFO_STRING]; + + Q_strncpyz(info, val, sizeof(info)); + if (idx == CS_SERVERINFO) + { + Info_SetValueForKey_Big(info, "protocol", (i == 1 ? "70" : "69")); + } + else if (i == 2) + { + Info_SetValueForKey_Big(info, "sv_paks", Cvar_VariableString("sv_alternatePaks")); + Info_SetValueForKey_Big(info, "sv_pakNames", Cvar_VariableString("sv_alternatePakNames")); + Info_SetValueForKey_Big(info, "sv_referencedPaks", Cvar_VariableString("sv_referencedAlternatePaks")); + Info_SetValueForKey_Big( + info, "sv_referencedPakNames", Cvar_VariableString("sv_referencedAlternatePakNames")); + Info_SetValueForKey_Big(info, "cl_allowDownload", "1, you should set it yourself"); + if (!(sv_allowDownload->integer & DLF_NO_REDIRECT)) + { + Info_SetValueForKey_Big(info, "sv_wwwBaseURL", Cvar_VariableString("sv_dlUrl")); + Info_SetValueForKey_Big( + info, "sv_wwwDownload", Cvar_VariableString("1, you should set it yourself")); + } + } + + if (strcmp(info, alternateInfos[idx][i - 1])) + { + modified[i] = true; + strcpy(alternateInfos[idx][i - 1], info); + } + } + + if (strcmp(val, sv.configstrings[idx].s)) + { + modified[0] = true; + Z_Free(sv.configstrings[idx].s); + sv.configstrings[idx].s = CopyString(val); + } + + if (!modified[0] && !modified[1] && !modified[2]) + { + return; + } + } + else + { + // don't bother broadcasting an update if no change + if (!strcmp(val, sv.configstrings[idx].s)) + { + return; + } + + // change the string in sv + Z_Free(sv.configstrings[idx].s); + sv.configstrings[idx].s = CopyString(val); + } + + // send it to all the clients if we aren't + // spawning a new server + if (sv.state == SS_GAME || sv.restarting) + { + // send the data to all relevent clients + for (i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++) + { + if (idx <= CS_SYSTEMINFO && !modified[client->netchan.alternateProtocol]) + { + continue; + } + + if (client->state < CS_ACTIVE) + { + if (client->state == CS_PRIMED) client->csUpdated[idx] = true; + continue; + } + // do not always send server info to all clients + if (idx == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO)) + { + continue; + } + + SV_SendConfigstring(client, idx); + } + } +} + +/* +=============== +SV_GetConfigstring + +=============== +*/ +void SV_GetConfigstring(int idx, char *buffer, int bufferSize) +{ + if (bufferSize < 1) + { + Com_Error(ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize); + } + if (idx < 0 || idx >= MAX_CONFIGSTRINGS) + { + Com_Error(ERR_DROP, "SV_GetConfigstring: bad idx %i", idx); + } + if (!sv.configstrings[idx].s) + { + buffer[0] = 0; + return; + } + + Q_strncpyz(buffer, sv.configstrings[idx].s, bufferSize); +} + +/* +=============== +SV_SetConfigstringRestrictions +=============== +*/ +void SV_SetConfigstringRestrictions(int idx, const clientList_t *clientList) +{ + int i; + clientList_t oldClientList = sv.configstrings[idx].clientList; + + sv.configstrings[idx].clientList = *clientList; + sv.configstrings[idx].restricted = true; + + for (i = 0; i < sv_maxclients->integer; i++) + { + if (svs.clients[i].state >= CS_CONNECTED) + { + if (Com_ClientListContains(&oldClientList, i) != Com_ClientListContains(clientList, i)) + { + // A client has left or joined the restricted list, so update + SV_SendConfigstring(&svs.clients[i], idx); + } + } + } +} + +/* +=============== +SV_SetUserinfo + +=============== +*/ +void SV_SetUserinfo(int idx, const char *val) +{ + if (idx < 0 || idx >= sv_maxclients->integer) + { + Com_Error(ERR_DROP, "SV_SetUserinfo: bad idx %i", idx); + } + + if (!val) + { + val = ""; + } + + Q_strncpyz(svs.clients[idx].userinfo, val, sizeof(svs.clients[idx].userinfo)); + Q_strncpyz(svs.clients[idx].name, Info_ValueForKey(val, "name"), sizeof(svs.clients[idx].name)); +} + +/* +=============== +SV_GetUserinfo + +=============== +*/ +void SV_GetUserinfo(int idx, char *buffer, int bufferSize) +{ + if (bufferSize < 1) + { + Com_Error(ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize); + } + if (idx < 0 || idx >= sv_maxclients->integer) + { + Com_Error(ERR_DROP, "SV_GetUserinfo: bad idx %i", idx); + } + Q_strncpyz(buffer, svs.clients[idx].userinfo, bufferSize); +} + +/* +================ +SV_CreateBaseline + +Entity baselines are used to compress non-delta messages +to the clients -- only the fields that differ from the +baseline will be transmitted +================ +*/ +static void SV_CreateBaseline(void) +{ + sharedEntity_t *svent; + int entnum; + + for (entnum = 1; entnum < sv.num_entities; entnum++) + { + svent = SV_GentityNum(entnum); + if (!svent->r.linked) + { + continue; + } + svent->s.number = entnum; + + // + // take current state as baseline + // + sv.svEntities[entnum].baseline = svent->s; + } +} + +/* +=============== +SV_BoundMaxClients + +=============== +*/ +static void SV_BoundMaxClients(int minimum) +{ + // get the current maxclients value + Cvar_Get("sv_maxclients", "8", 0); + + sv_maxclients->modified = false; + + if (sv_maxclients->integer < minimum) + { + Cvar_Set("sv_maxclients", va("%i", minimum)); + } + else if (sv_maxclients->integer > MAX_CLIENTS) + { + Cvar_Set("sv_maxclients", va("%i", MAX_CLIENTS)); + } +} + +/* +=============== +SV_Startup + +Called when a host starts a map when it wasn't running +one before. Successive map or map_restart commands will +NOT cause this to be called, unless the game is exited to +the menu system first. +=============== +*/ +static void SV_Startup(void) +{ + if (svs.initialized) + { + Com_Error(ERR_FATAL, "SV_Startup: svs.initialized"); + } + SV_BoundMaxClients(1); + + svs.clients = (client_t *)Z_Malloc(sizeof(client_t) * sv_maxclients->integer); + if (com_dedicated->integer) + { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * MAX_SNAPSHOT_ENTITIES; + } + else + { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * MAX_SNAPSHOT_ENTITIES; + } + svs.initialized = true; + + // Don't respect sv_killserver unless a server is actually running + if (sv_killserver->integer) + { + Cvar_Set("sv_killserver", "0"); + } + + Cvar_Set("sv_running", "1"); + + // Join the ipv6 multicast group now that a map is running so clients can scan for us on the local network. + NET_JoinMulticast6(); +} + +/* +================== +SV_ChangeMaxClients +================== +*/ +void SV_ChangeMaxClients(void) +{ + int oldMaxClients; + int i; + client_t *oldClients; + int count; + + // get the highest client number in use + count = 0; + for (i = 0; i < sv_maxclients->integer; i++) + { + if (svs.clients[i].state >= CS_CONNECTED) + { + if (i > count) count = i; + } + } + count++; + + oldMaxClients = sv_maxclients->integer; + // never go below the highest client number in use + SV_BoundMaxClients(count); + // if still the same + if (sv_maxclients->integer == oldMaxClients) + { + return; + } + + oldClients = (client_t *)Hunk_AllocateTempMemory(count * sizeof(client_t)); + // copy the clients to hunk memory + for (i = 0; i < count; i++) + { + if (svs.clients[i].state >= CS_CONNECTED) + { + oldClients[i] = svs.clients[i]; + } + else + { + ::memset(&oldClients[i], 0, sizeof(client_t)); + } + } + + // free old clients arrays + Z_Free(svs.clients); + + // allocate new clients + svs.clients = (client_t *)Z_Malloc(sv_maxclients->integer * sizeof(client_t)); + ::memset(svs.clients, 0, sv_maxclients->integer * sizeof(client_t)); + + // copy the clients over + for (i = 0; i < count; i++) + { + if (oldClients[i].state >= CS_CONNECTED) + { + svs.clients[i] = oldClients[i]; + } + } + + // free the old clients on the hunk + Hunk_FreeTempMemory(oldClients); + + // allocate new snapshot entities + if (com_dedicated->integer) + { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * MAX_SNAPSHOT_ENTITIES; + } + else + { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * MAX_SNAPSHOT_ENTITIES; + } +} + +/* +================ +SV_ClearServer +================ +*/ +static void SV_ClearServer(void) +{ + int i; + + for (i = 0; i < MAX_CONFIGSTRINGS; i++) + { + if (i <= CS_SYSTEMINFO) + { + alternateInfos[i][0][0] = alternateInfos[i][1][0] = '\0'; + } + if (sv.configstrings[i].s) + { + Z_Free(sv.configstrings[i].s); + } + } + ::memset(&sv, 0, sizeof(sv)); +} + +/* +================ +SV_TouchCGame + +Touch the cgame.qvm so that a pure client can load it if it's in a seperate pk3 +================ +*/ +static void SV_TouchCGame(void) +{ + fileHandle_t f; + char filename[MAX_QPATH]; + + Com_sprintf(filename, sizeof(filename), "vm/%s.qvm", "cgame"); + FS_FOpenFileRead(filename, &f, false); + if (f) + { + FS_FCloseFile(f); + } +} + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +This is NOT called for map_restart +================ +*/ +void SV_SpawnServer(char *server) +{ + int i; + int checksum; + char systemInfo[16384]; + const char *p; + + // shut down the existing game if it is running + SV_ShutdownGameProgs(); + + Com_Printf("------ Server Initialization ------\n"); + Com_Printf("Server: %s\n", server); + + // if not running a dedicated server CL_MapLoading will connect the client to the server + // also print some status stuff + CL_MapLoading(); + + // make sure all the client stuff is unloaded + CL_ShutdownAll(false); + + // clear the whole hunk because we're (re)loading the server + Hunk_Clear(); + + // clear collision map data + CM_ClearMap(); + + // init client structures and svs.numSnapshotEntities + if (!Cvar_VariableValue("sv_running")) + { + SV_Startup(); + } + else + { + // check for maxclients change + if (sv_maxclients->modified) + { + SV_ChangeMaxClients(); + } + } + + // clear pak references + FS_ClearPakReferences(0); + + // allocate the snapshot entities on the hunk + svs.snapshotEntities = (entityState_t *)Hunk_Alloc(sizeof(entityState_t) * svs.numSnapshotEntities, h_high); + svs.nextSnapshotEntities = 0; + + // toggle the server bit so clients can detect that a + // server has changed + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + for (i = 0; i < sv_maxclients->integer; i++) + { + // save when the server started for each client already connected + if (svs.clients[i].state >= CS_CONNECTED) + { + svs.clients[i].oldServerTime = sv.time; + } + } + + // wipe the entire per-level structure + SV_ClearServer(); + for (i = 0; i < MAX_CONFIGSTRINGS; i++) + { + if (i <= CS_SYSTEMINFO) + { + alternateInfos[i][0][0] = alternateInfos[i][1][0] = '\0'; + } + sv.configstrings[i].s = CopyString(""); + sv.configstrings[i].restricted = false; + ::memset(&sv.configstrings[i].clientList, 0, sizeof(clientList_t)); + } + + // make sure we are not paused + Cvar_Set("cl_paused", "0"); + + // get a new checksum feed and restart the file system + sv.checksumFeed = (((int)rand() << 16) ^ rand()) ^ Com_Milliseconds(); + FS_Restart(sv.checksumFeed); + + // advertise GPP-compatible extensions + Cvar_Set("sv_gppExtension", "1"); + + CM_LoadMap(va("maps/%s.bsp", server), false, &checksum); + + // set serverinfo visible name + Cvar_Set("mapname", server); + + Cvar_Set("sv_mapChecksum", va("%i", checksum)); + + // serverid should be different each time + sv.serverId = com_frameTime; + sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe + sv.checksumFeedServerId = sv.serverId; + Cvar_Set("sv_serverid", va("%i", sv.serverId)); + + // clear physics interaction links + SV_ClearWorld(); + + // media configstring setting should be done during + // the loading stage, so connected clients don't have + // to load during actual gameplay + sv.state = SS_LOADING; + + // load and spawn all other entities + SV_InitGameProgs(); + + // run a few frames to allow everything to settle + for (i = 0; i < 3; i++) + { + VM_Call(sv.gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; + } + + // create a baseline for more efficient communications + SV_CreateBaseline(); + + for (i = 0; i < sv_maxclients->integer; i++) + { + // send the new gamestate to all connected clients + if (svs.clients[i].state >= CS_CONNECTED) + { + char *denied; + + // connect the client again + denied = + (char *)VM_ExplicitArgPtr(sv.gvm, VM_Call(sv.gvm, GAME_CLIENT_CONNECT, i, false)); // firstTime = false + if (denied) + { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient(&svs.clients[i], denied); + } + else + { + // when we get the next packet from a connected client, + // the new gamestate will be sent + svs.clients[i].state = CS_CONNECTED; + } + } + } + + // run another frame to allow things to look at all the players + VM_Call(sv.gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; + + if (sv_pure->integer) + { + // the server sends these to the clients so they will only + // load pk3s also loaded at the server + p = FS_LoadedPakChecksums(false); + Cvar_Set("sv_paks", p); + p = FS_LoadedPakChecksums(true); + Cvar_Set("sv_alternatePaks", p); + if (strlen(p) == 0) + { + Com_Printf("WARNING: sv_pure set but no PK3 files loaded\n"); + } + p = FS_LoadedPakNames(false); + Cvar_Set("sv_pakNames", p); + p = FS_LoadedPakNames(true); + Cvar_Set("sv_alternatePakNames", p); + + // if a dedicated pure server we need to touch the cgame because it could be in a + // seperate pk3 file and the client will need to load the latest cgame.qvm + if (com_dedicated->integer) + { + SV_TouchCGame(); + } + } + else + { + Cvar_Set("sv_paks", ""); + Cvar_Set("sv_pakNames", ""); + Cvar_Set("sv_alternatePaks", ""); + Cvar_Set("sv_alternatePakNames", ""); + } + // the server sends these to the clients so they can figure + // out which pk3s should be auto-downloaded + p = FS_ReferencedPakChecksums(false); + Cvar_Set("sv_referencedPaks", p); + p = FS_ReferencedPakChecksums(true); + Cvar_Set("sv_referencedAlternatePaks", p); + p = FS_ReferencedPakNames(false); + Cvar_Set("sv_referencedPakNames", p); + p = FS_ReferencedPakNames(true); + Cvar_Set("sv_referencedAlternatePakNames", p); + + // save systeminfo and serverinfo strings + Q_strncpyz(systemInfo, Cvar_InfoString_Big(CVAR_SYSTEMINFO), sizeof(systemInfo)); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + SV_SetConfigstring(CS_SYSTEMINFO, systemInfo); + + SV_SetConfigstring(CS_SERVERINFO, Cvar_InfoString(CVAR_SERVERINFO)); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + + // any media configstring setting now should issue a warning + // and any configstring changes should be reliably transmitted + // to all clients + sv.state = SS_GAME; + + // send a heartbeat now so the master will get up to date info + SV_Heartbeat_f(); + + Hunk_SetMark(); + +#ifndef DEDICATED + if (com_dedicated->integer) + { + // restart renderer in order to show console for dedicated servers + // launched through the regular binary + CL_StartHunkUsers(true); + } +#endif + + Com_Printf("-----------------------------------\n"); +} + +/** + * @brief SV_WriteAttackLog + * @param[in] log + */ +void SV_WriteAttackLog(const char *log) +{ + if (attHandle > 0) + { + char string[512]; // 512 chars seem enough here + qtime_t time; + + Com_RealTime(&time); + Com_sprintf(string, sizeof(string), "%i/%i/%i %i:%i:%i %s", 1900 + time.tm_year, time.tm_mday, time.tm_mon + 1, time.tm_hour, time.tm_min, time.tm_sec, log); + (void) FS_Write(string, strlen(string), attHandle); + } + + if (sv_protect->integer & SVP_CONSOLE) + { + Com_Printf("%s", log); + } +} + +/** + * @brief SV_InitAttackLog + */ +void SV_InitAttackLog() +{ + if (sv_protectLog->string[0] == '\0') + { + Com_Printf("Not logging server attacks to disk.\n"); + } + else + { + // in sync so admins can check this at runtime + FS_FOpenFileByMode(sv_protectLog->string, &attHandle, FS_APPEND_SYNC); + + if (attHandle <= 0) + { + Com_Printf("WARNING: Couldn't open server attack logfile %s\n", sv_protectLog->string); + } + else + { + Com_Printf("Logging server attacks to %s\n", sv_protectLog->string); + SV_WriteAttackLog("-------------------------------------------------------------------------------\n"); + SV_WriteAttackLog("Start server attack log\n"); + SV_WriteAttackLog("-------------------------------------------------------------------------------\n"); + } + } +} + +/** + * @brief SV_CloseAttackLog + */ +void SV_CloseAttackLog() +{ + if (attHandle > 0) + { + SV_WriteAttackLog("-------------------------------------------------------------------------------\n"); + SV_WriteAttackLog("End server attack log\n"); + SV_WriteAttackLog("-------------------------------------------------------------------------------\n"); + Com_Printf("Server attack log closed \n"); + } + + FS_FCloseFile(attHandle); + + attHandle = 0; // local handle +} + +/* +=============== +SV_Init + +Only called at main exe startup, not for each game +=============== +*/ +void SV_Init(void) +{ + SV_AddOperatorCommands(); + + // serverinfo vars + Cvar_Get("timelimit", "0", CVAR_SERVERINFO); + Cvar_Get("sv_keywords", "", CVAR_SERVERINFO); + sv_mapname = Cvar_Get("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); + sv_privateClients = Cvar_Get("sv_privateClients", "0", CVAR_SERVERINFO); + sv_hostname = Cvar_Get("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE); + sv_maxclients = Cvar_Get("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); + + sv_minRate = Cvar_Get("sv_minRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO); + sv_maxRate = Cvar_Get("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO); + sv_dlRate = Cvar_Get("sv_dlRate", "100", CVAR_ARCHIVE | CVAR_SERVERINFO); + sv_minPing = Cvar_Get("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO); + sv_maxPing = Cvar_Get("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO); + + // systeminfo + Cvar_Get("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM); + sv_serverid = Cvar_Get("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM); + sv_pure = Cvar_Get("sv_pure", "1", CVAR_SYSTEMINFO); +#ifdef USE_VOIP + sv_voip = Cvar_Get("sv_voip", "1", CVAR_LATCH); + Cvar_CheckRange(sv_voip, 0, 1, true); + sv_voipProtocol = Cvar_Get("sv_voipProtocol", sv_voip->integer ? "opus" : "", CVAR_SYSTEMINFO | CVAR_ROM); +#endif + Cvar_Get("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_alternatePaks", "", CVAR_ALTERNATE_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_alternatePakNames", "", CVAR_ALTERNATE_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_referencedAlternatePaks", "", CVAR_ALTERNATE_SYSTEMINFO | CVAR_ROM); + Cvar_Get("sv_referencedAlternatePakNames", "", CVAR_ALTERNATE_SYSTEMINFO | CVAR_ROM); + + // server vars + sv_rconPassword = Cvar_Get("rconPassword", "", CVAR_TEMP); + sv_privatePassword = Cvar_Get("sv_privatePassword", "", CVAR_TEMP); + sv_fps = Cvar_Get("sv_fps", "40", CVAR_TEMP); + sv_timeout = Cvar_Get("sv_timeout", "200", CVAR_TEMP); + sv_zombietime = Cvar_Get("sv_zombietime", "2", CVAR_TEMP); + + sv_allowDownload = Cvar_Get("sv_allowDownload", "0", CVAR_SERVERINFO); + Cvar_Get("sv_dlURL", "http://downloads.tremulous.net", CVAR_SERVERINFO | CVAR_ARCHIVE); + + sv_protect = Cvar_Get("sv_protect", "3", CVAR_ARCHIVE); + sv_protectLog = Cvar_Get("sv_protectLog", "sv_protect.log", CVAR_ARCHIVE); + SV_InitAttackLog(); + + for (int a = 0; a < 3; ++a) + { + sv_masters[a][0] = Cvar_Get(va("sv_%smaster1", (a == 2 ? "alt2" : a == 1 ? "alt1" : "")), MASTER_SERVER_NAME, 0); + for (int i = 1; i < MAX_MASTER_SERVERS; i++) + sv_masters[a][i] = Cvar_Get(va("sv_%smaster%d", (a == 2 ? "alt2" : a == 1 ? "alt1" : ""), i + 1), "", CVAR_ARCHIVE); + } + + sv_reconnectlimit = Cvar_Get("sv_reconnectlimit", "3", 0); + sv_showloss = Cvar_Get("sv_showloss", "0", 0); + sv_padPackets = Cvar_Get("sv_padPackets", "0", 0); + sv_killserver = Cvar_Get("sv_killserver", "0", 0); + sv_mapChecksum = Cvar_Get("sv_mapChecksum", "", CVAR_ROM); + sv_lanForceRate = Cvar_Get("sv_lanForceRate", "1", CVAR_ARCHIVE); + sv_rsaAuth = Cvar_Get("sv_rsaAuth", "1", CVAR_INIT | CVAR_PROTECTED); + sv_schachtmeisterPort = Cvar_Get ("sv_schachtmeisterPort", "1337", CVAR_ARCHIVE); +} + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown to send a final message to all +connected clients before the server goes down. The messages are sent immediately, +not just stuck on the outgoing message list, because the server is going +to totally exit after returning from this function. +================== +*/ +void SV_FinalMessage(const char *message) +{ + client_t *cl; + + // send it twice, ignoring rate + for (int j = 0; j < 2; j++) + { + int i; + for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++) + { + if (cl->state >= CS_CONNECTED) + { + // don't send a disconnect to a local client + if (cl->netchan.remoteAddress.type != NA_LOOPBACK) + { + SV_SendServerCommand(cl, "print \"%s\n\"\n", message); + SV_SendServerCommand(cl, "disconnect \"%s\"", message); + } + // force a snapshot to be sent + cl->lastSnapshotTime = 0; + SV_SendClientSnapshot(cl); + } + } + } +} + +/* +================ +SV_Shutdown + +Called when each game quits, +before Sys_Quit or Sys_Error +================ +*/ +void SV_Shutdown(const char *finalmsg) +{ + // close attack log + SV_CloseAttackLog(); + + if (!com_sv_running || !com_sv_running->integer) + { + return; + } + + Com_Printf("----- Server Shutdown (%s) -----\n", finalmsg); + + NET_LeaveMulticast6(); + + if (svs.clients && !com_errorEntered) + { + SV_FinalMessage(finalmsg); + } + + SV_RemoveOperatorCommands(); + SV_MasterShutdown(); + SV_ShutdownGameProgs(); + + // free current level + SV_ClearServer(); + + // free server static data + if (svs.clients) + { + for (int i = 0; i < sv_maxclients->integer; i++) + SV_FreeClient(&svs.clients[i]); + + Z_Free(svs.clients); + } + ::memset(&svs, 0, sizeof(svs)); + + Cvar_Set("sv_running", "0"); + + Com_Printf("---------------------------\n"); + + // disconnect any local clients + if (sv_killserver->integer != 2) CL_Disconnect(false); +} diff --git a/src/server/sv_main.cpp b/src/server/sv_main.cpp new file mode 100644 index 0000000..f5c3b98 --- /dev/null +++ b/src/server/sv_main.cpp @@ -0,0 +1,1551 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2012-2018 ET:Legacy team +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "server.h" + +#include + +#ifdef USE_VOIP +cvar_t *sv_voip; +cvar_t *sv_voipProtocol; +#endif + +serverStatic_t svs; // persistant server info +server_t sv {}; // local server + +cvar_t *sv_fps = NULL; // time rate for running non-clients +cvar_t *sv_timeout; // seconds without any message +cvar_t *sv_zombietime; // seconds to sink messages after disconnect +cvar_t *sv_rconPassword; // password for remote server commands +cvar_t *sv_privatePassword; // password for the privateClient slots +cvar_t *sv_allowDownload; +cvar_t *sv_maxclients; + +cvar_t *sv_privateClients; // number of clients reserved for password +cvar_t *sv_hostname; +cvar_t *sv_masters[3][MAX_MASTER_SERVERS]; // master server IP addresses +cvar_t *sv_reconnectlimit; // minimum seconds between connect messages +cvar_t *sv_showloss; // report when usercmds are lost +cvar_t *sv_padPackets; // add nop bytes to messages +cvar_t *sv_killserver; // menu system can set to 1 to shut server down +cvar_t *sv_mapname; +cvar_t *sv_mapChecksum; +cvar_t *sv_serverid; +cvar_t *sv_minRate; +cvar_t *sv_maxRate; +cvar_t *sv_dlRate; +cvar_t *sv_minPing; +cvar_t *sv_maxPing; +cvar_t *sv_pure; +cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491) +cvar_t *sv_banFile; + +cvar_t *sv_rsaAuth; + +cvar_t *sv_schachtmeisterPort; + +// server attack protection +cvar_t *sv_protect; // 0 - unprotected + // 1 - ioquake3 method (default) + // 2 - OpenWolf method + // 4 - prints attack info to console (when ioquake3 or OPenWolf method is set) +cvar_t *sv_protectLog; // name of log file + +/* +============================================================================= + +EVENT MESSAGES + +============================================================================= +*/ + +/* +=============== +SV_ExpandNewlines + +Converts newlines to "\n" so a line prints nicer +=============== +*/ +static char *SV_ExpandNewlines( char *in ) { + static char string[1024]; + int l; + + l = 0; + while ( *in && l < sizeof(string) - 3 ) { + if ( *in == '\n' ) { + string[l++] = '\\'; + string[l++] = 'n'; + } else { + string[l++] = *in; + } + in++; + } + string[l] = 0; + + return string; +} + +/* +====================== +SV_ReplacePendingServerCommands + +FIXME: This is ugly +====================== +*/ +#if 0 // unused +static bool SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) +{ + int i, index, csnum1, csnum2; + + for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) { + index = i & ( MAX_RELIABLE_COMMANDS - 1 ); + // + if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) + { + sscanf(cmd, "cs %i", &csnum1); + sscanf(client->reliableCommands[ index ], "cs %i", &csnum2); + if ( csnum1 == csnum2 ) + { + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); + return true; + } + } + } + return false; +} +#endif + +/* +====================== +SV_AddServerCommand + +The given command will be transmitted to the client, and is guaranteed to +not have future snapshot_t executed before it is executed +====================== +*/ +void SV_AddServerCommand( client_t *client, const char *cmd ) { + int index, i; + + // this is very ugly but it's also a waste to for instance send multiple config string updates + // for the same config string index in one snapshot +// if ( SV_ReplacePendingServerCommands( client, cmd ) ) { +// return; +// } + + // do not send commands until the gamestate has been sent + if( client->state < CS_PRIMED ) + return; + + client->reliableSequence++; + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // we check == instead of >= so a broadcast print added by SV_DropClient() + // doesn't cause a recursive drop client + if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) { + Com_Printf( "===== pending server commands =====\n" ); + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + Com_Printf( "cmd %5d: %s\n", i, cmd ); + SV_DropClient( client, "Server command overflow" ); + return; + } + index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); +} + +/* +================= +SV_SendServerCommand + +Sends a reliable command string to be interpreted by +the client game module: "cp", "print", "chat", etc +A NULL client will broadcast to all clients +================= +*/ +void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) { + va_list argptr; + byte message[MAX_MSGLEN]; + client_t *client; + int j; + + va_start(argptr, fmt); + Q_vsnprintf((char*)message, sizeof(message), fmt,argptr); + va_end(argptr); + + // Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt + // The actual cause of the bug is probably further downstream + // and should maybe be addressed later, but this certainly + // fixes the problem for now. + // Summary: The bug is that messages longer than 1022 are not + // allowed downstream and there is a buffer overflow issue + // affecting network traffic etc. Therefore, one way to stop + // this from happening is stopping the packet here. Ideally, + // we should increase the size of the downstream message. + if ( strlen ((char *)message) > 1022 ) { + SV_WriteAttackLog( va( "SV_SendServerCommand( %ld, %.20s... ) length %ld > 1022, " + "dropping to avoid server buffer overflow.\n", + cl - svs.clients, message, strlen( (char *)message ) ) ); + SV_WriteAttackLog( va( "Full message: [%s]\n", message ) ); + return; + } + + if ( cl != NULL ) { + SV_AddServerCommand( cl, (char *)message ); + return; + } + + // hack to echo broadcast prints to console + if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) { + Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) ); + } + + // send the data to all relevent clients + for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) { + SV_AddServerCommand( client, (char *)message ); + } +} + + +/* +============================================================================== + +MASTER SERVER FUNCTIONS + +============================================================================== +*/ + +/* +================ +SV_MasterHeartbeat + +Send a message to the masters every few minutes to +let it know we are alive, and log information. +We will also have a heartbeat sent when a server +changes from empty to non-empty, and full to non-full, +but not on every player enter or exit. +================ +*/ +#define HEARTBEAT_MSEC 300*1000 +void SV_MasterHeartbeat(const char *message) +{ + static netadr_t adrs[3][MAX_MASTER_SERVERS][2]; // [2] for v4 and v6 address for the same address string. + int a; + int i; + int res; + int netenabled; + int netAlternateProtocols; + + netenabled = Cvar_VariableIntegerValue("net_enabled"); + netAlternateProtocols = Cvar_VariableIntegerValue("net_alternateProtocols"); + + // "dedicated 1" is for lan play, "dedicated 2" is for inet public play + if (!com_dedicated || com_dedicated->integer != 2 || !(netenabled & (NET_ENABLEV4 | NET_ENABLEV6))) + return; // only dedicated servers send heartbeats + + // if not time yet, don't send anything + if ( svs.time < svs.nextHeartbeatTime ) + return; + + svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC; + + for (a = 0; a < 3; ++a) + { + // indent + if(a == 0 && (netAlternateProtocols & NET_DISABLEPRIMPROTO)) + continue; + if(a == 1 && !(netAlternateProtocols & NET_ENABLEALT1PROTO)) + continue; + if(a == 2 && !(netAlternateProtocols & NET_ENABLEALT2PROTO)) + continue; + + // send to group masters + for (i = 0; i < MAX_MASTER_SERVERS; i++) + { + if(!sv_masters[a][i]->string[0]) + continue; + + // see if we haven't already resolved the name + // resolving usually causes hitches on win95, so only + // do it when needed + if(sv_masters[a][i]->modified || (adrs[a][i][0].type == NA_BAD && adrs[a][i][1].type == NA_BAD)) + { + sv_masters[a][i]->modified = false; + + if(netenabled & NET_ENABLEV4) + { + Com_Printf("Resolving %s (IPv4)\n", sv_masters[a][i]->string); + res = NET_StringToAdr(sv_masters[a][i]->string, &adrs[a][i][0], NA_IP); + adrs[a][i][0].alternateProtocol = a; + + if(res == 2) + { + // if no port was specified, use the default master port + adrs[a][i][0].port = BigShort(a == 2 ? ALT2PORT_MASTER : a == 1 ? ALT1PORT_MASTER : PORT_MASTER); + } + + if(res) + Com_Printf( "%s resolved to %s\n", sv_masters[a][i]->string, NET_AdrToStringwPort(adrs[a][i][0])); + else + Com_Printf( "%s has no IPv4 address.\n", sv_masters[a][i]->string); + } + + if(netenabled & NET_ENABLEV6) + { + Com_Printf("Resolving %s (IPv6)\n", sv_masters[a][i]->string); + res = NET_StringToAdr(sv_masters[a][i]->string, &adrs[a][i][1], NA_IP6); + adrs[a][i][1].alternateProtocol = a; + + if(res == 2) + { + // if no port was specified, use the default master port + adrs[a][i][1].port = BigShort(a == 2 ? ALT2PORT_MASTER : a == 1 ? ALT1PORT_MASTER : PORT_MASTER); + } + + if(res) + Com_Printf( "%s resolved to %s\n", sv_masters[a][i]->string, NET_AdrToStringwPort(adrs[a][i][1])); + else + Com_Printf( "%s has no IPv6 address.\n", sv_masters[a][i]->string); + } + + if(adrs[a][i][0].type == NA_BAD && adrs[a][i][1].type == NA_BAD) + { + Com_Printf("Couldn't resolve address: %s\n", sv_masters[a][i]->string); + Cvar_Set(sv_masters[a][i]->name, ""); + sv_masters[a][i]->modified = false; + continue; + } + } + + + Com_Printf ("Sending%s heartbeat to %s\n", (a == 2 ? " alternate-2" : a == 1 ? " alternate-1" : ""), sv_masters[a][i]->string ); + + // this command should be changed if the server info / status format + // ever incompatably changes + + if(adrs[a][i][0].type != NA_BAD) + NET_OutOfBandPrint( NS_SERVER, adrs[a][i][0], "heartbeat %s\n", message); + if(adrs[a][i][1].type != NA_BAD) + NET_OutOfBandPrint( NS_SERVER, adrs[a][i][1], "heartbeat %s\n", message); + } + // outdent + } +} + +/* +================= +SV_MasterShutdown + +Informs all masters that this server is going down +================= +*/ +void SV_MasterShutdown( void ) { + // send a heartbeat right now + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(HEARTBEAT_FOR_MASTER); + + // send it again to minimize chance of drops + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(HEARTBEAT_FOR_MASTER); + + // when the master tries to poll the server, it won't respond, so + // it will be removed from the list +} + +/* +============================================================================== + +CONNECTIONLESS COMMANDS + +============================================================================== +*/ + +// This is deliberately quite large to make it more of an effort to DoS +#define MAX_BUCKETS 16384 +#define MAX_HASHES 1024 + +static leakyBucket_t buckets[ MAX_BUCKETS ]; +static leakyBucket_t *bucketHashes[ MAX_HASHES ]; +leakyBucket_t outboundLeakyBucket; + +/* +================ +SVC_HashForAddress +================ +*/ +static long SVC_HashForAddress( netadr_t address ) { + byte *ip = NULL; + size_t size = 0; + int i; + long hash = 0; + + switch ( address.type ) { + case NA_IP: ip = address.ip; size = 4; break; + case NA_IP6: ip = address.ip6; size = 16; break; + default: break; + } + + for ( i = 0; i < size; i++ ) { + hash += (long)( ip[ i ] ) * ( i + 119 ); + } + + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ); + hash &= ( MAX_HASHES - 1 ); + + return hash; +} + +/* +================ +SVC_BucketForAddress + +Find or allocate a bucket for an address +================ +*/ +static leakyBucket_t *SVC_BucketForAddress( netadr_t address, int burst, int period ) { + leakyBucket_t *bucket = NULL; + long hash = SVC_HashForAddress( address ); + int now = Sys_Milliseconds(); + + for ( bucket = bucketHashes[ hash ]; bucket; bucket = bucket->next ) + { + switch ( bucket->type ) + { + case NA_IP: + if ( ::memcmp( bucket->ipv._4, address.ip, 4 ) == 0 ) + return bucket; + break; + + case NA_IP6: + if ( ::memcmp( bucket->ipv._6, address.ip6, 16 ) == 0 ) + return bucket; + break; + + default: + break; + } + } + + for ( int i = 0; i < MAX_BUCKETS; i++ ) + { + int interval; + + bucket = &buckets[ i ]; + interval = now - bucket->lastTime; + + // Reclaim expired buckets + if ( bucket->lastTime > 0 && ( interval > ( burst * period ) || + interval < 0 ) ) { + if ( bucket->prev != NULL ) { + bucket->prev->next = bucket->next; + } else { + bucketHashes[ bucket->hash ] = bucket->next; + } + + if ( bucket->next != NULL ) { + bucket->next->prev = bucket->prev; + } + + ::memset( bucket, 0, sizeof( leakyBucket_t ) ); + } + + if ( bucket->type == NA_BAD ) { + bucket->type = address.type; + switch ( address.type ) { + case NA_IP: ::memcpy( bucket->ipv._4, address.ip, 4 ); break; + case NA_IP6: ::memcpy( bucket->ipv._6, address.ip6, 16 ); break; + default: break; + } + + bucket->lastTime = now; + bucket->burst = 0; + bucket->hash = hash; + + // Add to the head of the relevant hash chain + bucket->next = bucketHashes[ hash ]; + if ( bucketHashes[ hash ] != NULL ) { + bucketHashes[ hash ]->prev = bucket; + } + + bucket->prev = NULL; + bucketHashes[ hash ] = bucket; + + return bucket; + } + } + + // Couldn't allocate a bucket for this address + // Write the info to the attack log since this is relevant information as the system is malfunctioning + SV_WriteAttackLogD(va("SVC_BucketForAddress: Could not allocate a bucket for client from %s\n", NET_AdrToString(address))); + + return NULL; +} + +/* +================ +SVC_RateLimit + * + * @param[in,out] bucket + * @param[in] burst + * @param[in] period + * @return + * + * @note Don't call if sv_protect 1 (SVP_IOQ3) flag is not set! +================ +*/ +bool SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) +{ + if ( bucket != NULL ) + { + int now = Sys_Milliseconds(); + int interval = now - bucket->lastTime; + int expired = interval / period; + int expiredRemainder = interval % period; + + if ( expired > bucket->burst || interval < 0 ) + { + bucket->burst = 0; + bucket->lastTime = now; + } + else + { + bucket->burst -= expired; + bucket->lastTime = now - expiredRemainder; + } + + if ( bucket->burst < burst ) + { + bucket->burst++; + return false; + } + else + { + SV_WriteAttackLogD(va("SVC_RateLimit: burst limit exceeded for bucket: %i limit: %i\n", bucket->burst, burst)); + } + } + + return true; +} + +/* +================ +SVC_RateLimitAddress + +Rate limit for a particular address +================ +*/ +bool SVC_RateLimitAddress( netadr_t from, int burst, int period ) +{ + leakyBucket_t *bucket = SVC_BucketForAddress( from, burst, period ); + return SVC_RateLimit( bucket, burst, period ); +} + +/* +================ +SVC_Status + +Responds with all the info that qplug or qspy can see about the server +and all connected players. Used for getting detailed information after +the simple info query. +================ +*/ +static void SVC_Status( netadr_t from ) { + char player[1024]; + char status[MAX_MSGLEN]; + int i; + client_t *cl; + playerState_t *ps; + int statusLength; + int playerLength; + char infostring[MAX_INFO_STRING]; + + if (sv_protect->integer & SVP_IOQ3) { + // Prevent using getstatus as an amplifier + if (SVC_RateLimitAddress(from, 10, 1000)) { + SV_WriteAttackLog(va("SVC_Status: rate limit from %s exceeded, dropping request\n", + NET_AdrToString(from))); + return; + } + + // Allow getstatus to be DoSed relatively easily, but prevent + // excess outbound bandwidth usage when being flooded inbound + if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) { + SV_WriteAttackLog("SVC_Status: rate limit exceeded, dropping request\n"); + return; + } + } + + // A maximum challenge length of 128 should be more than plenty. + if (strlen(Cmd_Argv(1)) > 128) { + SV_WriteAttackLog(va("SVC_Status: challenge length exceeded from %s, dropping request\n", NET_AdrToString(from))); + return; + } + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so master servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + if ( from.alternateProtocol != 0 ) + Info_SetValueForKey( infostring, "protocol", from.alternateProtocol == 2 ? "69" : "70" ); + + status[0] = 0; + statusLength = 0; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + ps = SV_GameClientNum( i ); + Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", + ps->persistant[PERS_SCORE], cl->ping, cl->name); + playerLength = strlen(player); + if (statusLength + playerLength >= sizeof(status) ) { + break; // can't hold any more + } + strcpy (status + statusLength, player); + statusLength += playerLength; + } + } + + NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); +} + +/* +================ +SVC_Info + +Responds with a short info message that should be enough to determine +if a user is interested in a server to do a full status +================ +*/ +void SVC_Info( netadr_t from ) { + int i, count; + const char *gamedir; + char infostring[MAX_INFO_STRING]; + + if (sv_protect->integer & SVP_IOQ3) { + // Prevent using getinfo as an amplifier + if (SVC_RateLimitAddress(from, 10, 1000)) { + SV_WriteAttackLog(va("SVC_Info: rate limit from %s exceeded, dropping request\n", + NET_AdrToString(from))); + return; + } + + // Allow getinfo to be DoSed relatively easily, but prevent + // excess outbound bandwidth usage when being flooded inbound + if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) { + SV_WriteAttackLog("SVC_Info: rate limit exceeded, dropping request\n"); + return; + } + } + + // Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led + // to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory. + // A maximum challenge length of 128 should be more than plenty. + if (strlen(Cmd_Argv(1)) > 128) { + SV_WriteAttackLog(va("SVC_Info: challenge length from %s exceeded, dropping request\n", NET_AdrToString(from))); + return; + } + + // don't count privateclients + count = 0; + for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + + infostring[0] = 0; + + // echo back the parameter to status. so servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + Info_SetValueForKey( infostring, "protocol", va("%i", from.alternateProtocol == 2 ? 69 : from.alternateProtocol == 1 ? 70 : PROTOCOL_VERSION) ); + Info_SetValueForKey( infostring, "gamename", com_gamename->string ); + Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); + Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); + Info_SetValueForKey( infostring, "clients", va("%i", count) ); + Info_SetValueForKey( infostring, "sv_maxclients", + va("%i", sv_maxclients->integer - sv_privateClients->integer ) ); + Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) ); + +#ifdef USE_VOIP + if (sv_voipProtocol->string && *sv_voipProtocol->string) { + Info_SetValueForKey( infostring, "voip", sv_voipProtocol->string ); + } +#endif + + if( sv_minPing->integer ) { + Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) ); + } + if( sv_maxPing->integer ) { + Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) ); + } + gamedir = Cvar_VariableString( "fs_game" ); + if( *gamedir ) { + Info_SetValueForKey( infostring, "game", gamedir ); + } + + NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring ); +} + +/* +================ +SVC_FlushRedirect + +================ +*/ +static void SV_FlushRedirect( char *outputbuf ) { + NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf ); +} + +/** + * @brief DRDoS stands for "Distributed Reflected Denial of Service". + * See here: http://www.lemuria.org/security/application-drdos.html + * + * If the address isn't NA_IP, it's automatically denied. + * + * @return false if we're good. + * otherwise true means we need to block. + * + * @note Don't call this if sv_protect 2 flag is not set! + */ +bool SV_CheckDRDoS(netadr_t from) { + int i; + int globalCount; + int specificCount; + int timeNow; + receipt_t *receipt; + netadr_t exactFrom; + int oldest; + int oldestTime; + static int lastGlobalLogTime = 0; + static int lastSpecificLogTime = 0; + + // Usually the network is smart enough to not allow incoming UDP packets + // with a source address being a spoofed LAN address. Even if that's not + // the case, sending packets to other hosts in the LAN is not a big deal. + // NA_LOOPBACK qualifies as a LAN address. + if (Sys_IsLANAddress(from)) { + return false; + } + + timeNow = svs.time; + exactFrom = from; + + // Time has wrapped + if (lastGlobalLogTime > timeNow || lastSpecificLogTime > timeNow) { + lastGlobalLogTime = 0; + lastSpecificLogTime = 0; + + // just setting time to 1 (cannot be 0 as then globalCount would not be counted) + for (i = 0; i < MAX_INFO_RECEIPTS; i++) { + if (svs.infoReceipts[i].time) { + svs.infoReceipts[i].time = 1; // hack it so we count globalCount correctly + } + } + } + + if (from.type == NA_IP) { + from.ip[3] = 0; // xx.xx.xx.0 + } else { + from.ip6[15] = 0; + } + + // Count receipts in last 2 seconds. + globalCount = 0; + specificCount = 0; + receipt = &svs.infoReceipts[0]; + oldest = 0; + oldestTime = 0x7fffffff; + for (i = 0; i < MAX_INFO_RECEIPTS; i++, receipt++) { + if (receipt->time + 2000 > timeNow) { + if (receipt->time) { + // When the server starts, all receipt times are at zero. Furthermore, + // svs.time is close to zero. We check that the receipt time is already + // set so that during the first two seconds after server starts, queries + // from the master servers don't get ignored. As a consequence a potentially + // unlimited number of getinfo+getstatus responses may be sent during the + // first frame of a server's life. + globalCount++; + } + if (NET_CompareBaseAdr(from, receipt->adr)) { + specificCount++; + } + } + if (receipt->time < oldestTime) { + oldestTime = receipt->time; + oldest = i; + } + } + + if (globalCount == MAX_INFO_RECEIPTS) { // All receipts happened in last 2 seconds. + if (lastGlobalLogTime + 1000 <= timeNow) { // Limit one log every second. + SV_WriteAttackLog("Detected flood of getinfo/getstatus connectionless packets\n"); + lastGlobalLogTime = timeNow; + } + + return true; + } + if (specificCount >= 3) { // Already sent 3 to this IP in last 2 seconds. + if (lastSpecificLogTime + 1000 <= timeNow) { // Limit one log every second. + SV_WriteAttackLog(va("Possible DRDoS attack to address %s, ignoring getinfo/getstatus connectionless packet\n", + NET_AdrToString(exactFrom))); + lastSpecificLogTime = timeNow; + } + + return true; + } + + receipt = &svs.infoReceipts[oldest]; + receipt->adr = from; + receipt->time = timeNow; + return false; +} + +/* +=============== +SVC_RemoteCommand + +An rcon packet arrived from the network. +Shift down the remaining args +Redirect all printfs +=============== +*/ +static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { + bool valid; + char remaining[1024]; + // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. + // (OOB messages are the bottleneck here) +#define SV_OUTPUTBUF_LENGTH (1024 - 16) + char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; + char *cmd_aux; + + // Prevent using rcon as an amplifier and make dictionary attacks impractical + if ((sv_protect->integer & SVP_IOQ3) && SVC_RateLimitAddress(from, 10, 1000)) { + SV_WriteAttackLog(va("Bad rcon - rate limit from %s exceeded, dropping request\n", + NET_AdrToString(from))); + return; + } + + if ( !strlen( sv_rconPassword->string ) || + strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { + static leakyBucket_t bucket; + + // Make DoS via rcon impractical + if ((sv_protect->integer & SVP_IOQ3) && SVC_RateLimit(&bucket, 10, 1000)) { + SV_WriteAttackLog("Bad rcon - rate limit exceeded, dropping request\n"); + return; + } + + valid = false; + Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) ); + } else { + valid = true; + Com_Printf ("Rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) ); + SV_WriteAttackLog(va("Rcon from %s: %s\n", NET_AdrToString(from), Cmd_Argv(2))); + } + + // start redirecting all print outputs to the packet + svs.redirectAddress = from; + Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect); + + if ( !strlen( sv_rconPassword->string ) ) { + Com_Printf ("No rconpassword set on the server.\n"); + } else if ( !valid ) { + Com_Printf ("Bad rconpassword.\n"); + SV_WriteAttackLog(va("Bad rconpassword from %s\n", NET_AdrToString(from))); + } else { + remaining[0] = 0; + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + // get the command directly, "rcon " to avoid quoting issues + // extract the command by walking + // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing + cmd_aux = Cmd_Cmd(); + cmd_aux+=4; + while(cmd_aux[0]==' ') + cmd_aux++; + while(cmd_aux[0] && cmd_aux[0]!=' ') // password + cmd_aux++; + while(cmd_aux[0]==' ') + cmd_aux++; + + Q_strcat( remaining, sizeof(remaining), cmd_aux); + + Cmd_ExecuteString (remaining); + + } + + Com_EndRedirect (); +} + +static void SVC_SchachtmeisterResponse( netadr_t from ) { + + int tmp[ 4 ]; + + if ( !( from.type == NA_IP && from.ip[0] == 127 ) ) { + return; + } + + if ( Cmd_Argc() >= 2 && sscanf( Cmd_Argv( 1 ), "%i.%i.%i.%i", &tmp[ 0 ], &tmp[ 1 ], &tmp[ 2 ], &tmp[ 3 ] ) == 4 ) { // compatibility with out-of-date crapware conceived in the future + char cmdl[ MAX_STRING_CHARS ]; + Com_sprintf( cmdl, sizeof( cmdl ), "smr ipa %s", Cmd_ArgsFrom( 1 ) ); + Cmd_TokenizeString( cmdl ); + } else { + strcpy( Cmd_Argv( 0 ), "smr" ); + } + + SV_GameCommand(); +} + +/* +================= +SV_ConnectionlessPacket + +A connectionless packet has four leading 0xff +characters to distinguish it from a game channel. +Clients that are in the game can still send +connectionless packets. +================= +*/ +static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + const char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 marker + + if (!Q_strncmp("connect", (char *) &msg->data[4], 7)) { + Huff_Decompress(msg, 12); + } + + s = MSG_ReadStringLine( msg ); + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); + + if (!Q_stricmp(c, "getstatus")) { + if ((sv_protect->integer & SVP_OWOLF) && SV_CheckDRDoS(from)) { + return; + } + + SVC_Status( from ); + } else if (!Q_stricmp(c, "getinfo")) { + if ((sv_protect->integer & SVP_OWOLF) && SV_CheckDRDoS(from)) { + return; + } + + SVC_Info( from ); + } else if (!Q_stricmp(c, "getchallenge")) { + if ((sv_protect->integer & SVP_OWOLF) && SV_CheckDRDoS(from)) { + return; + } + + SV_GetChallenge(from); + } else if (!Q_stricmp(c, "connect")) { + SV_DirectConnect( from ); + } else if (!Q_stricmp(c, "rcon")) { + SVC_RemoteCommand( from, msg ); + } else if (!Q_stricmp(c, "disconnect")) { + // if a client starts up a local server, we may see some spurious + // server disconnect messages when their new server sees our final + // sequenced messages to the old client + } else if (!Q_stricmp(c, "sm2reply")) { + SVC_SchachtmeisterResponse( from ); + Com_Printf( "^2response [^7%s^2]\n", s ); + } else { + SV_WriteAttackLog(va("bad connectionless packet from %s:\n%s\n" // changed from Com_DPrintf to print in attack log + , NET_AdrToString(from), s)); // this was never reported to admins before so they might be confused + } // note: if protect log isn't set we do Com_Printf +} + +//============================================================================ + +/* +================= +SV_PacketEvent +================= +*/ +void SV_PacketEvent( netadr_t from, msg_t *msg ) { + int i; + client_t *cl; + int qport; + + // check for connectionless packet (0xffffffff) first + if ( msg->cursize >= 4 && *(int *)msg->data == -1) { + SV_ConnectionlessPacket( from, msg ); + return; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // sequence number + qport = MSG_ReadShort( msg ) & 0xffff; + + // find which client the message is from + for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if (cl->state == CS_FREE) { + continue; + } + if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { + continue; + } + // it is possible to have multiple clients from a single IP + // address, so they are differentiated by the qport variable + if (cl->netchan.qport != qport) { + continue; + } + + // the IP port can't be used to differentiate them, because + // some address translating routers periodically change UDP + // port assignments + if (cl->netchan.remoteAddress.port != from.port) { + Com_Printf( "SV_PacketEvent: fixing up a translated port\n" ); + cl->netchan.remoteAddress.port = from.port; + } + + // make sure it is a valid, in sequence packet + if (SV_Netchan_Process(cl, msg)) { + // zombie clients still need to do the Netchan_Process + // to make sure they don't need to retransmit the final + // reliable message, but they don't do any other processing + if (cl->state != CS_ZOMBIE) { + cl->lastPacketTime = svs.time; // don't timeout + SV_ExecuteClientMessage( cl, msg ); + } + } + return; + } +} + + +/* +=================== +SV_CalcPings + +Updates the cl->ping variables +=================== +*/ +static void SV_CalcPings( void ) { + int i, j; + client_t *cl; + int total, count; + int delta; + playerState_t *ps; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state != CS_ACTIVE ) { + cl->ping = 999; + continue; + } + if ( !cl->gentity ) { + cl->ping = 999; + continue; + } + + total = 0; + count = 0; + for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { + if ( cl->frames[j].messageAcked <= 0 ) { + continue; + } + delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; + count++; + total += delta; + } + if (!count) { + cl->ping = 999; + } else { + cl->ping = total/count; + if ( cl->ping > 999 ) { + cl->ping = 999; + } + } + + // let the game dll know about the ping + ps = SV_GameClientNum( i ); + ps->ping = cl->ping; + } +} + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for timeout->integer +seconds, drop the conneciton. Server time is used instead of +realtime to avoid dropping the local client while debugging. + +When a client is normally dropped, the client_t goes into a zombie state +for a few seconds to make sure any final reliable message gets resent +if necessary +================== +*/ +static void SV_CheckTimeouts( void ) { + int i; + client_t *cl; + int droppoint; + int zombiepoint; + + droppoint = svs.time - 1000 * sv_timeout->integer; + zombiepoint = svs.time - 1000 * sv_zombietime->integer; + + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + // message times may be wrong across a changelevel + if (cl->lastPacketTime > svs.time) { + cl->lastPacketTime = svs.time; + } + + if (cl->state == CS_ZOMBIE + && cl->lastPacketTime < zombiepoint) { + // using the client id cause the cl->name is empty at this point + Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i ); + cl->state = CS_FREE; // can now be reused + continue; + } + if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) { + // wait several frames so a debugger session doesn't + // cause a timeout + if ( ++cl->timeoutCount > 5 ) { + SV_DropClient (cl, "timed out"); + cl->state = CS_FREE; // don't bother with zombie state + } + } else { + cl->timeoutCount = 0; + } + } +} + + +/* +================== +SV_CheckPaused +================== +*/ +static bool SV_CheckPaused( void ) { + int count; + client_t *cl; + int i; + + if ( !cl_paused->integer ) { + return false; + } + + // only pause if there is just a single client connected + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state >= CS_CONNECTED ) { + count++; + } + } + + if ( count > 1 ) { + // don't pause + if (sv_paused->integer) + Cvar_Set("sv_paused", "0"); + return false; + } + + if (!sv_paused->integer) + Cvar_Set("sv_paused", "1"); + return true; +} + +/* +================== +SV_FrameMsec +Return time in millseconds until processing of the next server frame. +================== +*/ +int SV_FrameMsec() +{ + if(sv_fps) + { + int frameMsec; + + frameMsec = 1000.0f / sv_fps->value; + + if(frameMsec < sv.timeResidual) + return 0; + else + return frameMsec - sv.timeResidual; + } + else + return 1; +} + +#define CPU_USAGE_WARNING 70 +#define FRAME_TIME_WARNING 30 + +/* +================== +SV_Frame + +Player movement occurs as a result of packet events, which +happen before SV_Frame is called +================== +*/ +void SV_Frame( int msec ) { + int frameMsec; + int startTime; + int frameStartTime = 0; + static int start, end; + + start = Sys_Milliseconds(); + svs.stats.idle += ( double )(start - end) / 1000; + + // the menu kills the server with this cvar + if ( sv_killserver->integer ) { + SV_Shutdown ("Server was killed"); + Cvar_Set( "sv_killserver", "0" ); + return; + } + + if (!com_sv_running->integer) + { + // Running as a server, but no map loaded +#ifdef DEDICATED + // Block until something interesting happens + Sys_Sleep(-1); +#endif + + return; + } + + // allow pause if only the local client is connected + if ( SV_CheckPaused() ) { + return; + } + + if (com_dedicated->integer) + { + frameStartTime = Sys_Milliseconds(); + } + + // if it isn't time for the next frame, do nothing + if ( sv_fps->integer < 1 ) { + Cvar_Set( "sv_fps", "10" ); + } + + frameMsec = 1000 / sv_fps->integer * com_timescale->value; + // don't let it scale below 1ms + if(frameMsec < 1) + { + Cvar_Set("timescale", va("%f", sv_fps->integer / 1000.0f)); + frameMsec = 1; + } + + sv.timeResidual += msec; + + // if time is about to hit the 32nd bit, kick all clients + // and clear sv.time, rather + // than checking for negative time wraparound everywhere. + // 2giga-milliseconds = 23 days, so it won't be too often + if ( svs.time > 0x70000000 ) { + SV_Shutdown( "Restarting server due to time wrapping" ); + Cbuf_AddText( va( "map \"%s\"\n", Cvar_VariableString( "mapname" ) ) ); + return; + } + // this can happen considerably earlier when lots of clients play and the map doesn't change + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) { + SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" ); + Cbuf_AddText( va( "map \"%s\"\n", Cvar_VariableString( "mapname" ) ) ); + return; + } + + if( sv.restartTime && sv.time >= sv.restartTime ) { + sv.restartTime = 0; + Cbuf_AddText( "map_restart 0\n" ); + return; + } + + // update infostrings if anything has been changed + if ( cvar_modifiedFlags & CVAR_SERVERINFO ) { + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + } + if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) { + SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + } + + if ( com_speeds->integer ) { + startTime = Sys_Milliseconds (); + } else { + startTime = 0; // quite a compiler warning + } + + // update ping based on the all received frames + SV_CalcPings(); + + // run the game simulation in chunks + while ( sv.timeResidual >= frameMsec ) { + sv.timeResidual -= frameMsec; + svs.time += frameMsec; + sv.time += frameMsec; + + // let everything in the world think and move + VM_Call (sv.gvm, GAME_RUN_FRAME, sv.time); + } + + if ( com_speeds->integer ) { + time_game = Sys_Milliseconds () - startTime; + } + + // check timeouts + SV_CheckTimeouts(); + + // send messages back to the clients + SV_SendClientMessages(); + + // send a heartbeat to the master if needed + SV_MasterHeartbeat(HEARTBEAT_FOR_MASTER); + + if (com_dedicated->integer) + { + int frameEndTime = Sys_Milliseconds(); + + svs.totalFrameTime += (frameEndTime - frameStartTime); + + // we may send warnings (similar to watchdog) to the game in case the frametime is unacceptable + //Com_Printf("FRAMETIME frame: %i total %i\n", frameEndTime - frameStartTime, svs.totalFrameTime); + + svs.currentFrameIndex++; + + //if( svs.currentFrameIndex % 50 == 0 ) + // Com_Printf( "currentFrameIndex: %i\n", svs.currentFrameIndex ); + + if (svs.currentFrameIndex == SERVER_PERFORMANCECOUNTER_FRAMES) + { + int averageFrameTime = svs.totalFrameTime / SERVER_PERFORMANCECOUNTER_FRAMES; + + svs.sampleTimes[svs.currentSampleIndex % SERVER_PERFORMANCECOUNTER_SAMPLES] = averageFrameTime; + svs.currentSampleIndex++; + + if (svs.currentSampleIndex > SERVER_PERFORMANCECOUNTER_SAMPLES) + { + int totalTime = 0, i; + + for (i = 0; i < SERVER_PERFORMANCECOUNTER_SAMPLES; i++) + { + totalTime += svs.sampleTimes[i]; + } + + if (!totalTime) + { + totalTime = 1; + } + + averageFrameTime = totalTime / SERVER_PERFORMANCECOUNTER_SAMPLES; + + svs.serverLoad = (int)((averageFrameTime / (float)frameMsec) * 100); + } + + //Com_Printf( "serverload: %i (%i/%i)\n", svs.serverLoad, averageFrameTime, frameMsec ); + + svs.totalFrameTime = 0; + svs.currentFrameIndex = 0; + } + } + else + { + svs.serverLoad = -1; + } + + // collect timing statistics + // - the above 2.60 performance thingy is just inaccurate (30 seconds 'stats') + // to give good warning messages and is only done for dedicated + end = Sys_Milliseconds(); + svs.stats.active += (( double )(end - start)) / 1000; + + if (++svs.stats.count == STATFRAMES) // 5 seconds + { + svs.stats.latched_active = svs.stats.active; + svs.stats.latched_idle = svs.stats.idle; + svs.stats.active = 0; + svs.stats.idle = 0; + svs.stats.count = 0; + + svs.stats.cpu = svs.stats.latched_active + svs.stats.latched_idle; + + if (svs.stats.cpu != 0.f) + { + svs.stats.cpu = 100 * svs.stats.latched_active / svs.stats.cpu; + } + + svs.stats.avg = 1000 * svs.stats.latched_active / STATFRAMES; + + // FIXME: add mail, IRC, player info etc for both warnings + // TODO: inspect/adjust these values and/or add cvars + if (svs.stats.cpu > CPU_USAGE_WARNING) + { + Com_Printf("^3WARNING: Server CPU has reached a critical usage of %i%%\n", (int) svs.stats.cpu); + } + + if (svs.stats.avg > FRAME_TIME_WARNING) + { + Com_Printf("^3WARNING: Average frame time has reached a critical value of %ims\n", (int) svs.stats.avg); + } + } +} + +/* +==================== +SV_RateMsec + +Return the number of msec until another message can be sent to +a client based on its rate settings +==================== +*/ + +#define UDPIP_HEADER_SIZE 28 +#define UDPIP6_HEADER_SIZE 48 + +int SV_RateMsec(client_t *client) +{ + int rate, rateMsec; + int messageSize; + + messageSize = client->netchan.lastSentSize; + rate = client->rate; + + if(sv_maxRate->integer) + { + if(sv_maxRate->integer < 1000) + Cvar_Set( "sv_MaxRate", "1000" ); + if(sv_maxRate->integer < rate) + rate = sv_maxRate->integer; + } + + if(sv_minRate->integer) + { + if(sv_minRate->integer < 1000) + Cvar_Set("sv_minRate", "1000"); + if(sv_minRate->integer > rate) + rate = sv_minRate->integer; + } + + if(client->netchan.remoteAddress.type == NA_IP6) + messageSize += UDPIP6_HEADER_SIZE; + else + messageSize += UDPIP_HEADER_SIZE; + + rate = (int)(rate * com_timescale->value); + if(rate < 1) + rate = 1; + rateMsec = messageSize * 1000 / rate; + rate = Sys_Milliseconds() - client->netchan.lastSentTime; + + if(rate > rateMsec) + return 0; + else + return rateMsec - rate; +} + +/* +==================== +SV_SendQueuedPackets + +Send download messages and queued packets in the time that we're idle, i.e. +not computing a server frame or sending client snapshots. +Return the time in msec until we expect to be called next +==================== +*/ + +int SV_SendQueuedPackets() +{ + int numBlocks; + int dlStart, deltaT, delayT; + static int dlNextRound = 0; + int timeVal = INT_MAX; + + // Send out fragmented packets now that we're idle + delayT = SV_SendQueuedMessages(); + if(delayT >= 0) + timeVal = delayT; + + if(sv_dlRate->integer) + { + // Rate limiting. This is very imprecise for high + // download rates due to millisecond timedelta resolution + dlStart = Sys_Milliseconds(); + deltaT = dlNextRound - dlStart; + + if(deltaT > 0) + { + if(deltaT < timeVal) + timeVal = deltaT + 1; + } + else + { + numBlocks = SV_SendDownloadMessages(); + + if(numBlocks) + { + // There are active downloads + deltaT = Sys_Milliseconds() - dlStart; + + delayT = 1000 * numBlocks * MAX_DOWNLOAD_BLKSIZE; + delayT /= sv_dlRate->integer * 1024; + + if(delayT <= deltaT + 1) + { + // Sending the last round of download messages + // took too long for given rate, don't wait for + // next round, but always enforce a 1ms delay + // between DL message rounds so we don't hog + // all of the bandwidth. This will result in an + // effective maximum rate of 1MB/s per user, but the + // low download window size limits this anyways. + if(timeVal > 2) + timeVal = 2; + + dlNextRound = dlStart + deltaT + 1; + } + else + { + dlNextRound = dlStart + delayT; + delayT -= deltaT; + + if(delayT < timeVal) + timeVal = delayT; + } + } + } + } + else + { + if(SV_SendDownloadMessages()) + timeVal = 0; + } + + return timeVal; +} diff --git a/src/server/sv_net_chan.cpp b/src/server/sv_net_chan.cpp new file mode 100644 index 0000000..f8d9b7e --- /dev/null +++ b/src/server/sv_net_chan.cpp @@ -0,0 +1,259 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "server.h" + +#include "qcommon/q_shared.h" +#include "qcommon/msg.h" +#include "qcommon/net.h" +#include "qcommon/qcommon.h" + +/* +============== +SV_Netchan_Encode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Encode( client_t *client, msg_t *msg ) { + long i, index; + byte key, *string; + int srdc, sbit; + bool soob; + + if ( msg->cursize < SV_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = false; + + /* reliableAcknowledge = */ MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->lastClientCommandString; + index = 0; + // xor the client challenge with the netchan sequence number + key = client->challenge ^ client->netchan.outgoingSequence; + for (i = SV_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received and with this message acknowledged client command + if (!string[index]) + index = 0; + if ( string[index] > 127 || (client->netchan.alternateProtocol == 2 && string[index] == '%')) + { + key ^= '.' << (i & 1); + } + else + { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +============== +SV_Netchan_Decode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit; + bool soob; + byte key, *string; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = false; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + + key = client->challenge ^ serverId ^ messageAcknowledge; + for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and acknowledged server command + if (!string[index]) + index = 0; + if (string[index] > 127 || (client->netchan.alternateProtocol == 2 && string[index] == '%')) { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +================= +SV_Netchan_FreeQueue +================= +*/ +void SV_Netchan_FreeQueue(client_t *client) +{ + netchan_buffer_t *netbuf, *next; + + for(netbuf = client->netchan_start_queue; netbuf; netbuf = next) + { + next = netbuf->next; + Z_Free(netbuf); + } + + client->netchan_start_queue = NULL; + client->netchan_end_queue = &client->netchan_start_queue; +} + +/* +================= +SV_Netchan_TransmitNextInQueue +================= +*/ +void SV_Netchan_TransmitNextInQueue(client_t *client) +{ + netchan_buffer_t *netbuf; + + Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); + netbuf = client->netchan_start_queue; + + Netchan_Transmit(&client->netchan, netbuf->msg.cursize, netbuf->msg.data); + + // pop from queue + client->netchan_start_queue = netbuf->next; + if(!client->netchan_start_queue) + { + Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); + client->netchan_end_queue = &client->netchan_start_queue; + } + else + Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); + + Z_Free(netbuf); +} + +/* +================= +SV_Netchan_TransmitNextFragment +Transmit the next fragment and the next queued packet +Return number of ms until next message can be sent based on throughput given by client rate, +-1 if no packet was sent. +================= +*/ + +int SV_Netchan_TransmitNextFragment(client_t *client) +{ + if(client->netchan.unsentFragments) + { + Netchan_TransmitNextFragment(&client->netchan); + return SV_RateMsec(client); + } + else if(client->netchan_start_queue) + { + SV_Netchan_TransmitNextInQueue(client); + return SV_RateMsec(client); + } + + return -1; +} + + +/* +=============== +SV_Netchan_Transmit +TTimo +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 +if there are some unsent fragments (which may happen if the snapshots +and the gamestate are fragmenting, and collide on send for instance) +then buffer them and make sure they get sent in correct order +================ +*/ + +void SV_Netchan_Transmit( client_t *client, msg_t *msg) +{ + MSG_WriteByte( msg, svc_EOF ); + + if(client->netchan.unsentFragments || client->netchan_start_queue) + { + netchan_buffer_t *netbuf; + Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n"); + netbuf = (netchan_buffer_t *) Z_Malloc(sizeof(netchan_buffer_t)); + // store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending + MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg); + netbuf->next = NULL; + // insert it in the queue, the message will be encoded and sent later + *client->netchan_end_queue = netbuf; + client->netchan_end_queue = &(*client->netchan_end_queue)->next; + } + else + { + if (client->netchan.alternateProtocol != 0) + SV_Netchan_Encode( client, msg ); + Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); + } +} + +/* +================= +Netchan_SV_Process +================= +*/ +bool SV_Netchan_Process( client_t *client, msg_t *msg ) +{ + bool ret = Netchan_Process( &client->netchan, msg ); + if (!ret) return false; + + if (client->netchan.alternateProtocol != 0) + SV_Netchan_Decode( client, msg ); + + return true; +} diff --git a/src/server/sv_snapshot.cpp b/src/server/sv_snapshot.cpp new file mode 100644 index 0000000..07dd210 --- /dev/null +++ b/src/server/sv_snapshot.cpp @@ -0,0 +1,749 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "server.h" + +/* +============================================================================= + +Delta encode a client frame onto the network channel + +A normal server packet will look like: + +4 sequence number (high bit set if an oversize fragment) + +1 svc_snapshot +4 last client reliable command +4 serverTime +1 lastframe for delta compression +1 snapFlags +1 areaBytes + + + + +============================================================================= +*/ + +/* +============= +SV_EmitPacketEntities + +Writes a delta update of an entityState_t list to the message. +============= +*/ +static void SV_EmitPacketEntities(int alternateProtocol, clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg) +{ + entityState_t *oldent, *newent; + int oldindex, newindex; + int oldnum, newnum; + int from_num_entities; + + // generate the delta update + if (!from) + { + from_num_entities = 0; + } + else + { + from_num_entities = from->num_entities; + } + + newent = NULL; + oldent = NULL; + newindex = 0; + oldindex = 0; + while (newindex < to->num_entities || oldindex < from_num_entities) + { + if (newindex >= to->num_entities) + { + newnum = 9999; + } + else + { + newent = &svs.snapshotEntities[(to->first_entity + newindex) % svs.numSnapshotEntities]; + newnum = newent->number; + } + + if (oldindex >= from_num_entities) + { + oldnum = 9999; + } + else + { + oldent = &svs.snapshotEntities[(from->first_entity + oldindex) % svs.numSnapshotEntities]; + oldnum = oldent->number; + } + + if (newnum == oldnum) + { + // delta update from old position + // because the force parm is false, this will not result + // in any bytes being emited if the entity has not changed at all + MSG_WriteDeltaEntity(alternateProtocol, msg, oldent, newent, false); + oldindex++; + newindex++; + continue; + } + + if (newnum < oldnum) + { + // this is a new entity, send it from the baseline + MSG_WriteDeltaEntity(alternateProtocol, msg, &sv.svEntities[newnum].baseline, newent, true); + newindex++; + continue; + } + + if (newnum > oldnum) + { + // the old entity isn't present in the new message + MSG_WriteDeltaEntity(alternateProtocol, msg, oldent, NULL, true); + oldindex++; + continue; + } + } + + MSG_WriteBits(msg, (MAX_GENTITIES - 1), GENTITYNUM_BITS); // end of packetentities +} + +/* +================== +SV_WriteSnapshotToClient +================== +*/ +static void SV_WriteSnapshotToClient(client_t *client, msg_t *msg) +{ + clientSnapshot_t *frame, *oldframe; + int lastframe; + int i; + int snapFlags; + + // this is the snapshot we are creating + frame = &client->frames[client->netchan.outgoingSequence & PACKET_MASK]; + + // try to use a previous frame as the source for delta compressing the snapshot + if (client->deltaMessage <= 0 || client->state != CS_ACTIVE) + { + // client is asking for a retransmit + oldframe = NULL; + lastframe = 0; + } + else if (client->netchan.outgoingSequence - client->deltaMessage >= (PACKET_BACKUP - 3)) + { + // client hasn't gotten a good message through in a long time + Com_DPrintf("%s: Delta request from out of date packet.\n", client->name); + oldframe = NULL; + lastframe = 0; + } + else + { + // we have a valid snapshot to delta from + oldframe = &client->frames[client->deltaMessage & PACKET_MASK]; + lastframe = client->netchan.outgoingSequence - client->deltaMessage; + + // the snapshot's entities may still have rolled off the buffer, though + if (oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities) + { + Com_DPrintf("%s: Delta request from out of date entities.\n", client->name); + oldframe = NULL; + lastframe = 0; + } + } + + MSG_WriteByte(msg, svc_snapshot); + + // NOTE, MRE: now sent at the start of every message from server to client + // let the client know which reliable clientCommands we have received + // MSG_WriteLong( msg, client->lastClientCommand ); + + // send over the current server time so the client can drift + // its view of time to try to match + if (client->oldServerTime) + { + // The server has not yet got an acknowledgement of the + // new gamestate from this client, so continue to send it + // a time as if the server has not restarted. Note from + // the client's perspective this time is strictly speaking + // incorrect, but since it'll be busy loading a map at + // the time it doesn't really matter. + MSG_WriteLong(msg, sv.time + client->oldServerTime); + } + else + { + MSG_WriteLong(msg, sv.time); + } + + // what we are delta'ing from + MSG_WriteByte(msg, lastframe); + + snapFlags = svs.snapFlagServerBit; + if (client->rateDelayed) + { + snapFlags |= SNAPFLAG_RATE_DELAYED; + } + if (client->state != CS_ACTIVE) + { + snapFlags |= SNAPFLAG_NOT_ACTIVE; + } + + MSG_WriteByte(msg, snapFlags); + + // send over the areabits + MSG_WriteByte(msg, frame->areabytes); + MSG_WriteData(msg, frame->areabits, frame->areabytes); + + // delta encode the playerstate + if (oldframe) + { + MSG_WriteDeltaPlayerstate(client->netchan.alternateProtocol, msg, &oldframe->ps, &frame->ps); + } + else + { + MSG_WriteDeltaPlayerstate(client->netchan.alternateProtocol, msg, NULL, &frame->ps); + } + + // delta encode the entities + SV_EmitPacketEntities(client->netchan.alternateProtocol, oldframe, frame, msg); + + // padding for rate debugging + if (sv_padPackets->integer) + { + for (i = 0; i < sv_padPackets->integer; i++) + { + MSG_WriteByte(msg, svc_nop); + } + } +} + +/* +================== +SV_UpdateServerCommandsToClient + +(re)send all server commands the client hasn't acknowledged yet +================== +*/ +void SV_UpdateServerCommandsToClient(client_t *client, msg_t *msg) +{ + int i; + + // write any unacknowledged serverCommands + for (i = client->reliableAcknowledge + 1; i <= client->reliableSequence; i++) + { + MSG_WriteByte(msg, svc_serverCommand); + MSG_WriteLong(msg, i); + MSG_WriteString(msg, client->reliableCommands[i & (MAX_RELIABLE_COMMANDS - 1)]); + } + client->reliableSent = client->reliableSequence; +} + +/* +============================================================================= + +Build a client snapshot structure + +============================================================================= +*/ + +typedef struct { + int numSnapshotEntities; + int snapshotEntities[MAX_SNAPSHOT_ENTITIES]; +} snapshotEntityNumbers_t; + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int QDECL SV_QsortEntityNumbers(const void *a, const void *b) +{ + int *ea, *eb; + + ea = (int *)a; + eb = (int *)b; + + if (*ea == *eb) + { + Com_Error(ERR_DROP, "SV_QsortEntityStates: duplicated entity"); + } + + if (*ea < *eb) + { + return -1; + } + + return 1; +} + +/* +=============== +SV_AddEntToSnapshot +=============== +*/ +static void SV_AddEntToSnapshot(svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums) +{ + // if we have already added this entity to this snapshot, don't add again + if (svEnt->snapshotCounter == sv.snapshotCounter) + { + return; + } + svEnt->snapshotCounter = sv.snapshotCounter; + + // if we are full, silently discard entities + if (eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES) + { + return; + } + + eNums->snapshotEntities[eNums->numSnapshotEntities] = gEnt->s.number; + eNums->numSnapshotEntities++; +} + +/* +=============== +SV_AddEntitiesVisibleFromPoint +=============== +*/ +static void SV_AddEntitiesVisibleFromPoint(vec3_t origin, clientSnapshot_t *frame, snapshotEntityNumbers_t *eNums) +{ + int e, i; + sharedEntity_t *ent; + svEntity_t *svEnt; + int l; + int clientarea, clientcluster; + int leafnum; + byte *clientpvs; + byte *bitvector; + + // during an error shutdown message we may need to transmit + // the shutdown message after the server has shutdown, so + // specfically check for it + if (!sv.state) + { + return; + } + + leafnum = CM_PointLeafnum(origin); + clientarea = CM_LeafArea(leafnum); + clientcluster = CM_LeafCluster(leafnum); + + // calculate the visible areas + frame->areabytes = CM_WriteAreaBits(frame->areabits, clientarea); + + clientpvs = CM_ClusterPVS(clientcluster); + + for (e = 0; e < sv.num_entities; e++) + { + ent = SV_GentityNum(e); + + // never send entities that aren't linked in + if (!ent->r.linked) + { + continue; + } + + if (ent->s.number != e) + { + Com_DPrintf("FIXING ENT->S.NUMBER!!!\n"); + ent->s.number = e; + } + + // entities can be flagged to explicitly not be sent to the client + if (ent->r.svFlags & SVF_NOCLIENT) + { + continue; + } + + // entities can be flagged to be sent to only one client + if (ent->r.svFlags & SVF_SINGLECLIENT) + { + if (ent->r.singleClient != frame->ps.clientNum) + { + continue; + } + } + // entities can be flagged to be sent to everyone but one client + if (ent->r.svFlags & SVF_NOTSINGLECLIENT) + { + if (ent->r.singleClient == frame->ps.clientNum) + { + continue; + } + } + // entities can be flagged to be sent to a given mask of clients + if (ent->r.svFlags & SVF_CLIENTMASK) + { + if (frame->ps.clientNum >= 32) + { + if (~ent->r.hack.generic1 & (1 << (frame->ps.clientNum - 32))) continue; + } + else + { + if (~ent->r.singleClient & (1 << frame->ps.clientNum)) continue; + } + } + + svEnt = SV_SvEntityForGentity(ent); + + // don't double add an entity through portals + if (svEnt->snapshotCounter == sv.snapshotCounter) + { + continue; + } + + // broadcast entities are always sent + if (ent->r.svFlags & SVF_BROADCAST) + { + SV_AddEntToSnapshot(svEnt, ent, eNums); + continue; + } + + // ignore if not touching a PV leaf + // check area + if (!CM_AreasConnected(clientarea, svEnt->areanum)) + { + // doors can legally straddle two areas, so + // we may need to check another one + if (!CM_AreasConnected(clientarea, svEnt->areanum2)) + { + continue; // blocked by a door + } + } + + bitvector = clientpvs; + + // check individual leafs + if (!svEnt->numClusters) + { + continue; + } + l = 0; + for (i = 0; i < svEnt->numClusters; i++) + { + l = svEnt->clusternums[i]; + if (bitvector[l >> 3] & (1 << (l & 7))) + { + break; + } + } + + // if we haven't found it to be visible, + // check overflow clusters that coudln't be stored + if (i == svEnt->numClusters) + { + if (svEnt->lastCluster) + { + for (; l <= svEnt->lastCluster; l++) + { + if (bitvector[l >> 3] & (1 << (l & 7))) + { + break; + } + } + if (l == svEnt->lastCluster) + { + continue; // not visible + } + } + else + { + continue; + } + } + + // add it + SV_AddEntToSnapshot(svEnt, ent, eNums); + + // if it's a portal entity, add everything visible from its camera position + if (ent->r.svFlags & SVF_PORTAL) + { + if (ent->s.generic1) + { + vec3_t dir; + VectorSubtract(ent->r.currentOrigin, origin, dir); + if (VectorLengthSquared(dir) > (float)ent->s.generic1 * ent->s.generic1) + { + continue; + } + } + SV_AddEntitiesVisibleFromPoint(ent->s.origin2, frame, eNums); + } + } +} + +/* +============= +SV_BuildClientSnapshot + +Decides which entities are going to be visible to the client, and +copies off the playerstate and areabits. + +This properly handles multiple recursive portals, but the render +currently doesn't. + +For viewing through other player's eyes, clent can be something other than client->gentity +============= +*/ +static void SV_BuildClientSnapshot(client_t *client) +{ + vec3_t org; + clientSnapshot_t *frame; + snapshotEntityNumbers_t entityNumbers; + int i; + sharedEntity_t *ent; + entityState_t *state; + svEntity_t *svEnt; + sharedEntity_t *clent; + int clientNum; + playerState_t *ps; + + // bump the counter used to prevent double adding + sv.snapshotCounter++; + + // this is the frame we are creating + frame = &client->frames[client->netchan.outgoingSequence & PACKET_MASK]; + + // clear everything in this snapshot + entityNumbers.numSnapshotEntities = 0; + ::memset(frame->areabits, 0, sizeof(frame->areabits)); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62 + frame->num_entities = 0; + + clent = client->gentity; + if (!clent || client->state == CS_ZOMBIE) + { + return; + } + + // grab the current playerState_t + ps = SV_GameClientNum(client - svs.clients); + frame->ps = *ps; + + // never send client's own entity, because it can + // be regenerated from the playerstate + clientNum = frame->ps.clientNum; + if (clientNum < 0 || clientNum >= MAX_GENTITIES) + { + Com_Error(ERR_DROP, "SV_SvEntityForGentity: bad gEnt"); + } + svEnt = &sv.svEntities[clientNum]; + + svEnt->snapshotCounter = sv.snapshotCounter; + + // find the client's viewpoint + VectorCopy(ps->origin, org); + org[2] += ps->viewheight; + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesVisibleFromPoint(org, frame, &entityNumbers); + + // if there were portals visible, there may be out of order entities + // in the list which will need to be resorted for the delta compression + // to work correctly. This also catches the error condition + // of an entity being included twice. + qsort(entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, sizeof(entityNumbers.snapshotEntities[0]), + SV_QsortEntityNumbers); + + // now that all viewpoint's areabits have been OR'd together, invert + // all of them to make it a mask vector, which is what the renderer wants + for (i = 0; i < MAX_MAP_AREA_BYTES / 4; i++) + { + ((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1; + } + + // copy the entity states out + frame->num_entities = 0; + frame->first_entity = svs.nextSnapshotEntities; + for (i = 0; i < entityNumbers.numSnapshotEntities; i++) + { + ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); + state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; + *state = ent->s; + svs.nextSnapshotEntities++; + // this should never hit, map should always be restarted first in SV_Frame + if (svs.nextSnapshotEntities >= 0x7FFFFFFE) + { + Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped"); + } + frame->num_entities++; + } +} + +#ifdef USE_VOIP +/* +================== +SV_WriteVoipToClient + +Check to see if there is any VoIP queued for a client, and send if there is. +================== +*/ +static void SV_WriteVoipToClient(client_t *cl, msg_t *msg) +{ + int totalbytes = 0; + int i; + voipServerPacket_t *packet; + + if (cl->queuedVoipPackets) + { + // Write as many VoIP packets as we reasonably can... + for (i = 0; i < cl->queuedVoipPackets; i++) + { + packet = cl->voipPacket[(i + cl->queuedVoipIndex) % ARRAY_LEN(cl->voipPacket)]; + + if (!*cl->downloadName) + { + totalbytes += packet->len; + if (totalbytes > (msg->maxsize - msg->cursize) / 2) break; + + if (cl->netchan.alternateProtocol != 0) MSG_WriteByte(msg, svc_EOF); + MSG_WriteByte(msg, svc_voipSpeex); + if (cl->netchan.alternateProtocol != 0) MSG_WriteByte(msg, svc_voipSpeex + 1); + MSG_WriteShort(msg, packet->sender); + MSG_WriteByte(msg, (byte)packet->generation); + MSG_WriteLong(msg, packet->sequence); + MSG_WriteByte(msg, packet->frames); + MSG_WriteShort(msg, packet->len); + if (cl->netchan.alternateProtocol == 0) MSG_WriteBits(msg, packet->flags, VOIP_FLAGCNT); + MSG_WriteData(msg, packet->data, packet->len); + } + + Z_Free(packet); + } + + cl->queuedVoipPackets -= i; + cl->queuedVoipIndex += i; + cl->queuedVoipIndex %= ARRAY_LEN(cl->voipPacket); + } +} +#endif + +/* +======================= +SV_SendMessageToClient + +Called by SV_SendClientSnapshot and SV_SendClientGameState +======================= +*/ +void SV_SendMessageToClient(msg_t *msg, client_t *client) +{ + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; + + // send the datagram + SV_Netchan_Transmit(client, msg); +} + +/* +======================= +SV_SendClientSnapshot + +Also called by SV_FinalMessage + +======================= +*/ +void SV_SendClientSnapshot(client_t *client) +{ + byte msg_buf[MAX_MSGLEN]; + msg_t msg; + + // build the snapshot + SV_BuildClientSnapshot(client); + + MSG_Init(&msg, msg_buf, sizeof(msg_buf)); + msg.allowoverflow = true; + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong(&msg, client->lastClientCommand); + + // (re)send any reliable server commands + SV_UpdateServerCommandsToClient(client, &msg); + + // send over all the relevant entityState_t + // and the playerState_t + SV_WriteSnapshotToClient(client, &msg); + +#ifdef USE_VOIP + SV_WriteVoipToClient(client, &msg); +#endif + + // check for overflow + if (msg.overflowed) + { + Com_Printf("WARNING: msg overflowed for %s\n", client->name); + MSG_Clear(&msg); + } + + SV_SendMessageToClient(&msg, client); +} + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages(void) +{ + int i; + client_t *c; + + // send a message to each connected client + for (i = 0; i < sv_maxclients->integer; i++) + { + c = &svs.clients[i]; + + if (!c->state) continue; // not connected + + if (svs.time - c->lastSnapshotTime < c->snapshotMsec * com_timescale->value) continue; // It's not time yet + + if (*c->downloadName) continue; // Client is downloading, don't send snapshots + + if (c->netchan.unsentFragments || c->netchan_start_queue) + { + c->rateDelayed = true; + continue; // Drop this snapshot if the packet queue is still full or delta compression will break + } + + if (!(c->netchan.remoteAddress.type == NA_LOOPBACK || + (sv_lanForceRate->integer && Sys_IsLANAddress(c->netchan.remoteAddress)))) + { + // rate control for clients not on LAN + + if (SV_RateMsec(c) > 0) + { + // Not enough time since last packet passed through the line + c->rateDelayed = true; + continue; + } + } + + // generate and send a new message + SV_SendClientSnapshot(c); + c->lastSnapshotTime = svs.time; + c->rateDelayed = false; + } +} diff --git a/src/server/sv_world.cpp b/src/server/sv_world.cpp new file mode 100644 index 0000000..fd0710e --- /dev/null +++ b/src/server/sv_world.cpp @@ -0,0 +1,745 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// world.c -- world query functions + +#include "server.h" + +/* +================ +SV_ClipHandleForEntity + +Returns a headnode that can be used for testing or clipping to a +given entity. If the entity is a bsp model, the headnode will +be returned, otherwise a custom box tree will be constructed. +================ +*/ +clipHandle_t SV_ClipHandleForEntity(const sharedEntity_t *ent) +{ + if (ent->r.bmodel) + { + // explicit hulls in the BSP model + return CM_InlineModel(ent->s.modelindex); + } + if (ent->r.svFlags & SVF_CAPSULE) + { + // create a temp capsule from bounding box sizes + return CM_TempBoxModel(ent->r.mins, ent->r.maxs, true); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel(ent->r.mins, ent->r.maxs, qfalse); +} + +/* +=============================================================================== + +ENTITY CHECKING + +To avoid linearly searching through lists of entities during environment testing, +the world is carved up with an evenly spaced, axially aligned bsp tree. Entities +are kept in chains either at the final leafs, or at the first node that splits +them, which prevents having to deal with multiple fragments of a single entity. + +=============================================================================== +*/ + +struct worldSector_t { + int axis; // -1 = leaf node + float dist; + worldSector_t *children[2]; + svEntity_t *entities; +}; + +#define AREA_DEPTH 4 +#define AREA_NODES 64 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + +/* +=============== +SV_SectorList_f +=============== +*/ +void SV_SectorList_f(void) +{ + int i, c; + worldSector_t *sec; + svEntity_t *ent; + + for (i = 0; i < AREA_NODES; i++) + { + sec = &sv_worldSectors[i]; + + c = 0; + for (ent = sec->entities; ent; ent = ent->nextEntityInWorldSector) + { + c++; + } + Com_Printf("sector %i: %i entities\n", i, c); + } +} + +/* +=============== +SV_CreateworldSector + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +static worldSector_t *SV_CreateworldSector(int depth, vec3_t mins, vec3_t maxs) +{ + worldSector_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_worldSectors[sv_numworldSectors]; + sv_numworldSectors++; + + if (depth == AREA_DEPTH) + { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract(maxs, mins, size); + if (size[0] > size[1]) + { + anode->axis = 0; + } + else + { + anode->axis = 1; + } + + anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); + VectorCopy(mins, mins1); + VectorCopy(mins, mins2); + VectorCopy(maxs, maxs1); + VectorCopy(maxs, maxs2); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + + anode->children[0] = SV_CreateworldSector(depth + 1, mins2, maxs2); + anode->children[1] = SV_CreateworldSector(depth + 1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld(void) +{ + clipHandle_t h; + vec3_t mins, maxs; + + ::memset(sv_worldSectors, 0, sizeof(sv_worldSectors)); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel(0); + CM_ModelBounds(h, mins, maxs); + SV_CreateworldSector(0, mins, maxs); +} + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity(sharedEntity_t *gEnt) +{ + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + ent = SV_SvEntityForGentity(gEnt); + + gEnt->r.linked = qfalse; + + ws = ent->worldSector; + if (!ws) + { + return; // not linked in anywhere + } + ent->worldSector = NULL; + + if (ws->entities == ent) + { + ws->entities = ent->nextEntityInWorldSector; + return; + } + + for (scan = ws->entities; scan; scan = scan->nextEntityInWorldSector) + { + if (scan->nextEntityInWorldSector == ent) + { + scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; + return; + } + } + + Com_Printf("WARNING: SV_UnlinkEntity: not found in worldSector\n"); +} + +/* +=============== +SV_LinkEntity + +=============== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +void SV_LinkEntity(sharedEntity_t *gEnt) +{ + worldSector_t *node; + int leafs[MAX_TOTAL_ENT_LEAFS]; + int cluster; + int num_leafs; + int i, j, k; + int area; + int lastLeaf; + float *origin, *angles; + svEntity_t *ent; + + ent = SV_SvEntityForGentity(gEnt); + + if (ent->worldSector) + { + SV_UnlinkEntity(gEnt); // unlink from old position + } + + // encode the size into the entityState_t for client prediction + if (gEnt->r.bmodel) + { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } + else if (gEnt->r.contents & (CONTENTS_SOLID | CONTENTS_BODY)) + { + // assume that x/y are equal and symetric + i = gEnt->r.maxs[0]; + if (i < 1) i = 1; + if (i > 255) i = 255; + + // z is not symetric + j = (-gEnt->r.mins[2]); + if (j < 1) j = 1; + if (j > 255) j = 255; + + // and z maxs can be negative... + k = (gEnt->r.maxs[2] + 32); + if (k < 1) k = 1; + if (k > 255) k = 255; + + gEnt->s.solid = (k << 16) | (j << 8) | i; + } + else + { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + // set the abs box + if (gEnt->r.bmodel && (angles[0] || angles[1] || angles[2])) + { + // expand for rotation + float max; + + max = RadiusFromBounds(gEnt->r.mins, gEnt->r.maxs); + for (i = 0; i < 3; i++) + { + gEnt->r.absmin[i] = origin[i] - max; + gEnt->r.absmax[i] = origin[i] + max; + } + } + else + { + // normal + VectorAdd(origin, gEnt->r.mins, gEnt->r.absmin); + VectorAdd(origin, gEnt->r.maxs, gEnt->r.absmax); + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + gEnt->r.absmin[0] -= 1; + gEnt->r.absmin[1] -= 1; + gEnt->r.absmin[2] -= 1; + gEnt->r.absmax[0] += 1; + gEnt->r.absmax[1] += 1; + gEnt->r.absmax[2] += 1; + + // link to PVS leafs + ent->numClusters = 0; + ent->lastCluster = 0; + ent->areanum = -1; + ent->areanum2 = -1; + + // get all leafs, including solids + num_leafs = CM_BoxLeafnums(gEnt->r.absmin, gEnt->r.absmax, leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf); + + // if none of the leafs were inside the map, the + // entity is outside the world and can be considered unlinked + if (!num_leafs) + { + return; + } + + // set areas, even from clusters that don't fit in the entity array + for (i = 0; i < num_leafs; i++) + { + area = CM_LeafArea(leafs[i]); + if (area != -1) + { + // doors may legally straggle two areas, + // but nothing should evern need more than that + if (ent->areanum != -1 && ent->areanum != area) + { + if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) + { + Com_DPrintf("Object %i touching 3 areas at %f %f %f\n", gEnt->s.number, gEnt->r.absmin[0], + gEnt->r.absmin[1], gEnt->r.absmin[2]); + } + ent->areanum2 = area; + } + else + { + ent->areanum = area; + } + } + } + + // store as many explicit clusters as we can + ent->numClusters = 0; + for (i = 0; i < num_leafs; i++) + { + cluster = CM_LeafCluster(leafs[i]); + if (cluster != -1) + { + ent->clusternums[ent->numClusters++] = cluster; + if (ent->numClusters == MAX_ENT_CLUSTERS) + { + break; + } + } + } + + // store off a last cluster if we need to + if (i != num_leafs) + { + ent->lastCluster = CM_LeafCluster(lastLeaf); + } + + gEnt->r.linkcount++; + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + for ( ;; ) + { + if (node->axis == -1) + break; + + if (gEnt->r.absmin[node->axis] > node->dist) + node = node->children[0]; + else if (gEnt->r.absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + + // link it in + ent->worldSector = node; + ent->nextEntityInWorldSector = node->entities; + node->entities = ent; + + gEnt->r.linked = qtrue; +} + +/* +============================================================================ + +AREA QUERY + +Fills in a list of all entities who's absmin / absmax intersects the given +bounds. This does NOT mean that they actually touch in the case of bmodels. +============================================================================ +*/ + +struct areaParms_t { + const float *mins; + const float *maxs; + int *list; + int count; + int maxcount; +}; + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +static void SV_AreaEntities_r(worldSector_t *node, areaParms_t *ap) +{ + svEntity_t *check, *next; + sharedEntity_t *gcheck; + + for (check = node->entities; check; check = next) + { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity(check); + + if (gcheck->r.absmin[0] > ap->maxs[0] || gcheck->r.absmin[1] > ap->maxs[1] || + gcheck->r.absmin[2] > ap->maxs[2] || gcheck->r.absmax[0] < ap->mins[0] || + gcheck->r.absmax[1] < ap->mins[1] || gcheck->r.absmax[2] < ap->mins[2]) + { + continue; + } + + if (ap->count == ap->maxcount) + { + Com_Printf("SV_AreaEntities: MAXCOUNT\n"); + return; + } + + ap->list[ap->count] = check - sv.svEntities; + ap->count++; + } + + if (node->axis == -1) + { + return; // terminal node + } + + // recurse down both sides + if (ap->maxs[node->axis] > node->dist) + { + SV_AreaEntities_r(node->children[0], ap); + } + if (ap->mins[node->axis] < node->dist) + { + SV_AreaEntities_r(node->children[1], ap); + } +} + +/* +================ +SV_AreaEntities +================ +*/ +int SV_AreaEntities(const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount) +{ + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = entityList; + ap.count = 0; + ap.maxcount = maxcount; + + SV_AreaEntities_r(sv_worldSectors, &ap); + + return ap.count; +} + +//=========================================================================== + +struct moveclip_t { + vec3_t boxmins; + vec3_t boxmaxs; // enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object + const float *start; + vec3_t end; + trace_t trace; + int passEntityNum; + int contentmask; + traceType_t collisionType; +}; + +/* +==================== +SV_ClipToEntity + +==================== +*/ +void SV_ClipToEntity(trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int entityNum, int contentmask, traceType_t type) +{ + sharedEntity_t *touch; + clipHandle_t clipHandle; + float *origin, *angles; + + touch = SV_GentityNum(entityNum); + + ::memset(trace, 0, sizeof(trace_t)); + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if (!(contentmask & touch->r.contents)) + { + trace->fraction = 1.0; + return; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity(touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if (!touch->r.bmodel) + { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace(trace, (float *)start, (float *)end, (float *)mins, (float *)maxs, clipHandle, contentmask, + origin, angles, type); + + if (trace->fraction < 1) + { + trace->entityNum = touch->s.number; + } +} + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +static void SV_ClipMoveToEntities(moveclip_t *clip) +{ + int i, num; + int touchlist[MAX_GENTITIES]; + sharedEntity_t *touch; + int passOwnerNum; + trace_t trace; + clipHandle_t clipHandle; + float *origin, *angles; + + num = SV_AreaEntities(clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); + + if (clip->passEntityNum != ENTITYNUM_NONE) + { + passOwnerNum = (SV_GentityNum(clip->passEntityNum))->r.ownerNum; + if (passOwnerNum == ENTITYNUM_NONE) + { + passOwnerNum = -1; + } + } + else + { + passOwnerNum = -1; + } + + for (i = 0; i < num; i++) + { + if (clip->trace.allsolid) + { + return; + } + touch = SV_GentityNum(touchlist[i]); + + // see if we should ignore this entity + if (clip->passEntityNum != ENTITYNUM_NONE) + { + if (touchlist[i] == clip->passEntityNum) + { + continue; // don't clip against the pass entity + } + if (touch->r.ownerNum == clip->passEntityNum) + { + continue; // don't clip against own missiles + } + if (touch->r.ownerNum == passOwnerNum) + { + continue; // don't clip against other missiles from our owner + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if (!(clip->contentmask & touch->r.contents)) + { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity(touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if (!touch->r.bmodel) + { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace(&trace, (float *)clip->start, (float *)clip->end, (float *)clip->mins, + (float *)clip->maxs, clipHandle, clip->contentmask, origin, angles, clip->collisionType); + + if (trace.allsolid) + { + clip->trace.allsolid = qtrue; + trace.entityNum = touch->s.number; + } + else if (trace.startsolid) + { + clip->trace.startsolid = qtrue; + trace.entityNum = touch->s.number; + } + + if (trace.fraction < clip->trace.fraction) + { + int oldStart; + + // make sure we keep a startsolid from a previous trace + oldStart = clip->trace.startsolid; + + trace.entityNum = touch->s.number; + clip->trace = trace; + clip->trace.startsolid |= oldStart; + } + } +} + +/* +================== +SV_Trace + +Moves the given mins/maxs volume through the world from start to end. +passEntityNum and entities owned by passEntityNum are explicitly not checked. +================== +*/ +void SV_Trace(trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, + int contentmask, traceType_t type) +{ + moveclip_t clip; + int i; + + if (!mins) + { + mins = vec3_origin; + } + if (!maxs) + { + maxs = vec3_origin; + } + + ::memset(&clip, 0, sizeof(moveclip_t)); + + // clip to world + CM_BoxTrace(&clip.trace, start, end, mins, maxs, 0, contentmask, type); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if (clip.trace.fraction == 0) + { + *results = clip.trace; + return; // blocked immediately by the world + } + + clip.contentmask = contentmask; + clip.start = start; + // VectorCopy( clip.trace.endpos, clip.end ); + VectorCopy(end, clip.end); + clip.mins = mins; + clip.maxs = maxs; + clip.passEntityNum = passEntityNum; + clip.collisionType = type; + + // create the bounding box of the entire move + // we can limit it to the part of the move not + // already clipped off by the world, which can be + // a significant savings for line of sight and shot traces + for (i = 0; i < 3; i++) + { + if (end[i] > start[i]) + { + clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1; + } + else + { + clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1; + } + } + + // clip to other solid entities + SV_ClipMoveToEntities(&clip); + + *results = clip.trace; +} + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents(const vec3_t p, int passEntityNum) +{ + int touch[MAX_GENTITIES]; + sharedEntity_t *hit; + int i, num; + int contents, c2; + clipHandle_t clipHandle; + float *angles; + + // get base contents from world + contents = CM_PointContents(p, 0); + + // or in contents from all the other entities + num = SV_AreaEntities(p, p, touch, MAX_GENTITIES); + + for (i = 0; i < num; i++) + { + if (touch[i] == passEntityNum) + { + continue; + } + hit = SV_GentityNum(touch[i]); + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity(hit); + angles = hit->r.currentAngles; + if (!hit->r.bmodel) + { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents(p, clipHandle, hit->r.currentOrigin, angles); + + contents |= c2; + } + + return contents; +} diff --git a/src/sys/CMakeLists.txt b/src/sys/CMakeLists.txt new file mode 100644 index 0000000..5d7ae9b --- /dev/null +++ b/src/sys/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library ( + sys STATIC + con_log.cpp + con_passive.cpp + con_tty.cpp + con_win32.cpp + sys_loadlib.h + sys_local.h + sys_main.cpp + sys_unix.cpp + sys_win32.cpp + win_resource.h +) + +set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14" ) diff --git a/src/sys/con_log.cpp b/src/sys/con_log.cpp new file mode 100644 index 0000000..eaa81b0 --- /dev/null +++ b/src/sys/con_log.cpp @@ -0,0 +1,132 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "sys_local.h" + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +#define MAX_LOG 32768 + +static char consoleLog[ MAX_LOG ]; +static unsigned int writePos = 0; +static unsigned int readPos = 0; + +/* +================== +CON_LogSize +================== +*/ +unsigned int CON_LogSize( void ) +{ + if( readPos <= writePos ) + return writePos - readPos; + else + return writePos + MAX_LOG - readPos; +} + +/* +================== +CON_LogFree +================== +*/ +static unsigned int CON_LogFree( void ) +{ + return MAX_LOG - CON_LogSize( ) - 1; +} + +/* +================== +CON_LogWrite +================== +*/ +unsigned int CON_LogWrite( const char *in ) +{ + unsigned int length = strlen( in ); + unsigned int firstChunk; + unsigned int secondChunk; + + while( CON_LogFree( ) < length && CON_LogSize( ) > 0 ) + { + // Free enough space + while( consoleLog[ readPos ] != '\n' && CON_LogSize( ) > 1 ) + readPos = ( readPos + 1 ) % MAX_LOG; + + // Skip past the '\n' + readPos = ( readPos + 1 ) % MAX_LOG; + } + + if( CON_LogFree( ) < length ) + return 0; + + if( writePos + length > MAX_LOG ) + { + firstChunk = MAX_LOG - writePos; + secondChunk = length - firstChunk; + } + else + { + firstChunk = length; + secondChunk = 0; + } + + Com_Memcpy( consoleLog + writePos, in, firstChunk ); + Com_Memcpy( consoleLog, in + firstChunk, secondChunk ); + + writePos = ( writePos + length ) % MAX_LOG; + + return length; +} + +/* +================== +CON_LogRead +================== +*/ +unsigned int CON_LogRead( char *out, unsigned int outSize ) +{ + unsigned int firstChunk; + unsigned int secondChunk; + + if( CON_LogSize( ) < outSize ) + outSize = CON_LogSize( ); + + if( readPos + outSize > MAX_LOG ) + { + firstChunk = MAX_LOG - readPos; + secondChunk = outSize - firstChunk; + } + else + { + firstChunk = outSize; + secondChunk = 0; + } + + Com_Memcpy( out, consoleLog + readPos, firstChunk ); + Com_Memcpy( out + firstChunk, out, secondChunk ); + + readPos = ( readPos + outSize ) % MAX_LOG; + + return outSize; +} diff --git a/src/sys/con_passive.cpp b/src/sys/con_passive.cpp new file mode 100644 index 0000000..35918e5 --- /dev/null +++ b/src/sys/con_passive.cpp @@ -0,0 +1,72 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "sys_local.h" + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +#include + +/* +================== +CON_Shutdown +================== +*/ +void CON_Shutdown( void ) +{ +} + +/* +================== +CON_Init +================== +*/ +void CON_Init( void ) +{ +} + +/* +================== +CON_Input +================== +*/ +char *CON_Input( void ) +{ + return NULL; +} + +/* +================== +CON_Print +================== +*/ +void CON_Print( const char *msg ) +{ + if( com_ansiColor && com_ansiColor->integer ) + Sys_AnsiColorPrint( msg ); + else + fputs( msg, stderr ); +} diff --git a/src/sys/con_tty.cpp b/src/sys/con_tty.cpp new file mode 100644 index 0000000..660c160 --- /dev/null +++ b/src/sys/con_tty.cpp @@ -0,0 +1,552 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "sys_local.h" + +#include +#include +#include +#include + +#include + +#ifndef DEDICATED +#include "client/client.h" +#endif +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +/* +============================================================= +tty console routines + +NOTE: if the user is editing a line when something gets printed to the early +console then it won't look good so we provide CON_Hide and CON_Show to be +called before and after a stdout or stderr output +============================================================= +*/ + +extern bool stdinIsATTY; + +static bool stdin_active = false; +// general flag to tell about tty console mode +static bool ttycon_on = false; +static int ttycon_hide = 0; +static int ttycon_show_overdue = 0; + +// some key codes that the terminal may be using, initialised on start up +static int TTY_erase; +static int TTY_eof; + +static struct termios TTY_tc; + +static field_t TTY_con; + +// This is somewhat of aduplicate of the graphical console history +// but it's safer more modular to have our own here +#define CON_HISTORY 32 +static field_t ttyEditLines[ CON_HISTORY ]; +static int hist_current = -1, hist_count = 0; + +#ifndef DEDICATED +// Don't use "]" as it would be the same as in-game console, +// this makes it clear where input came from. +#define TTY_CONSOLE_PROMPT "tty]" +#else +#define TTY_CONSOLE_PROMPT "]" +#endif + +/* +================== +CON_FlushIn + +Flush stdin, I suspect some terminals are sending a LOT of shit +FIXME relevant? +================== +*/ +static void CON_FlushIn( void ) +{ + char key; + while (read(STDIN_FILENO, &key, 1)!=-1); +} + +/* +================== +CON_Back + +Output a backspace + +NOTE: it seems on some terminals just sending '\b' is not enough so instead we +send "\b \b" +(FIXME there may be a way to find out if '\b' alone would work though) +================== +*/ +static void CON_Back( void ) +{ + char key; + size_t UNUSED_VAR size; + + key = '\b'; + size = write(STDOUT_FILENO, &key, 1); + key = ' '; + size = write(STDOUT_FILENO, &key, 1); + key = '\b'; + size = write(STDOUT_FILENO, &key, 1); +} + +/* +================== +CON_Hide + +Clear the display of the line currently edited +bring cursor back to beginning of line +================== +*/ +static void CON_Hide( void ) +{ + if( ttycon_on ) + { + int i; + if (ttycon_hide) + { + ttycon_hide++; + return; + } + if (TTY_con.cursor>0) + { + for (i=0; i 0; i--) { + CON_Back(); + } + ttycon_hide++; + } +} + +/* +================== +CON_Show + +Show the current line +FIXME need to position the cursor if needed? +================== +*/ +static void CON_Show( void ) +{ + if( ttycon_on ) + { + int i; + + assert(ttycon_hide>0); + ttycon_hide--; + if (ttycon_hide == 0) + { + size_t UNUSED_VAR size; + size = write(STDOUT_FILENO, TTY_CONSOLE_PROMPT, strlen(TTY_CONSOLE_PROMPT)); + if (TTY_con.cursor) + { + for (i=0; icursor) + return; + + assert(hist_count <= CON_HISTORY); + assert(hist_count >= 0); + assert(hist_current >= -1); + assert(hist_current <= hist_count); + // make some room + for (i=CON_HISTORY-1; i>0; i--) + { + ttyEditLines[i] = ttyEditLines[i-1]; + } + ttyEditLines[0] = *field; + if (hist_count= 0); + assert(hist_current >= -1); + assert(hist_current <= hist_count); + hist_prev = hist_current + 1; + if (hist_prev >= hist_count) + { + return NULL; + } + hist_current++; + return &(ttyEditLines[hist_current]); +} + +/* +================== +Hist_Next +================== +*/ +field_t *Hist_Next( void ) +{ + assert(hist_count <= CON_HISTORY); + assert(hist_count >= 0); + assert(hist_current >= -1); + assert(hist_current <= hist_count); + if (hist_current >= 0) + { + hist_current--; + } + if (hist_current == -1) + { + return NULL; + } + return &(ttyEditLines[hist_current]); +} + +/* +================== +CON_SigCont +Reinitialize console input after receiving SIGCONT, as on Linux the terminal seems to lose all +set attributes if user did CTRL+Z and then does fg again. +================== +*/ + +void CON_SigCont(int signum) +{ + CON_Init(); +} + +/* +================== +CON_Init + +Initialize the console input (tty mode if possible) +================== +*/ +void CON_Init( void ) +{ + struct termios tc; + + // If the process is backgrounded (running non interactively) + // then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + // If SIGCONT is received, reinitialize console + signal(SIGCONT, CON_SigCont); + + // Make stdin reads non-blocking + fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK ); + + if (!stdinIsATTY) + { + Com_Printf("tty console mode disabled\n"); + ttycon_on = false; + stdin_active = true; + return; + } + + Field_Clear(&TTY_con); + tcgetattr (STDIN_FILENO, &TTY_tc); + TTY_erase = TTY_tc.c_cc[VERASE]; + TTY_eof = TTY_tc.c_cc[VEOF]; + tc = TTY_tc; + + /* + ECHO: don't echo input characters + ICANON: enable canonical mode. This enables the special + characters EOF, EOL, EOL2, ERASE, KILL, REPRINT, + STATUS, and WERASE, and buffers by lines. + ISIG: when any of the characters INTR, QUIT, SUSP, or + DSUSP are received, generate the corresponding signal + */ + tc.c_lflag &= ~(ECHO | ICANON); + + /* + ISTRIP strip off bit 8 + INPCK enable input parity checking + */ + tc.c_iflag &= ~(ISTRIP | INPCK); + tc.c_cc[VMIN] = 1; + tc.c_cc[VTIME] = 0; + tcsetattr (STDIN_FILENO, TCSADRAIN, &tc); + ttycon_on = true; + ttycon_hide = 1; // Mark as hidden, so prompt is shown in CON_Show + CON_Show(); +} + +/* +================== +CON_Input +================== +*/ +char *CON_Input( void ) +{ + // we use this when sending back commands + static char text[MAX_EDIT_LINE]; + int avail; + char key; + field_t *history; + size_t UNUSED_VAR size; + + if(ttycon_on) + { + avail = read(STDIN_FILENO, &key, 1); + if (avail != -1) + { + // we have something + // backspace? + // NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere + if ((key == TTY_erase) || (key == 127) || (key == 8)) + { + if (TTY_con.cursor > 0) + { + TTY_con.cursor--; + TTY_con.buffer[TTY_con.cursor] = '\0'; + CON_Back(); + } + return NULL; + } + // check if this is a control char + if ((key) && (key) < ' ') + { + if (key == '\n') + { +#ifndef DEDICATED + // if not in the game explicitly prepend a slash if needed + if (clc.state != CA_ACTIVE && TTY_con.cursor && + TTY_con.buffer[0] != '/' && TTY_con.buffer[0] != '\\') + { + memmove(TTY_con.buffer + 1, TTY_con.buffer, sizeof(TTY_con.buffer) - 1); + TTY_con.buffer[0] = '\\'; + TTY_con.cursor++; + } + + if (TTY_con.buffer[0] == '/' || TTY_con.buffer[0] == '\\') { + Q_strncpyz(text, TTY_con.buffer + 1, sizeof(text)); + } else if (TTY_con.cursor) { + Com_sprintf(text, sizeof(text), "cmd say %s", TTY_con.buffer); + } else { + text[0] = '\0'; + } + + // push it in history + Hist_Add(&TTY_con); + CON_Hide(); + Com_Printf("%s%s\n", TTY_CONSOLE_PROMPT, TTY_con.buffer); + Field_Clear(&TTY_con); + CON_Show(); +#else + // push it in history + Hist_Add(&TTY_con); + Q_strncpyz(text, TTY_con.buffer, sizeof(text)); + Field_Clear(&TTY_con); + key = '\n'; + size = write(STDOUT_FILENO, &key, 1); + size = write(STDOUT_FILENO, TTY_CONSOLE_PROMPT, strlen(TTY_CONSOLE_PROMPT)); +#endif + return text; + } + if (key == '\t') + { + CON_Hide(); + Field_AutoComplete( &TTY_con ); + CON_Show(); + return NULL; + } + avail = read(STDIN_FILENO, &key, 1); + if (avail != -1) + { + // VT 100 keys + if (key == '[' || key == 'O') + { + avail = read(STDIN_FILENO, &key, 1); + if (avail != -1) + { + switch (key) + { + case 'A': + history = Hist_Prev(); + if (history) + { + CON_Hide(); + TTY_con = *history; + CON_Show(); + } + CON_FlushIn(); + return NULL; + break; + case 'B': + history = Hist_Next(); + CON_Hide(); + if (history) + { + TTY_con = *history; + } else + { + Field_Clear(&TTY_con); + } + CON_Show(); + CON_FlushIn(); + return NULL; + break; + case 'C': + return NULL; + case 'D': + return NULL; + } + } + } + } + Com_DPrintf("droping ISCTL sequence: %d, TTY_erase: %d\n", key, TTY_erase); + CON_FlushIn(); + return NULL; + } + if (TTY_con.cursor >= sizeof(text) - 1) + return NULL; + // push regular character + TTY_con.buffer[TTY_con.cursor] = key; + TTY_con.cursor++; // next char will always be '\0' + // print the current line (this is differential) + size = write(STDOUT_FILENO, &key, 1); + } + + return NULL; + } + else if (stdin_active) + { + int len; + fd_set fdset; + struct timeval timeout; + + FD_ZERO(&fdset); + FD_SET(STDIN_FILENO, &fdset); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if(select (STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(STDIN_FILENO, &fdset)) + return NULL; + + len = read(STDIN_FILENO, text, sizeof(text)); + if (len == 0) + { // eof! + stdin_active = false; + return NULL; + } + + if (len < 1) + return NULL; + text[len-1] = 0; // rip off the /n and terminate + + return text; + } + return NULL; +} + +/* +================== +CON_Print +================== +*/ +void CON_Print( const char *msg ) +{ + if (!msg[0]) + return; + + CON_Hide( ); + + if( com_ansiColor && com_ansiColor->integer ) + Sys_AnsiColorPrint( msg ); + else + fputs( msg, stderr ); + + if (!ttycon_on) { + // CON_Hide didn't do anything. + return; + } + + // Only print prompt when msg ends with a newline, otherwise the console + // might get garbled when output does not fit on one line. + if (msg[strlen(msg) - 1] == '\n') { + CON_Show(); + + // Run CON_Show the number of times it was deferred. + while (ttycon_show_overdue > 0) { + CON_Show(); + ttycon_show_overdue--; + } + } + else + { + // Defer calling CON_Show + ttycon_show_overdue++; + } +} diff --git a/src/sys/con_win32.cpp b/src/sys/con_win32.cpp new file mode 100644 index 0000000..8d8783a --- /dev/null +++ b/src/sys/con_win32.cpp @@ -0,0 +1,558 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "sys_local.h" + +#include + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +#define QCONSOLE_HISTORY 32 + +static WORD qconsole_attrib; +static WORD qconsole_backgroundAttrib; + +// saved console status +static DWORD qconsole_orig_mode; +static CONSOLE_CURSOR_INFO qconsole_orig_cursorinfo; + +// cmd history +static char qconsole_history[ QCONSOLE_HISTORY ][ MAX_EDIT_LINE ]; +static int qconsole_history_pos = -1; +static int qconsole_history_lines = 0; +static int qconsole_history_oldest = 0; + +// current edit buffer +static char qconsole_line[ MAX_EDIT_LINE ]; +static int qconsole_linelen = 0; +static bool qconsole_drawinput = true; +static int qconsole_cursor; + +static HANDLE qconsole_hout; +static HANDLE qconsole_hin; + +/* +================== +CON_ColorCharToAttrib + +Convert Quake color character to Windows text attrib +================== +*/ +static WORD CON_ColorCharToAttrib( char color ) { + WORD attrib; + + if ( color == COLOR_WHITE ) + { + // use console's foreground and background colors + attrib = qconsole_attrib; + } + else + { + float *rgba = g_color_table[ ColorIndex( color ) ]; + + // set foreground color + attrib = ( rgba[0] >= 0.5 ? FOREGROUND_RED : 0 ) | + ( rgba[1] >= 0.5 ? FOREGROUND_GREEN : 0 ) | + ( rgba[2] >= 0.5 ? FOREGROUND_BLUE : 0 ) | + ( rgba[3] >= 0.5 ? FOREGROUND_INTENSITY : 0 ); + + // use console's background color + attrib |= qconsole_backgroundAttrib; + } + + return attrib; +} + +/* +================== +CON_CtrlHandler + +The Windows Console doesn't use signals for terminating the application +with Ctrl-C, logging off, window closing, etc. Instead it uses a special +handler routine. Fortunately, the values for Ctrl signals don't seem to +overlap with true signal codes that Windows provides, so calling +Sys_SigHandler() with those numbers should be safe for generating unique +shutdown messages. +================== +*/ +static BOOL WINAPI CON_CtrlHandler( DWORD sig ) +{ + Sys_SigHandler( sig ); + return TRUE; +} + +/* +================== +CON_HistAdd +================== +*/ +static void CON_HistAdd( void ) +{ + Q_strncpyz( qconsole_history[ qconsole_history_oldest ], qconsole_line, + sizeof( qconsole_history[ qconsole_history_oldest ] ) ); + + if( qconsole_history_lines < QCONSOLE_HISTORY ) + qconsole_history_lines++; + + if( qconsole_history_oldest >= QCONSOLE_HISTORY - 1 ) + qconsole_history_oldest = 0; + else + qconsole_history_oldest++; + + qconsole_history_pos = qconsole_history_oldest; +} + +/* +================== +CON_HistPrev +================== +*/ +static void CON_HistPrev( void ) +{ + int pos; + + pos = ( qconsole_history_pos < 1 ) ? + ( QCONSOLE_HISTORY - 1 ) : ( qconsole_history_pos - 1 ); + + // don' t allow looping through history + if( pos == qconsole_history_oldest || pos >= qconsole_history_lines ) + return; + + qconsole_history_pos = pos; + Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], + sizeof( qconsole_line ) ); + qconsole_linelen = strlen( qconsole_line ); + qconsole_cursor = qconsole_linelen; +} + +/* +================== +CON_HistNext +================== +*/ +static void CON_HistNext( void ) +{ + int pos; + + // don' t allow looping through history + if( qconsole_history_pos == qconsole_history_oldest ) + return; + + pos = ( qconsole_history_pos >= QCONSOLE_HISTORY - 1 ) ? + 0 : ( qconsole_history_pos + 1 ); + + // clear the edit buffer if they try to advance to a future command + if( pos == qconsole_history_oldest ) + { + qconsole_history_pos = pos; + qconsole_line[ 0 ] = '\0'; + qconsole_linelen = 0; + qconsole_cursor = qconsole_linelen; + return; + } + + qconsole_history_pos = pos; + Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], + sizeof( qconsole_line ) ); + qconsole_linelen = strlen( qconsole_line ); + qconsole_cursor = qconsole_linelen; +} + + +/* +================== +CON_Show +================== +*/ +static void CON_Show( void ) +{ + CONSOLE_SCREEN_BUFFER_INFO binfo; + COORD writeSize = { MAX_EDIT_LINE, 1 }; + COORD writePos = { 0, 0 }; + SMALL_RECT writeArea = { 0, 0, 0, 0 }; + COORD cursorPos; + int i; + CHAR_INFO line[ MAX_EDIT_LINE ]; + WORD attrib; + + GetConsoleScreenBufferInfo( qconsole_hout, &binfo ); + + // if we're in the middle of printf, don't bother writing the buffer + if( !qconsole_drawinput ) + return; + + writeArea.Left = 0; + writeArea.Top = binfo.dwCursorPosition.Y; + writeArea.Bottom = binfo.dwCursorPosition.Y; + writeArea.Right = MAX_EDIT_LINE; + + // set color to white + attrib = CON_ColorCharToAttrib( COLOR_WHITE ); + + // build a space-padded CHAR_INFO array + for( i = 0; i < MAX_EDIT_LINE; i++ ) + { + if( i < qconsole_linelen ) + { + if( i + 1 < qconsole_linelen && Q_IsColorString( qconsole_line + i ) ) + attrib = CON_ColorCharToAttrib( *( qconsole_line + i + 1 ) ); + + line[ i ].Char.AsciiChar = qconsole_line[ i ]; + } + else + line[ i ].Char.AsciiChar = ' '; + + line[ i ].Attributes = attrib; + } + + if( qconsole_linelen > binfo.srWindow.Right ) + { + WriteConsoleOutput( qconsole_hout, + line + (qconsole_linelen - binfo.srWindow.Right ), + writeSize, writePos, &writeArea ); + } + else + { + WriteConsoleOutput( qconsole_hout, line, writeSize, + writePos, &writeArea ); + } + + // set curor position + cursorPos.Y = binfo.dwCursorPosition.Y; + cursorPos.X = qconsole_cursor < qconsole_linelen + ? qconsole_cursor + : qconsole_linelen > binfo.srWindow.Right + ? binfo.srWindow.Right + : qconsole_linelen; + + SetConsoleCursorPosition( qconsole_hout, cursorPos ); +} + +/* +================== +CON_Hide +================== +*/ +static void CON_Hide( void ) +{ + int realLen; + + realLen = qconsole_linelen; + + // remove input line from console output buffer + qconsole_linelen = 0; + CON_Show( ); + + qconsole_linelen = realLen; +} + + +/* +================== +CON_Shutdown +================== +*/ +void CON_Shutdown( void ) +{ + CON_Hide( ); + SetConsoleMode( qconsole_hin, qconsole_orig_mode ); + SetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo ); + SetConsoleTextAttribute( qconsole_hout, qconsole_attrib ); + CloseHandle( qconsole_hout ); + CloseHandle( qconsole_hin ); +} + +/* +================== +CON_Init +================== +*/ +void CON_Init( void ) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + int i; + + // handle Ctrl-C or other console termination + SetConsoleCtrlHandler( CON_CtrlHandler, TRUE ); + + qconsole_hin = GetStdHandle( STD_INPUT_HANDLE ); + if( qconsole_hin == INVALID_HANDLE_VALUE ) + return; + + qconsole_hout = GetStdHandle( STD_OUTPUT_HANDLE ); + if( qconsole_hout == INVALID_HANDLE_VALUE ) + return; + + GetConsoleMode( qconsole_hin, &qconsole_orig_mode ); + + // allow mouse wheel scrolling + SetConsoleMode( qconsole_hin, + qconsole_orig_mode & ~ENABLE_MOUSE_INPUT ); + + FlushConsoleInputBuffer( qconsole_hin ); + + GetConsoleScreenBufferInfo( qconsole_hout, &info ); + qconsole_attrib = info.wAttributes; + qconsole_backgroundAttrib = qconsole_attrib & (BACKGROUND_BLUE|BACKGROUND_GREEN|BACKGROUND_RED|BACKGROUND_INTENSITY); + + SetConsoleTitle("Tremulous Dedicated Server Console"); + + // initialize history + for( i = 0; i < QCONSOLE_HISTORY; i++ ) + qconsole_history[ i ][ 0 ] = '\0'; + + // set text color to white + SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) ); +} + +/* +================== +CON_Input +================== +*/ +char *CON_Input( void ) +{ + INPUT_RECORD buff[ MAX_EDIT_LINE ]; + DWORD count = 0, events = 0; + WORD key = 0; + int i; + int newlinepos = -1; + + if( !GetNumberOfConsoleInputEvents( qconsole_hin, &events ) ) + return NULL; + + if( events < 1 ) + return NULL; + + // if we have overflowed, start dropping oldest input events + if( events >= MAX_EDIT_LINE ) + { + ReadConsoleInput( qconsole_hin, buff, 1, &events ); + return NULL; + } + + if( !ReadConsoleInput( qconsole_hin, buff, events, &count ) ) + return NULL; + + FlushConsoleInputBuffer( qconsole_hin ); + + for( i = 0; i < count; i++ ) + { + if( buff[ i ].EventType != KEY_EVENT ) + continue; + if( !buff[ i ].Event.KeyEvent.bKeyDown ) + continue; + + key = buff[ i ].Event.KeyEvent.wVirtualKeyCode; + + if( key == VK_RETURN ) + { + newlinepos = i; + qconsole_cursor = 0; + break; + } + else if( key == VK_UP ) + { + CON_HistPrev(); + break; + } + else if( key == VK_DOWN ) + { + CON_HistNext(); + break; + } + else if( key == VK_LEFT ) + { + qconsole_cursor--; + if ( qconsole_cursor < 0 ) + { + qconsole_cursor = 0; + } + break; + } + else if( key == VK_RIGHT ) + { + qconsole_cursor++; + if ( qconsole_cursor > qconsole_linelen ) + { + qconsole_cursor = qconsole_linelen; + } + break; + } + else if( key == VK_HOME ) + { + qconsole_cursor = 0; + break; + } + else if( key == VK_END ) + { + qconsole_cursor = qconsole_linelen; + break; + } + else if( key == VK_TAB ) + { + field_t f; + + Q_strncpyz( f.buffer, qconsole_line, + sizeof( f.buffer ) ); + Field_AutoComplete( &f ); + Q_strncpyz( qconsole_line, f.buffer, + sizeof( qconsole_line ) ); + qconsole_linelen = strlen( qconsole_line ); + qconsole_cursor = qconsole_linelen; + break; + } + + if( qconsole_linelen < sizeof( qconsole_line ) - 1 ) + { + char c = buff[ i ].Event.KeyEvent.uChar.AsciiChar; + + if( key == VK_BACK ) + { + if ( qconsole_cursor > 0 ) + { + int newlen = ( qconsole_linelen > 0 ) ? qconsole_linelen - 1 : 0; + if ( qconsole_cursor < qconsole_linelen ) + { + memmove( qconsole_line + qconsole_cursor - 1, + qconsole_line + qconsole_cursor, + qconsole_linelen - qconsole_cursor ); + } + + qconsole_line[ newlen ] = '\0'; + qconsole_linelen = newlen; + qconsole_cursor--; + } + } + else if( c ) + { + if ( qconsole_linelen > qconsole_cursor ) + { + memmove( qconsole_line + qconsole_cursor + 1, + qconsole_line + qconsole_cursor, + qconsole_linelen - qconsole_cursor ); + } + + qconsole_line[ qconsole_cursor++ ] = c; + + qconsole_linelen++; + qconsole_line[ qconsole_linelen ] = '\0'; + } + } + } + + if( newlinepos < 0) { + CON_Show(); + return NULL; + } + + if( !qconsole_linelen ) + { + CON_Show(); + Com_Printf( "\n" ); + return NULL; + } + + qconsole_linelen = 0; + CON_Show(); + + CON_HistAdd(); + Com_Printf( "%s\n", qconsole_line ); + + return qconsole_line; +} + +/* +================= +CON_WindowsColorPrint + +Set text colors based on Q3 color codes +================= +*/ +void CON_WindowsColorPrint( const char *msg ) +{ + static char buffer[ MAXPRINTMSG ]; + int length = 0; + + while( *msg ) + { + qconsole_drawinput = ( *msg == '\n' ); + + if( Q_IsColorString( msg ) || *msg == '\n' ) + { + // First empty the buffer + if( length > 0 ) + { + buffer[ length ] = '\0'; + fputs( buffer, stderr ); + length = 0; + } + + if( *msg == '\n' ) + { + // Reset color and then add the newline + SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) ); + fputs( "\n", stderr ); + msg++; + } + else + { + // Set the color + SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( *( msg + 1 ) ) ); + msg += 2; + } + } + else + { + if( length >= MAXPRINTMSG - 1 ) + break; + + buffer[ length ] = *msg; + length++; + msg++; + } + } + + // Empty anything still left in the buffer + if( length > 0 ) + { + buffer[ length ] = '\0'; + fputs( buffer, stderr ); + } +} + +/* +================== +CON_Print +================== +*/ +void CON_Print( const char *msg ) +{ + CON_Hide( ); + + CON_WindowsColorPrint( msg ); + + CON_Show( ); +} diff --git a/src/sys/dialog.h b/src/sys/dialog.h new file mode 100644 index 0000000..b96368a --- /dev/null +++ b/src/sys/dialog.h @@ -0,0 +1,40 @@ +// This file is part of Tremulous. +// Copyright © 2016 Victor Roemer (blowfish) +// Copyright (C) 2015-2019 GrangerHub +// +// 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 3 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, see . + +#ifndef SYS_DIALOG_H +#define SYS_DIALOG_H + +enum dialogResult_t +{ + DR_YES = 0, + DR_NO = 1, + DR_OK = 0, + DR_CANCEL = 1 +}; + +enum dialogType_t +{ + DT_INFO, + DT_WARNING, + DT_ERROR, + DT_YES_NO, + DT_OK_CANCEL +}; + +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ); + +#endif diff --git a/src/sys/sys_loadlib.h b/src/sys/sys_loadlib.h new file mode 100644 index 0000000..10baebc --- /dev/null +++ b/src/sys/sys_loadlib.h @@ -0,0 +1,57 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef _SYS_LOADLIB_H_ +#define _SYS_LOADLIB_H_ + +#ifdef DEDICATED +# ifdef _WIN32 +# include +# define Sys_LoadLibrary(f) (void*)LoadLibrary(f) +# define Sys_UnloadLibrary(h) FreeLibrary((HMODULE)h) +# define Sys_LoadFunction(h,fn) (void*)GetProcAddress((HMODULE)h,fn) +# define Sys_LibraryError() "unknown" +# else +# include +# define Sys_LoadLibrary(f) dlopen(f,RTLD_NOW) +# define Sys_UnloadLibrary(h) dlclose(h) +# define Sys_LoadFunction(h,fn) dlsym(h,fn) +# define Sys_LibraryError() dlerror() +# endif +#else +# ifdef USE_LOCAL_HEADERS +# include "SDL.h" +# include "SDL_loadso.h" +# else +# include +# include +# endif +# define Sys_LoadLibrary(f) SDL_LoadObject(f) +# define Sys_UnloadLibrary(h) SDL_UnloadObject(h) +# define Sys_LoadFunction(h,fn) SDL_LoadFunction(h,fn) +# define Sys_LibraryError() SDL_GetError() +#endif + +void * QDECL Sys_LoadDll(const char *name, bool useSystemLib); + +#endif diff --git a/src/sys/sys_local.h b/src/sys/sys_local.h new file mode 100644 index 0000000..f309bfe --- /dev/null +++ b/src/sys/sys_local.h @@ -0,0 +1,58 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +#ifndef _SYS_LOCAL_H_ +#define _SYS_LOCAL_H_ + +#include "sys_shared.h" + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" + +// Require a minimum version of SDL +#define MINSDL_MAJOR 2 +#define MINSDL_MINOR 0 +#define MINSDL_PATCH 0 + +// Console +void CON_Shutdown( void ); +void CON_Init( void ); +char *CON_Input( void ); +void CON_Print( const char *message ); + +unsigned int CON_LogSize( void ); +unsigned int CON_LogWrite( const char *in ); +unsigned int CON_LogRead( char *out, unsigned int outSize ); + +void Sys_GLimpSafeInit( void ); +void Sys_GLimpInit( void ); +void Sys_PlatformInit( void ); +void Sys_PlatformExit( void ); +void Sys_SigHandler( int signal ) __attribute__ ((noreturn)); +void Sys_ErrorDialog( const char *error ); +void Sys_AnsiColorPrint( const char *msg ); + +int Sys_PID( void ); +bool Sys_PIDIsRunning( int pid ); + +#endif diff --git a/src/sys/sys_main.cpp b/src/sys/sys_main.cpp new file mode 100644 index 0000000..efc1ecb --- /dev/null +++ b/src/sys/sys_main.cpp @@ -0,0 +1,798 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "sys_local.h" + +#include +#include +#include +#include +#ifdef WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lua.hpp" +#include "sol.hpp" +#ifndef DEDICATED +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +# include "SDL_cpuinfo.h" +#else +# include +# include +#endif +#endif + +#include "qcommon/files.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "qcommon/vm.h" +#ifndef DEDICATED +#include "script/bind.h" +#include "script/client.h" +#include "script/http_client.h" +#endif +#include "script/cmd.h" +#include "script/cvar.h" +#include "script/rapidjson.h" +#include "script/nettle.h" + +#include "dialog.h" +#include "sys_loadlib.h" + +sol::state lua; + +static char binaryPath[ MAX_OSPATH ] = { 0 }; +static char installPath[ MAX_OSPATH ] = { 0 }; + +/* +================= +Sys_SetBinaryPath +================= +*/ +void Sys_SetBinaryPath(const char *path) +{ + Q_strncpyz(binaryPath, path, sizeof(binaryPath)); +} + +/* +================= +Sys_BinaryPath +================= +*/ +char *Sys_BinaryPath(void) +{ + return binaryPath; +} + +/* +================= +Sys_SetDefaultInstallPath +================= +*/ +void Sys_SetDefaultInstallPath(const char *path) +{ + Q_strncpyz(installPath, path, sizeof(installPath)); +} + +/* +================= +Sys_DefaultInstallPath +================= +*/ +char *Sys_DefaultInstallPath(void) +{ + return installPath; +} + +/* +================= +Sys_DefaultAppPath +================= +*/ +char *Sys_DefaultAppPath(void) +{ + return Sys_BinaryPath(); +} + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) +{ + IN_Restart( ); +} + +/* +================= +Sys_ConsoleInput + +Handle new console input +================= +*/ +char *Sys_ConsoleInput(void) +{ + return CON_Input( ); +} + +/* +================== +Sys_GetClipboardData +================== +*/ +char *Sys_GetClipboardData(void) +{ + char *data = NULL; +#ifndef DEDICATED + char *cliptext; + + if ( ( cliptext = SDL_GetClipboardText() ) != NULL ) { + if ( cliptext[0] != '\0' ) { + size_t bufsize = strlen( cliptext ) + 1; + + data = (char*)Z_Malloc( bufsize ); + Q_strncpyz( data, cliptext, bufsize ); + + // find first listed char and set to '\0' + strtok( data, "\n\r\b" ); + } + SDL_free( cliptext ); + } +#endif + return data; +} + +#ifdef DEDICATED +# define PID_FILENAME PRODUCT_NAME "_server.pid" +#else +# define PID_FILENAME PRODUCT_NAME ".pid" +#endif + +/* +================= +Sys_PIDFileName +================= +*/ +static std::string Sys_PIDFileName( void ) +{ + const char *homePath = Cvar_VariableString( "fs_homepath" ); + std::string pidfile; + + if( *homePath != '\0' ) + { + pidfile += homePath; + pidfile += "/"; + pidfile += PID_FILENAME; + } + + return pidfile; +} + +/* +================= +Sys_WritePIDFile + +Return true if there is an existing stale PID file +================= +*/ +bool Sys_WritePIDFile( void ) +{ + const char *pidFile = Sys_PIDFileName( ).c_str(); + FILE *f; + bool stale = false; + + if( pidFile == NULL ) + return false; + + // First, check if the pid file is already there + if( ( f = fopen( pidFile, "r" ) ) != NULL ) + { + char pidBuffer[ 64 ] = { 0 }; + int pid; + + pid = fread( pidBuffer, sizeof( char ), sizeof( pidBuffer ) - 1, f ); + fclose( f ); + + if(pid > 0) + { + pid = atoi( pidBuffer ); + if( !Sys_PIDIsRunning( pid ) ) + stale = true; + } + else + stale = true; + } + + if( ( f = fopen( pidFile, "w" ) ) != NULL ) + { + fprintf( f, "%d", Sys_PID( ) ); + fclose( f ); + } + else + Com_Printf( S_COLOR_YELLOW "Couldn't write %s.\n", pidFile ); + + return stale; +} + +/* +================= +Sys_Exit + +Single exit point (regular exit or in case of error) +================= +*/ +static __attribute__ ((noreturn)) void Sys_Exit( int exitCode ) +{ + CON_Shutdown( ); + +#ifndef DEDICATED + SDL_Quit( ); +#endif + + if( exitCode < 2 ) + { + // Normal exit + const char *pidFile = Sys_PIDFileName( ).c_str(); + if( pidFile != NULL ) + remove( pidFile ); + } + + NET_Shutdown( ); + + Sys_PlatformExit( ); + + exit( exitCode ); +} + +/* +================= +Sys_Quit +================= +*/ +void Sys_Quit( void ) +{ + Sys_Exit( 0 ); +} + +/* +================= +Sys_GetProcessorFeatures +================= +*/ +cpuFeatures_t Sys_GetProcessorFeatures( void ) +{ + cpuFeatures_t features = CF_NONE; + +#ifndef DEDICATED + if( SDL_HasRDTSC( ) ) features |= CF_RDTSC; + if( SDL_Has3DNow( ) ) features |= CF_3DNOW; + if( SDL_HasMMX( ) ) features |= CF_MMX; + if( SDL_HasSSE( ) ) features |= CF_SSE; + if( SDL_HasSSE2( ) ) features |= CF_SSE2; + if( SDL_HasAltiVec( ) ) features |= CF_ALTIVEC; +#endif + + return features; +} + +void Sys_Script_f( void ) +{ + std::string args = Cmd_Args(); + lua.script(args); +} + +void Sys_ScriptFile_f( void ) +{ + std::string args = Cmd_Args(); + lua.script_file(args); +} +/* +================= +Sys_Init +================= +*/ +void Sys_Init(void) +{ + Cmd_AddCommand( "in_restart", Sys_In_Restart_f ); + Cmd_AddCommand( "script", Sys_Script_f ); + Cmd_AddCommand( "script_file", Sys_ScriptFile_f ); + Cvar_Set( "arch", OS_STRING " " ARCH_STRING ); + Cvar_Set( "username", "UnnamedPlayer" ); +} + +/* +================= +Sys_AnsiColorPrint +Transform Q3 colour codes to ANSI escape sequences +================= +*/ +// FIXME -bbq This could be more extensible +void Sys_AnsiColorPrint( const char *msg ) +{ + static char buffer[ MAXPRINTMSG ]; + int length = 0; + static int q3ToAnsi[ 8 ] = + { + 7, // COLOR_BLACK + 31, // COLOR_RED + 32, // COLOR_GREEN + 33, // COLOR_YELLOW + 34, // COLOR_BLUE + 36, // COLOR_CYAN + 35, // COLOR_MAGENTA + 0 // COLOR_WHITE + }; + + while( *msg ) + { + if( Q_IsColorString( msg ) || *msg == '\n' ) + { + // First empty the buffer + if( length > 0 ) + { + buffer[ length ] = '\0'; + fputs( buffer, stderr ); + length = 0; + } + + if( *msg == '\n' ) + { + // Issue a reset and then the newline + fputs( "\033[0m\n", stderr ); + msg++; + } + else + { + // Print the color code (reset first to clear potential inverse (black)) + Com_sprintf( buffer, sizeof( buffer ), "\033[0m\033[%dm", + q3ToAnsi[ ColorIndex( *( msg + 1 ) ) ] ); + fputs( buffer, stderr ); + msg += 2; + } + } + else + { + if( length >= MAXPRINTMSG - 1 ) + break; + + buffer[ length ] = *msg; + length++; + msg++; + } + } + + // Empty anything still left in the buffer + if( length > 0 ) + { + buffer[ length ] = '\0'; + fputs( buffer, stderr ); + } +} + +/* +================= +Sys_Print +================= +*/ +void Sys_Print( const char *msg ) +{ + CON_LogWrite( msg ); + CON_Print( msg ); +} + +/* +================= +Sys_Error +================= +*/ +void Sys_Error( const char *error, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,error); + Q_vsnprintf (string, sizeof(string), error, argptr); + va_end (argptr); + + Sys_ErrorDialog( string ); + + Sys_Exit( 3 ); +} + +/* +============ +Sys_FileTime + +returns -1 if not present +============ +*/ +int Sys_FileTime( char *path ) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + +/* +================= +Sys_UnloadDll +================= +*/ +void Sys_UnloadDll( void *dllHandle ) +{ + if( !dllHandle ) + { + Com_Printf("Sys_UnloadDll(NULL)\n"); + return; + } + + Sys_UnloadLibrary(dllHandle); +} + +/* +================= +Sys_LoadDll + +First try to load library name from system library path, +from executable path, then fs_basepath. +================= +*/ +void *Sys_LoadDll(const char *name, bool useSystemLib) +{ + void *dllhandle; + + if (!Sys_DllExtension(name)) + { + Com_Printf("Refusing to load library \"%s\": Extension not allowed.\n", name); + return nullptr; + } + + if(useSystemLib) + Com_Printf("Trying to load \"%s\"...\n", name); + + if(!useSystemLib || !(dllhandle = Sys_LoadLibrary(name))) + { + const char *topDir; + char libPath[MAX_OSPATH]; + + topDir = Sys_BinaryPath(); + + if(!*topDir) + topDir = "."; + + Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, topDir); + + int len = Com_sprintf(libPath, sizeof(libPath), "%s%c%s", topDir, PATH_SEP, name); + if(len < sizeof(libPath)) + { + Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, topDir); + dllhandle = Sys_LoadLibrary(libPath); + } + else + { + Com_Printf("Skipping trying to load \"%s\" from \"%s\", file name is too long.\n", name, topDir); + } + + if (!dllhandle) + { + const char *basePath = Cvar_VariableString("fs_basepath"); + + if(!basePath || !*basePath) + basePath = "."; + + if(FS_FilenameCompare(topDir, basePath)) + { + Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, basePath); + len = Com_sprintf(libPath, sizeof(libPath), "%s%c%s", basePath, PATH_SEP, name); + if(len < sizeof(libPath)) + { + Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, basePath); + dllhandle = Sys_LoadLibrary(libPath); + } + else + { + Com_Printf("Skipping trying to load \"%s\" from \"%s\", file name is too long.\n", name, basePath); + } + } + + if(!dllhandle) + Com_Printf("Loading \"%s\" failed\n", name); + } + } + + return dllhandle; +} + +/* +================= +Sys_LoadGameDll + +Used to load a development dll instead of a virtual machine +================= +*/ +using Entry = void (*)(intptr_t (*syscallptr)(intptr_t, ...)); +using EntryPoint = intptr_t (QDECL *)(int, ...); +using SysCalls = intptr_t (*)(intptr_t, ...); + +void *Sys_LoadGameDll(const char *name, EntryPoint* entryPoint, SysCalls systemcalls) +{ + void *libHandle; + + assert(name); + + if (!Sys_DllExtension(name)) + { + Com_Printf("Refusing to load library \"%s\": Extension not allowed.\n", name); + return nullptr; + } + + Com_Printf( "Loading DLL file: %s\n", name); + libHandle = Sys_LoadLibrary(name); + + if(!libHandle) + { + Com_Printf("Sys_LoadGameDll(%s) failed:\n\"%s\"\n", name, Sys_LibraryError()); + return NULL; + } + + Entry entry = (Entry)Sys_LoadFunction( libHandle, "dllEntry" ); + *entryPoint = (EntryPoint)Sys_LoadFunction( libHandle, "vmMain" ); + + if ( !*entryPoint || !entry ) + { + Com_Printf ( "Sys_LoadGameDll(%s) failed to find vmMain function:\n\"%s\" !\n", name, Sys_LibraryError( ) ); + Sys_UnloadLibrary(libHandle); + return NULL; + } + + Com_Printf ( "Sys_LoadGameDll(%s) found vmMain function at %p\n", name, *entryPoint ); + entry( systemcalls ); + + return libHandle; +} + +/* +================= +Sys_ParseArgs +================= +*/ +void Sys_ParseArgs( int argc, char **argv ) +{ + if( argc == 2 ) + { + if( !strcmp( argv[1], "--version" ) || + !strcmp( argv[1], "-v" ) ) + { + const char* date = __DATE__; +#ifdef DEDICATED + fprintf( stdout, Q3_VERSION " dedicated server (%s)\n", date ); +#else + fprintf( stdout, Q3_VERSION " client (%s)\n", date ); +#endif + Sys_Exit( 0 ); + } + } +} + +/* +================= +Sys_SigHandler +================= +*/ +void Sys_SigHandler( int signal ) +{ + static bool signalcaught = false; + + if( signalcaught ) + { + std::cerr << "DOUBLE SIGNAL FAULT: Received signal " + << signal << std::endl; + } + else + { + char const* msg = va("Received signal %d", signal); + + signalcaught = true; + VM_Forced_Unload_Start(); +#ifndef DEDICATED + CL_Shutdown(va("Received signal %d", signal), true, true); +#endif + SV_Shutdown(msg); + VM_Forced_Unload_Done(); + } + + if( signal == SIGTERM || signal == SIGINT ) + Sys_Exit( 1 ); + + Sys_Exit( 2 ); +} + +#ifndef DEFAULT_BASEDIR +# ifdef __APPLE__ +# define DEFAULT_BASEDIR Sys_StripAppBundle(Sys_BinaryPath()) +# else +# define DEFAULT_BASEDIR Sys_BinaryPath() +# endif +#endif + +#ifdef __APPLE__ +/* +================= +Sys_StripAppBundle + +Discovers if passed dir is suffixed with the directory structure of a Mac OS X +.app bundle. If it is, the .app directory structure is stripped off the end and +the result is returned. If not, dir is returned untouched. +================= +*/ +const char *Sys_StripAppBundle( const char *dir ) +{ + static char cwd[MAX_OSPATH]; + + Q_strncpyz(cwd, dir, sizeof(cwd)); + if(strcmp(Sys_Basename(cwd), "MacOS")) + return dir; + Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd)); + if(strcmp(Sys_Basename(cwd), "Contents")) + return dir; + Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd)); + if(!strstr(Sys_Basename(cwd), ".app")) + return dir; + Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd)); + return cwd; +} +#endif + +#ifndef DEDICATED + +void SDLVersionCheck() +{ +#if !SDL_VERSION_ATLEAST(MINSDL_MAJOR,MINSDL_MINOR,MINSDL_PATCH) +#error A more recent version of SDL is required +#endif + SDL_version ver; + SDL_GetVersion( &ver ); +#define MINSDL_VERSION XSTRING(MINSDL_MAJOR) "." \ + XSTRING(MINSDL_MINOR) "." \ + XSTRING(MINSDL_PATCH) + if( SDL_VERSIONNUM(ver.major, ver.minor, ver.patch) + < SDL_VERSIONNUM(MINSDL_MAJOR, MINSDL_MINOR, MINSDL_PATCH) ) + { + Sys_Dialog( DT_ERROR, va( "SDL version " MINSDL_VERSION " or greater is required, " + "but only version %d.%d.%d was found. You may be able to obtain a more recent copy " + "from http://www.libsdl.org/.", ver.major, ver.minor, ver.patch ), "SDL Library Too Old" ); + Sys_Exit( 1 ); + } +} +#endif + + +/* +================= +main +================= +*/ +int main( int argc, char **argv ) +{ +#ifndef DEDICATED + SDLVersionCheck(); +#endif + Sys_PlatformInit( ); + + // Set the initial time base + Sys_Milliseconds( ); + +#ifdef __APPLE__ + // This is passed if we are launched by double-clicking + if ( argc >= 2 ) + if ( Q_strncmp( argv[1], "-psn", 4 ) == 0 ) + argc = 1; +#endif + + Sys_ParseArgs( argc, argv ); + Sys_SetBinaryPath( Sys_Dirname( argv[ 0 ] ) ); + Sys_SetDefaultInstallPath( DEFAULT_BASEDIR ); + + // Concatenate the command line for passing to Com_Init + char args[MAX_STRING_CHARS]; + args[0] = '\0'; + + for( int i = 1; i < argc; i++ ) + { + const bool ws = strchr(argv[i], ' ') ? true : false; + + if (ws) Q_strcat(args, sizeof(args), "\""); + Q_strcat(args, sizeof(args), argv[i]); + if (ws) Q_strcat(args, sizeof(args), "\""); + Q_strcat(args, sizeof(args), " " ); + } + + CON_Init( ); + Com_Init( args ); + NET_Init( ); + + lua.open_libraries + ( + sol::lib::base, + sol::lib::package, +#if !defined(SOL_LUAJIT) // Not with LuaJIT. + sol::lib::coroutine, +#endif + sol::lib::string, + sol::lib::table, + sol::lib::math, + sol::lib::bit32, + sol::lib::io, + sol::lib::os, + sol::lib::debug, + sol::lib::utf8 // Only with Lua 5.3; ommiting ifdef on purpose. -bbq +#if defined(SOL_LUAJIT) // Only with LuaJIT. + ,sol::lib::ffi, + sol::lib::jit +#endif + ); + + script::cvar::init(std::move(lua)); + script::cmd::init(std::move(lua)); + script::rapidjson::init(std::move(lua)); + script::nettle::init(std::move(lua)); + +#ifndef DEDICATED + script::client::init(std::move(lua)); + script::keybind::init(std::move(lua)); + script::http_client::init(std::move(lua)); +#endif + + for ( ;; ) + { + try + { + Com_Frame( ); + } + catch (sol::error& e) + { + Com_Printf(S_COLOR_YELLOW "%s\n", e.what()); + } + } + + return 0; +} diff --git a/src/sys/sys_osx.mm b/src/sys/sys_osx.mm new file mode 100644 index 0000000..36caa40 --- /dev/null +++ b/src/sys/sys_osx.mm @@ -0,0 +1,103 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#ifndef __APPLE__ +#error This file is for Mac OS X only. You probably should not compile it. +#endif + +// Please note that this file is just some Mac-specific bits. Most of the +// Mac OS X code is shared with other Unix platforms in sys_unix.c ... + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "dialog.h" +#include "sys_local.h" + +//#import +#import +#import +#import +// +//#import +//#import +//#import +//#import +//#import + +/* +============== +Sys_Dialog + +Display an OS X dialog box +============== +*/ +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) +{ + dialogResult_t result = DR_OK; + NSAlert *alert = [NSAlert new]; + + [alert setMessageText: [NSString stringWithUTF8String: title]]; + [alert setInformativeText: [NSString stringWithUTF8String: message]]; + + if( type == DT_ERROR ) + [alert setAlertStyle: NSCriticalAlertStyle]; + else + [alert setAlertStyle: NSWarningAlertStyle]; + + switch( type ) + { + default: + [alert runModal]; + result = DR_OK; + break; + + case DT_YES_NO: + [alert addButtonWithTitle: @"Yes"]; + [alert addButtonWithTitle: @"No"]; + switch( [alert runModal] ) + { + default: + case NSAlertFirstButtonReturn: result = DR_YES; break; + case NSAlertSecondButtonReturn: result = DR_NO; break; + } + break; + + case DT_OK_CANCEL: + [alert addButtonWithTitle: @"OK"]; + [alert addButtonWithTitle: @"Cancel"]; + + switch( [alert runModal] ) + { + default: + case NSAlertFirstButtonReturn: result = DR_OK; break; + case NSAlertSecondButtonReturn: result = DR_CANCEL; break; + } + break; + } + + [alert release]; + + return result; +} diff --git a/src/sys/sys_shared.h b/src/sys/sys_shared.h new file mode 100644 index 0000000..283ae3a --- /dev/null +++ b/src/sys/sys_shared.h @@ -0,0 +1,111 @@ +/* + + This File is part of Tremulous. + Copyright (C) 2016, wtfbbqhax . + Copyright (C) 2015-2019, GrangerHub . + +*/ + +#ifndef SYS_SHARED_H +#define SYS_SHARED_H 1 + +#include + +#include "qcommon/qcommon.h" +#include "qcommon/net.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_JOYSTICK_AXIS 16 + +typedef int cpuFeatures_t; +enum CPU_FEATURES { + CF_NONE = 0, + CF_RDTSC = 1 << 0, + CF_MMX = 1 << 1, + CF_MMX_EXT = 1 << 2, + CF_3DNOW = 1 << 3, + CF_3DNOW_EXT = 1 << 4, + CF_SSE = 1 << 5, + CF_SSE2 = 1 << 6, + CF_ALTIVEC = 1 << 7 +}; + +struct netadr_t; +enum netadrtype_t; + +void Sys_Init(void); + +// general development dll loading for virtual machine testing +void *QDECL Sys_LoadGameDll(const char *name, + intptr_t(QDECL **entryPoint)(int, ...), + intptr_t(QDECL *systemcalls)(intptr_t, ...)); + +void Sys_UnloadDll(void *dllHandle); +bool Sys_DllExtension(const char *name); + +void QDECL Sys_Error(const char *error, ...) __attribute__((noreturn, format(printf, 1, 2))); +void Sys_Quit(void) __attribute__((noreturn)); + +char *Sys_GetClipboardData(void); // note that this isn't journaled... + +void Sys_Print(const char *msg); + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds(void); + +bool Sys_RandomBytes(byte *string, int len); + +void Sys_CryptoRandomBytes(byte *string, int len); + +// the system console is shown when a dedicated server is running +void Sys_DisplaySystemConsole(bool show); + +cpuFeatures_t Sys_GetProcessorFeatures(void); + +void Sys_SetErrorText(const char *text); + +FILE *Sys_FOpen(const char *ospath, const char *mode); +bool Sys_Mkdir(const char *path); +FILE *Sys_Mkfifo(const char *ospath); +bool Sys_OpenWithDefault( const char *path ); +char *Sys_Cwd(void); +void Sys_SetDefaultInstallPath(const char *path); +char *Sys_DefaultInstallPath(void); + +#ifdef __APPLE__ +char *Sys_DefaultAppPath(void); +#endif + +void Sys_SetDefaultHomePath(const char *path); +char *Sys_DefaultHomePath(void); +const char *Sys_Dirname(char *path); +const char *Sys_Basename(char *path); +char *Sys_ConsoleInput(void); + +char **Sys_ListFiles(const char *directory, const char *extension, + const char *filter, + int *numfiles, bool wantsubs); +void Sys_FreeFileList(char **list); +void Sys_Sleep(int msec); + +bool Sys_LowPhysicalMemory(void); + +void Sys_SetEnv(const char *name, const char *value); + +bool Sys_WritePIDFile(void); + +// Input subsystem +void IN_Init( void *windowData ); +void IN_Frame( void ); +void IN_Shutdown( void ); +void IN_Restart( void ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/sys/sys_unix.cpp b/src/sys/sys_unix.cpp new file mode 100644 index 0000000..e55655c --- /dev/null +++ b/src/sys/sys_unix.cpp @@ -0,0 +1,1006 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +#include "qcommon/cvar.h" +#include "qcommon/files.h" +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "dialog.h" +#include "sys_local.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool stdinIsATTY; + +// Used to determine where to store user-specific files +static char homePath[ MAX_OSPATH ] = { 0 }; + +/* +================== +Sys_DefaultHomePath +================== +*/ +char *Sys_DefaultHomePath(void) +{ + char *p; + + if( !*homePath && com_homepath != NULL ) + { + if( ( p = getenv( "HOME" ) ) != NULL ) + { + Com_sprintf(homePath, sizeof(homePath), "%s%c", p, PATH_SEP); +#ifdef __APPLE__ + Q_strcat(homePath, sizeof(homePath), + "Library/Application Support/"); + + if(com_homepath->string[0]) + Q_strcat(homePath, sizeof(homePath), com_homepath->string); + else + Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_MACOSX); +#else + if(com_homepath->string[0]) + Q_strcat(homePath, sizeof(homePath), com_homepath->string); + else + Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_UNIX); +#endif + } + } + + return homePath; +} + +/* +================ +Sys_Milliseconds +================ +*/ +/* base time in seconds, that's our origin + timeval:tv_sec is an int: + assuming this wraps every 0x7fffffff - ~68 years since the Epoch (1970) - we're safe till 2038 */ +unsigned long sys_timeBase = 0; +/* current time in ms, using sys_timeBase as origin + NOTE: sys_timeBase*1000 + curtime -> ms since the Epoch + 0x7fffffff ms - ~24 days + although timeval:tv_usec is an int, I'm not sure wether it is actually used as an unsigned int + (which would affect the wrap period) */ +int curtime; +int Sys_Milliseconds (void) +{ + struct timeval tp; + + gettimeofday(&tp, NULL); + + if (!sys_timeBase) + { + sys_timeBase = tp.tv_sec; + return tp.tv_usec/1000; + } + + curtime = (tp.tv_sec - sys_timeBase)*1000 + tp.tv_usec/1000; + + return curtime; +} + +/* +================== +Sys_RandomBytes +================== +*/ +bool Sys_RandomBytes( byte *string, int len ) +{ + FILE *fp; + + fp = fopen( "/dev/urandom", "r" ); + if( !fp ) + return false; + + setvbuf( fp, NULL, _IONBF, 0 ); // don't buffer reads from /dev/urandom + + if( fread( string, sizeof( byte ), len, fp ) != len ) + { + fclose( fp ); + return false; + } + + fclose( fp ); + return true; +} + +/* +================== +Sys_GetCurrentUser +================== +*/ +const char *Sys_GetCurrentUser( void ) +{ + struct passwd *p; + + if ( (p = getpwuid( getuid() )) == NULL ) { + return "player"; + } + return p->pw_name; +} + +/* +================== +Sys_CryptoRandomBytes +================== +*/ +void Sys_CryptoRandomBytes( byte *string, int len ) +{ + if ( !Sys_RandomBytes( string, len ) ) + Com_Error( ERR_FATAL, "Sys_CryptoRandomBytes: error reading /dev/urandom" ); +} + +#define MEM_THRESHOLD 96*1024*1024 + +/* +================== +Sys_LowPhysicalMemory + +TODO +================== +*/ +bool Sys_LowPhysicalMemory( void ) +{ + return false; +} + +/* +================== +Sys_Basename +================== +*/ +const char *Sys_Basename( char *path ) +{ + return basename( path ); +} + +/* +================== +Sys_Dirname +================== +*/ +const char *Sys_Dirname( char* path ) +{ + return dirname( path ); +} + +/* +============== +Sys_FOpen +============== +*/ +FILE *Sys_FOpen( const char *ospath, const char *mode ) { + struct stat buf; + + // check if path exists and is a directory + if ( !stat( ospath, &buf ) && S_ISDIR( buf.st_mode ) ) + return NULL; + + return fopen( ospath, mode ); +} + +/* +================== +Sys_Mkdir +================== +*/ +bool Sys_Mkdir( const char *path ) +{ + int result = mkdir( path, 0750 ); + + if( result != 0 ) + return (bool)(errno == EEXIST); + + return true; +} + +/* +================== +Sys_Mkfifo +================== +*/ +FILE *Sys_Mkfifo( const char *ospath ) +{ + FILE *fifo; + int result; + int fn; + struct stat buf; + + // if file already exists AND is a pipefile, remove it + if( !stat( ospath, &buf ) && S_ISFIFO( buf.st_mode ) ) + FS_Remove( ospath ); + + result = mkfifo( ospath, 0600 ); + if( result != 0 ) + return NULL; + + fifo = fopen( ospath, "w+" ); + if( fifo ) + { + fn = fileno( fifo ); + fcntl( fn, F_SETFL, O_NONBLOCK ); + } + + return fifo; +} + +/* +============== +Sys_OpenWithDefault + +Opens a path with the default application +============== +*/ +bool Sys_OpenWithDefault( const char *path ) +{ + int status; + int exitNum; + pid_t pid; + + Com_Printf( S_COLOR_WHITE "Sys_OpenWithDefault: opening %s .....\n", + path ); + + // attempt to start child process + pid = fork(); + + if( pid < 0 ) + { + // failed to start the child process + Com_Printf( S_COLOR_RED "Sys_OpenWithDefault: %s\n" S_COLOR_WHITE, + strerror( exitNum ) ); + return false; + } + else if ( pid == 0 ) + { + //child proccess + char *argv[3]; + char tempPath[MAX_OSPATH]; + char openCmd[MAX_OSPATH]; + + ::memset( tempPath, 0, sizeof( tempPath ) ); + ::memset( openCmd, 0, sizeof( openCmd ) ); + + Q_strcat( tempPath, sizeof(tempPath), path ); + + argv[1] = tempPath; + argv[2] = NULL; + +#ifdef __APPLE__ + Q_strcat( openCmd, sizeof(openCmd), "open"); +#else + Q_strcat( openCmd, sizeof(openCmd), "xdg-open"); +#endif + + argv[0] = openCmd; + + // attempt to open the path + if( execvp( argv[0], argv ) < 0 ) + { + //failure + exit( errno ); + } + + //success + exit(0); + } + + wait( &status ); + exitNum = WEXITSTATUS( status ); + + if( !exitNum ) + { + return true; + } + else + { + Com_Printf( S_COLOR_RED "Sys_OpenWithDefault: %s\n" S_COLOR_WHITE, strerror( exitNum ) ); + return false; + } +} + +/* +================== +Sys_Cwd +================== +*/ +char *Sys_Cwd( void ) +{ + static char cwd[MAX_OSPATH]; + + char *result = getcwd( cwd, sizeof( cwd ) - 1 ); + if( result != cwd ) + return NULL; + + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============================================================== + +DIRECTORY SCANNING + +============================================================== +*/ + +#define MAX_FOUND_FILES 0x1000 + +/* +================== +Sys_ListFilteredFiles +================== +*/ +void Sys_ListFilteredFiles( const char *basedir, const char *subdirs, + const char *filter, char **list, int *numfiles ) +{ + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + DIR *fdir; + struct dirent *d; + struct stat st; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s/%s", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s", basedir ); + } + + if ((fdir = opendir(search)) == NULL) { + return; + } + + while ((d = readdir(fdir)) != NULL) { + Com_sprintf(filename, sizeof(filename), "%s/%s", search, d->d_name); + if (stat(filename, &st) == -1) + continue; + + if (st.st_mode & S_IFDIR) { + if (Q_stricmp(d->d_name, ".") && Q_stricmp(d->d_name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s/%s", subdirs, d->d_name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", d->d_name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s/%s", subdirs, d->d_name ); + if (!Com_FilterPath( filter, filename, false )) + continue; + list[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } + + closedir(fdir); +} + +/* +================== +Sys_ListFiles +================== +*/ +char **Sys_ListFiles( const char *directory, const char *extension, + const char *filter, int *numfiles, bool wantsubs ) +{ + struct dirent *d; + DIR *fdir; + bool dironly = wantsubs; + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + int i; + struct stat st; + + int extLen; + + if (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = NULL; + *numfiles = nfiles; + + if (!nfiles) + return NULL; + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; + } + + if ( !extension) + extension = ""; + + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + dironly = true; + } + + extLen = strlen( extension ); + + // search + nfiles = 0; + + if ((fdir = opendir(directory)) == NULL) { + *numfiles = 0; + return NULL; + } + + while ((d = readdir(fdir)) != NULL) { + Com_sprintf(search, sizeof(search), "%s/%s", directory, d->d_name); + if (stat(search, &st) == -1) + continue; + if ((dironly && !(st.st_mode & S_IFDIR)) || + (!dironly && (st.st_mode & S_IFDIR))) + continue; + + if (*extension) { + if ( strlen( d->d_name ) < extLen || + Q_stricmp( + d->d_name + strlen( d->d_name ) - extLen, + extension ) ) { + continue; // didn't match + } + } + + if ( nfiles == MAX_FOUND_FILES - 1 ) + break; + list[ nfiles ] = CopyString( d->d_name ); + nfiles++; + } + + list[ nfiles ] = NULL; + + closedir(fdir); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +/* +================== +Sys_FreeFileList +================== +*/ +void Sys_FreeFileList( char **list ) +{ + int i; + + if ( !list ) { + return; + } + + for ( i = 0 ; list[i] ; i++ ) { + Z_Free( list[i] ); + } + + Z_Free( list ); +} + +/* +================== +Sys_Sleep + +Block execution for msec or until input is recieved. +================== +*/ +void Sys_Sleep( int msec ) +{ + if( msec == 0 ) + return; + + if( stdinIsATTY ) + { + fd_set fdset; + + FD_ZERO(&fdset); + FD_SET(STDIN_FILENO, &fdset); + if( msec < 0 ) + { + select(STDIN_FILENO + 1, &fdset, NULL, NULL, NULL); + } + else + { + struct timeval timeout; + + timeout.tv_sec = msec/1000; + timeout.tv_usec = (msec%1000)*1000; + select(STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout); + } + } + else + { + // With nothing to select() on, we can't wait indefinitely + if( msec < 0 ) + msec = 10; + + usleep( msec * 1000 ); + } +} + +/* +============== +Sys_ErrorDialog + +Display an error message +============== +*/ +void Sys_ErrorDialog( const char *error ) +{ + char buffer[ 1024 ]; + unsigned int size; + int f = -1; + const char *homepath = Cvar_VariableString( "fs_homepath" ); + const char *gamedir = Cvar_VariableString( "fs_game" ); + const char *fileName = "crashlog.txt"; + char *dirpath = FS_BuildOSPath( homepath, gamedir, ""); + char *ospath = FS_BuildOSPath( homepath, gamedir, fileName ); + + Sys_Print( va( "%s\n", error ) ); + +#ifndef DEDICATED + Sys_Dialog( DT_ERROR, va( "%s. See \"%s\" for details.", error, ospath ), "Error" ); +#endif + + // Make sure the write path for the crashlog exists... + + if(!Sys_Mkdir(homepath)) + { + Com_Printf("ERROR: couldn't create path '%s' for crash log.\n", homepath); + return; + } + + if(!Sys_Mkdir(dirpath)) + { + Com_Printf("ERROR: couldn't create path '%s' for crash log.\n", dirpath); + return; + } + + // We might be crashing because we maxed out the Quake MAX_FILE_HANDLES, + // which will come through here, so we don't want to recurse forever by + // calling FS_FOpenFileWrite()...use the Unix system APIs instead. + f = open( ospath, O_CREAT | O_TRUNC | O_WRONLY, 0640 ); + if( f == -1 ) + { + Com_Printf( "ERROR: couldn't open %s\n", fileName ); + return; + } + + // We're crashing, so we don't care much if write() or close() fails. + while( ( size = CON_LogRead( buffer, sizeof( buffer ) ) ) > 0 ) { + if( write( f, buffer, size ) != size ) { + Com_Printf( "ERROR: couldn't fully write to %s\n", fileName ); + break; + } + } + + close( f ); +} + +#ifndef __APPLE__ +static char execBuffer[ 1024 ]; +static char *execBufferPointer; +static char *execArgv[ 16 ]; +static int execArgc; + +/* +============== +Sys_ClearExecBuffer +============== +*/ +static void Sys_ClearExecBuffer( void ) +{ + execBufferPointer = execBuffer; + ::memset( execArgv, 0, sizeof( execArgv ) ); + execArgc = 0; +} + +/* +============== +Sys_AppendToExecBuffer +============== +*/ +static void Sys_AppendToExecBuffer( const char *text ) +{ + size_t size = sizeof( execBuffer ) - ( execBufferPointer - execBuffer ); + int length = strlen( text ) + 1; + + if( length > size || execArgc >= ARRAY_LEN( execArgv ) ) + return; + + Q_strncpyz( execBufferPointer, text, size ); + execArgv[ execArgc++ ] = execBufferPointer; + + execBufferPointer += length; +} + +/* +============== +Sys_Exec +============== +*/ +static int Sys_Exec( void ) +{ + pid_t pid = fork( ); + + if( pid < 0 ) + return -1; + + if( pid ) + { + // Parent + int exitCode; + + wait( &exitCode ); + + return WEXITSTATUS( exitCode ); + } + else + { + // Child + execvp( execArgv[ 0 ], execArgv ); + + // Failed to execute + exit( -1 ); + + return -1; + } +} + +/* +============== +Sys_ZenityCommand +============== +*/ +static void Sys_ZenityCommand( dialogType_t type, const char *message, const char *title ) +{ + Sys_ClearExecBuffer( ); + Sys_AppendToExecBuffer( "zenity" ); + + switch( type ) + { + default: + case DT_INFO: Sys_AppendToExecBuffer( "--info" ); break; + case DT_WARNING: Sys_AppendToExecBuffer( "--warning" ); break; + case DT_ERROR: Sys_AppendToExecBuffer( "--error" ); break; + case DT_YES_NO: + Sys_AppendToExecBuffer( "--question" ); + Sys_AppendToExecBuffer( "--ok-label=Yes" ); + Sys_AppendToExecBuffer( "--cancel-label=No" ); + break; + + case DT_OK_CANCEL: + Sys_AppendToExecBuffer( "--question" ); + Sys_AppendToExecBuffer( "--ok-label=OK" ); + Sys_AppendToExecBuffer( "--cancel-label=Cancel" ); + break; + } + + Sys_AppendToExecBuffer( va( "--text=%s", message ) ); + Sys_AppendToExecBuffer( va( "--title=%s", title ) ); +} + +/* +============== +Sys_KdialogCommand +============== +*/ +static void Sys_KdialogCommand( dialogType_t type, const char *message, const char *title ) +{ + Sys_ClearExecBuffer( ); + Sys_AppendToExecBuffer( "kdialog" ); + + switch( type ) + { + default: + case DT_INFO: Sys_AppendToExecBuffer( "--msgbox" ); break; + case DT_WARNING: Sys_AppendToExecBuffer( "--sorry" ); break; + case DT_ERROR: Sys_AppendToExecBuffer( "--error" ); break; + case DT_YES_NO: Sys_AppendToExecBuffer( "--warningyesno" ); break; + case DT_OK_CANCEL: Sys_AppendToExecBuffer( "--warningcontinuecancel" ); break; + } + + Sys_AppendToExecBuffer( message ); + Sys_AppendToExecBuffer( va( "--title=%s", title ) ); +} + +/* +============== +Sys_XmessageCommand +============== +*/ +static void Sys_XmessageCommand( dialogType_t type, const char *message, const char *title ) +{ + Sys_ClearExecBuffer( ); + Sys_AppendToExecBuffer( "xmessage" ); + Sys_AppendToExecBuffer( "-buttons" ); + + switch( type ) + { + default: Sys_AppendToExecBuffer( "OK:0" ); break; + case DT_YES_NO: Sys_AppendToExecBuffer( "Yes:0,No:1" ); break; + case DT_OK_CANCEL: Sys_AppendToExecBuffer( "OK:0,Cancel:1" ); break; + } + + Sys_AppendToExecBuffer( "-center" ); + Sys_AppendToExecBuffer( message ); +} + +/* +============== +Sys_Dialog + +Display a *nix dialog box +============== +*/ +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) +{ + typedef enum + { + NONE = 0, + ZENITY, + KDIALOG, + XMESSAGE, + NUM_DIALOG_PROGRAMS + } dialogCommandType_t; + typedef void (*dialogCommandBuilder_t)( dialogType_t, const char *, const char * ); + + const char *session = getenv( "DESKTOP_SESSION" ); + bool tried[ NUM_DIALOG_PROGRAMS ] = { false }; + dialogCommandBuilder_t commands[ NUM_DIALOG_PROGRAMS ] = { NULL }; + dialogCommandType_t preferredCommandType = NONE; + int i; + + commands[ ZENITY ] = &Sys_ZenityCommand; + commands[ KDIALOG ] = &Sys_KdialogCommand; + commands[ XMESSAGE ] = &Sys_XmessageCommand; + + // This may not be the best way + if( !Q_stricmp( session, "gnome" ) ) + preferredCommandType = ZENITY; + else if( !Q_stricmp( session, "kde" ) ) + preferredCommandType = KDIALOG; + + for( i = NONE + 1; i < NUM_DIALOG_PROGRAMS; i++ ) + { + if( preferredCommandType != NONE && preferredCommandType != i ) + continue; + + if( !tried[ i ] ) + { + int exitCode; + + commands[ i ]( type, message, title ); + exitCode = Sys_Exec( ); + + if( exitCode >= 0 ) + { + switch( type ) + { + case DT_YES_NO: return exitCode ? DR_NO : DR_YES; + case DT_OK_CANCEL: return exitCode ? DR_CANCEL : DR_OK; + default: return DR_OK; + } + } + + tried[ i ] = true; + + // The preference failed, so start again in order + if( preferredCommandType != NONE ) + { + preferredCommandType = NONE; + i = NONE + 1; + } + } + } + + Com_DPrintf( S_COLOR_YELLOW "WARNING: failed to show a dialog\n" ); + return DR_OK; +} +#endif + +/* +============== +Sys_GLimpSafeInit + +Unix specific "safe" GL implementation initialisation +============== +*/ +void Sys_GLimpSafeInit( void ) +{ + // NOP +} + +/* +============== +Sys_GLimpInit + +Unix specific GL implementation initialisation +============== +*/ +void Sys_GLimpInit( void ) +{ + // NOP +} + +void Sys_SetFloatEnv(void) +{ + // rounding toward nearest + fesetround(FE_TONEAREST); +} + +/* +============== +Sys_PlatformInit + +Unix specific initialisation +============== +*/ +void Sys_PlatformInit( void ) +{ + const char* term = getenv( "TERM" ); + + signal( SIGHUP, Sys_SigHandler ); + signal( SIGQUIT, Sys_SigHandler ); + signal( SIGTRAP, Sys_SigHandler ); + signal( SIGABRT, Sys_SigHandler ); + signal( SIGBUS, Sys_SigHandler ); + + Sys_SetFloatEnv(); + + stdinIsATTY = isatty( STDIN_FILENO ) && + !( term && ( !strcmp( term, "raw" ) || !strcmp( term, "dumb" ) ) ); +} + +/* +============== +Sys_PlatformExit + +Unix specific deinitialisation +============== +*/ +void Sys_PlatformExit( void ) +{ +} + +/* +============== +Sys_SetEnv + +set/unset environment variables (empty value removes it) +============== +*/ + +void Sys_SetEnv(const char *name, const char *value) +{ + if(value && *value) + setenv(name, value, 1); + else + unsetenv(name); +} + +/* +============== +Sys_PID +============== +*/ +int Sys_PID( void ) +{ + return getpid( ); +} + +/* +============== +Sys_PIDIsRunning +============== +*/ +bool Sys_PIDIsRunning( int pid ) +{ + return kill( pid, 0 ) == 0; +} + + +/* +================= +Sys_DllExtension + +Check if filename should be allowed to be loaded as a DLL. +================= +*/ +bool Sys_DllExtension( const char *name ) +{ + const char *p; + char c = 0; + + if ( COM_CompareExtension(name, DLL_EXT) ) + return true; + + // Check for format of filename.so.1.2.3 + p = strstr( name, DLL_EXT "." ); + + if ( p ) + { + p += strlen( DLL_EXT ); + + // Check if .so is only followed for periods and numbers. + while ( *p ) + { + c = *p; + + if ( !isdigit( c ) && c != '.' ) + return false; + + p++; + } + + // Don't allow filename to end in a period. file.so., file.so.0., etc + if ( c != '.' ) + return true; + } + + return false; +} diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp new file mode 100644 index 0000000..0ec1f2c --- /dev/null +++ b/src/sys/sys_win32.cpp @@ -0,0 +1,842 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qcommon/q_shared.h" +#include "qcommon/qcommon.h" +#include "dialog.h" +#include "sys_local.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef DEDICATED +static UINT timerResolution = 0; +#endif + +/* +================ +Sys_SetFPUCW +Set FPU control word to default value +================ +*/ + +#ifndef _RC_CHOP +// mingw doesn't seem to have these defined :( + + #define _MCW_EM 0x0008001fU + #define _MCW_RC 0x00000300U + #define _MCW_PC 0x00030000U + #define _RC_NEAR 0x00000000U + #define _PC_53 0x00010000U + + extern "C" unsigned int _controlfp(unsigned int _new, unsigned int mask); +#endif + +#define FPUCWMASK1 (_MCW_RC | _MCW_EM) +#define FPUCW (_RC_NEAR | _MCW_EM | _PC_53) + +#if idx64 +#define FPUCWMASK (FPUCWMASK1) +#else +#define FPUCWMASK (FPUCWMASK1 | _MCW_PC) +#endif + +void Sys_SetFloatEnv(void) +{ + _controlfp(FPUCW, FPUCWMASK); +} + +/* +================ +Sys_Milliseconds +================ +*/ +int sys_timeBase; +int Sys_Milliseconds (void) +{ + int sys_curtime; + static bool initialized = false; + + if (!initialized) { + sys_timeBase = timeGetTime(); + initialized = true; + } + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +} + +/* +================ +Sys_RandomBytes +================ +*/ +bool Sys_RandomBytes( byte *string, int len ) +{ + HCRYPTPROV prov; + + if( !CryptAcquireContext( &prov, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) { + + return false; + } + + if( !CryptGenRandom( prov, len, (BYTE *)string ) ) { + CryptReleaseContext( prov, 0 ); + return false; + } + CryptReleaseContext( prov, 0 ); + return true; +} + +/* +================ +Sys_GetCurrentUser +================ +*/ +char *Sys_GetCurrentUser( void ) +{ + static char s_userName[1024]; + unsigned long size = sizeof( s_userName ); + + if( !GetUserName( s_userName, &size ) ) + strcpy( s_userName, "player" ); + + if( !s_userName[0] ) + { + strcpy( s_userName, "player" ); + } + + return s_userName; +} + +/* +================== +Sys_CryptoRandomBytes +================== +*/ +void Sys_CryptoRandomBytes( byte *string, int len ) +{ + if ( !Sys_RandomBytes( string, len ) ) + Com_Error( ERR_FATAL, "Sys_CryptoRandomBytes: error generating random data" ); +} + +#define MEM_THRESHOLD 96*1024*1024 + +/* +================== +Sys_LowPhysicalMemory +================== +*/ +bool Sys_LowPhysicalMemory( void ) +{ + MEMORYSTATUS stat; + GlobalMemoryStatus (&stat); + return (stat.dwTotalPhys <= MEM_THRESHOLD) ? true : false; +} + +/* +============== +Sys_Basename +============== +*/ +const char *Sys_Basename( char *path ) +{ + static char base[ MAX_OSPATH ] = { 0 }; + int length; + + length = strlen( path ) - 1; + + // Skip trailing slashes + while( length > 0 && path[ length ] == '\\' ) + length--; + + while( length > 0 && path[ length - 1 ] != '\\' ) + length--; + + Q_strncpyz( base, &path[ length ], sizeof( base ) ); + + length = strlen( base ) - 1; + + // Strip trailing slashes + while( length > 0 && base[ length ] == '\\' ) + base[ length-- ] = '\0'; + + return base; +} + +/* +============== +Sys_Dirname +============== +*/ +const char *Sys_Dirname( char *path ) +{ + static char dir[ MAX_OSPATH ] = { 0 }; + int length; + + Q_strncpyz( dir, path, sizeof( dir ) ); + length = strlen( dir ) - 1; + + while( length > 0 && dir[ length ] != '\\' ) + length--; + + dir[ length ] = '\0'; + + return dir; +} + +/* +============== +Sys_FOpen +============== +*/ +FILE *Sys_FOpen( const char *ospath, const char *mode ) { + return fopen( ospath, mode ); +} + +/* +============== +Sys_Mkdir +============== +*/ +bool Sys_Mkdir( const char *path ) +{ + if( !CreateDirectory( path, NULL ) ) + { + if( GetLastError( ) != ERROR_ALREADY_EXISTS ) + return false; + } + + return true; +} + +/* +================== +Sys_Mkfifo +Noop on windows because named pipes do not function the same way +================== +*/ +FILE *Sys_Mkfifo( const char *ospath ) +{ + return NULL; +} + +/* +============== +Sys_OpenWithDefault + +Opens a path with the default application +============== +*/ +bool Sys_OpenWithDefault( const char *path ) +{ + HINSTANCE hInst; + uint64_t err; + + Com_Printf( S_COLOR_WHITE "Sys_OpenWithDefault: opening %s .....\n", path ); + + hInst = ShellExecute(0, "open", path, 0, 0 , SW_SHOWNORMAL ); + err = (uint64_t)hInst; + + if( err > 32 ) + { + //success + return true; + } + + // failure + switch ( err ) + { + case 0: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The operating system is out of memory or resources.\n", + "warning" ); + break; + + case ERROR_FILE_NOT_FOUND: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The specified file was not found.\n", + "warning" ); + break; + + case ERROR_PATH_NOT_FOUND: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The specified path was not found.\n", + "warning" ); + break; + + case ERROR_BAD_FORMAT: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The .exe file is invalid (non-Win32 .exe or error in .exe image).\n", + "warning" ); + break; + + case SE_ERR_ACCESSDENIED: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The operating system denied access to the specified file.\n", + "warning" ); + break; + + case SE_ERR_ASSOCINCOMPLETE: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The file name association is incomplete or invalid.\n", + "warning" ); + break; + + case SE_ERR_DDEBUSY: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The DDE transaction could not be completed because other DDE transactions were being processed.\n", + "warning" ); + break; + + case SE_ERR_DDEFAIL: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The DDE transaction failed.\n", + "warning" ); + break; + + case SE_ERR_DDETIMEOUT: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The DDE transaction could not be completed because the request timed out.\n", + "warning" ); + break; + + case SE_ERR_DLLNOTFOUND: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: The specified DLL was not found.\n", + "warning" ); + break; + + case SE_ERR_NOASSOC: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable.\n", + "warning" ); + break; + + case SE_ERR_OOM: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: There was not enough memory to complete the operation.\n", + "warning" ); + break; + + case SE_ERR_SHARE: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: A sharing violation occurred.\n", + "warning" ); + break; + + default: + Sys_Dialog( DT_WARNING, + "Sys_OpenWithDefault: Failed to open path.\n", + "warning" ); + break; + } + + return false; +} + +/* +============== +Sys_Cwd +============== +*/ +char *Sys_Cwd( void ) { + static char cwd[MAX_OSPATH]; + + _getcwd( cwd, sizeof( cwd ) - 1 ); + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============================================================== + +DIRECTORY SCANNING + +============================================================== +*/ + +#define MAX_FOUND_FILES 0x1000 + +/* +============== +Sys_ListFilteredFiles +============== +*/ +void Sys_ListFilteredFiles( const char *basedir, const char *subdirs, + const char *filter, char **list, int *numfiles ) +{ + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + intptr_t findhandle; + struct _finddata_t findinfo; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s\\*", basedir ); + } + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + return; + } + + do { + if (findinfo.attrib & _A_SUBDIR) { + if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name ); + if (!Com_FilterPath( filter, filename, false )) + continue; + list[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } while ( _findnext (findhandle, &findinfo) != -1 ); + + _findclose (findhandle); +} + +/* +============== +strgtr +============== +*/ +static bool strgtr(const char *s0, const char *s1) +{ + int l0, l1, i; + + l0 = strlen(s0); + l1 = strlen(s1); + + if (l1 s0[i]) { + return true; + } + if (s1[i] < s0[i]) { + return false; + } + } + return false; +} + +/* +============== +Sys_ListFiles +============== +*/ +char **Sys_ListFiles( const char *directory, const char *extension, + const char *filter, int *numfiles, bool wantsubs ) +{ + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + struct _finddata_t findinfo; + intptr_t findhandle; + int flag; + int i; + int extLen; + + if (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = 0; + *numfiles = nfiles; + + if (!nfiles) + return NULL; + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; + } + + if ( !extension) { + extension = ""; + } + + // passing a slash as extension will find directories + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + flag = 0; + } else { + flag = _A_SUBDIR; + } + + extLen = strlen( extension ); + + Com_sprintf( search, sizeof(search), "%s\\*%s", directory, extension ); + + // search + nfiles = 0; + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + *numfiles = 0; + return NULL; + } + + do { + if ( (!wantsubs && flag ^ ( findinfo.attrib & _A_SUBDIR )) || (wantsubs && findinfo.attrib & _A_SUBDIR) ) { + if (*extension) { + if ( strlen( findinfo.name ) < extLen || + Q_stricmp( + findinfo.name + strlen( findinfo.name ) - extLen, + extension ) ) { + continue; // didn't match + } + } + if ( nfiles == MAX_FOUND_FILES - 1 ) { + break; + } + list[ nfiles ] = CopyString( findinfo.name ); + nfiles++; + } + } while ( _findnext (findhandle, &findinfo) != -1 ); + + list[ nfiles ] = 0; + + _findclose (findhandle); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + do { + flag = 0; + for(i=1; i 0 ) + { + memcpy( p, buffer, size ); + p += size; + } + + *p = '\0'; + + if( OpenClipboard( NULL ) && EmptyClipboard( ) ) + SetClipboardData( CF_TEXT, memoryHandle ); + + GlobalUnlock( clipMemory ); + CloseClipboard( ); + } + } +} + +/* +============== +Sys_Dialog + +Display a win32 dialog box +============== +*/ +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) +{ + UINT uType; + + switch( type ) + { + default: + case DT_INFO: uType = MB_ICONINFORMATION|MB_OK; break; + case DT_WARNING: uType = MB_ICONWARNING|MB_OK; break; + case DT_ERROR: uType = MB_ICONERROR|MB_OK; break; + case DT_YES_NO: uType = MB_ICONQUESTION|MB_YESNO; break; + case DT_OK_CANCEL: uType = MB_ICONWARNING|MB_OKCANCEL; break; + } + + switch( MessageBox( NULL, message, title, uType ) ) + { + default: + case IDOK: return DR_OK; + case IDCANCEL: return DR_CANCEL; + case IDYES: return DR_YES; + case IDNO: return DR_NO; + } +} + +/* +============== +Sys_GLimpSafeInit + +Windows specific "safe" GL implementation initialisation +============== +*/ +void Sys_GLimpSafeInit( void ) +{ +} + +/* +============== +Sys_GLimpInit + +Windows specific GL implementation initialisation +============== +*/ +void Sys_GLimpInit( void ) +{ +} + +/* +============== +Sys_PlatformInit + +Windows specific initialisation +============== +*/ +void Sys_PlatformInit( void ) +{ +#ifndef DEDICATED + TIMECAPS ptc; +#endif + + Sys_SetFloatEnv(); + +#ifndef DEDICATED + if(timeGetDevCaps(&ptc, sizeof(ptc)) == MMSYSERR_NOERROR) + { + timerResolution = ptc.wPeriodMin; + + if(timerResolution > 1) + { + Com_Printf("Warning: Minimum supported timer resolution is %ums " + "on this system, recommended resolution 1ms\n", timerResolution); + } + + timeBeginPeriod(timerResolution); + } + else + timerResolution = 0; +#endif +} + +/* +============== +Sys_PlatformExit + +Windows specific initialisation +============== +*/ +void Sys_PlatformExit( void ) +{ +#ifndef DEDICATED + if(timerResolution) + timeEndPeriod(timerResolution); +#endif +} + +/* +============== +Sys_SetEnv + +set/unset environment variables (empty value removes it) +============== +*/ +void Sys_SetEnv(const char *name, const char *value) +{ + if(value) + _putenv(va("%s=%s", name, value)); + else + _putenv(va("%s=", name)); +} + +/* +============== +Sys_PID +============== +*/ +int Sys_PID( void ) +{ + return GetCurrentProcessId( ); +} + +/* +============== +Sys_PIDIsRunning +============== +*/ +bool Sys_PIDIsRunning( int pid ) +{ + DWORD processes[ 1024 ]; + DWORD numBytes, numProcesses; + int i; + + if( !EnumProcesses( processes, sizeof( processes ), &numBytes ) ) + return false; // Assume it's not running + + numProcesses = numBytes / sizeof( DWORD ); + + // Search for the pid + for( i = 0; i < numProcesses; i++ ) + { + if( processes[ i ] == pid ) + return true; + } + + return false; +} + +/* +================= +Sys_DllExtension + +Check if filename should be allowed to be loaded as a DLL. +================= +*/ +bool Sys_DllExtension( const char *name ) +{ + return COM_CompareExtension( name, DLL_EXT ); +} diff --git a/src/sys/sys_win32_default_homepath.cpp b/src/sys/sys_win32_default_homepath.cpp new file mode 100644 index 0000000..1c4a149 --- /dev/null +++ b/src/sys/sys_win32_default_homepath.cpp @@ -0,0 +1,52 @@ +#include "sys_local.h" + +#include "qcommon/cvar.h" +#include "qcommon/q_shared.h" +#include "qcommon/q_platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Used to determine where to store user-specific files +static char homePath[ MAX_OSPATH ] = { 0 }; + +/* +================ +Sys_DefaultHomePath +================ +*/ +char *Sys_DefaultHomePath( void ) +{ + TCHAR szPath[MAX_PATH]; + + if(!*homePath && com_homepath) + { + if( !SUCCEEDED( SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, szPath ) ) ) + { + Com_Printf("Unable to detect CSIDL_APPDATA\n"); + return NULL; + } + + Com_sprintf(homePath, sizeof(homePath), "%s%c", szPath, PATH_SEP); + + if(com_homepath->string[0]) + Q_strcat(homePath, sizeof(homePath), com_homepath->string); + else + Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_WIN); + } + + return homePath; +} + diff --git a/src/sys/win_resource.h b/src/sys/win_resource.h new file mode 100644 index 0000000..8c8783a --- /dev/null +++ b/src/sys/win_resource.h @@ -0,0 +1,46 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by winquake.rc +// +#define IDS_STRING1 1 +#define IDI_ICON1 1 +#define IDB_BITMAP1 1 +#define IDB_BITMAP2 128 +#define IDC_CURSOR1 129 +#define IDC_CURSOR2 130 +#define IDC_CURSOR3 131 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 132 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/sys/win_resource.rc b/src/sys/win_resource.rc new file mode 100644 index 0000000..6f5d82a --- /dev/null +++ b/src/sys/win_resource.rc @@ -0,0 +1,72 @@ +//Microsoft Developer Studio generated resource script. +// +#include "win_resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "misc/tremulous.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_STRING1 "Tremulous" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt new file mode 100644 index 0000000..c54f9ce --- /dev/null +++ b/src/tools/CMakeLists.txt @@ -0,0 +1,145 @@ +# _______ ___ ___ _______ __ __ +# | | | | | | .----.-----.--------.-----.|__| |.-----.----. +# | - _| | | | | __| _ | | _ || | || -__| _| +# |_______|\_____/|__|_|__| |____|_____|__|__|__| __||__|__||_____|__| +# |__| +# TODO: DO NOT build unless building a QVM + + +# _ _ +# | || | +# | || | ,_ __, +# |/ |/ \_| | / | / | +# |__/\_/ \_/|_/ |_/\_/|/ +# /| +# \| + +add_executable( + lburg + lcc/lburg/lburg.c + lcc/lburg/gram.c + ) + + +# ___ +# / \ +# __, __/ __, , _ _ _ +# / | \/ | / \_/ |/ |/ | +# \_/|_/\___/\_/|_/ \/ | | |_/ +# |\ +# |/ +# + +add_executable( + q3asm + asm/q3asm.c + asm/cmdlib.c + ) + +target_include_directories( q3asm PUBLIC lcc/src lcc/lburg ) + + +# ___ _ +# / \| | +# __, __/| | __ __ +# / | \|/ / / +# \_/|_/\___/|__/\___/\___/ +# |\ +# |/ +# + +add_executable( + q3lcc + lcc/etc/bytecode.c + lcc/etc/lcc.c + ) + +target_include_directories( q3lcc PUBLIC lcc/src lcc/lburg ) + +# ___ +# / \ +# __, __/ __ _ _ +#/ | \/ |/ \_|/ \_ +#\_/|_/\___/\___/|__/ |__/ +# |\ /| /| +# |/ \| \| +# + +add_executable( + q3cpp + # + lcc/cpp/cpp.c + lcc/cpp/lex.c + lcc/cpp/nlist.c + lcc/cpp/tokens.c + lcc/cpp/macro.c + lcc/cpp/eval.c + lcc/cpp/include.c + lcc/cpp/hideset.c + lcc/cpp/getopt.c + lcc/cpp/unix.c + ) + +target_include_directories( q3cpp PUBLIC lcc/src lcc/lburg ) + +# ___ +# / \ +# __, __/ ,_ __ __ +# / | \/ | / / +# \_/|_/\___/ |_/\___/\___/ +# |\ +# |/ +# + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dagcheck.c + COMMAND lburg + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/lcc/src/dagcheck.md dagcheck.c + DEPENDS lburg lcc/src/dagcheck.md + ) +set_source_files_properties(dagcheck.c PROPERTIES GENERATED TRUE) + +add_executable( + q3rcc + lcc/src/alloc.c + lcc/src/bind.c + lcc/src/bytecode.c + lcc/src/dag.c + ${CMAKE_CURRENT_BINARY_DIR}/dagcheck.c + lcc/src/decl.c + lcc/src/enode.c + lcc/src/error.c + lcc/src/event.c + lcc/src/expr.c + lcc/src/gen.c + lcc/src/init.c + lcc/src/inits.c + lcc/src/input.c + lcc/src/lex.c + lcc/src/list.c + lcc/src/main.c + lcc/src/null.c + lcc/src/output.c + lcc/src/prof.c + lcc/src/profio.c + lcc/src/simp.c + lcc/src/stmt.c + lcc/src/string.c + lcc/src/sym.c + lcc/src/symbolic.c + lcc/src/trace.c + lcc/src/tree.c + lcc/src/types.c + ) + +target_include_directories( q3rcc PUBLIC lcc/src lcc/lburg ) + +set_property( + TARGET lburg q3asm q3lcc q3cpp q3rcc + PROPERTY RUNTIME_OUTPUT_DIRECTORY + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qvm_tools + ) + +# Victor Roemer (wtfbbqhax), . +cmake_minimum_required(VERSION 3.5) +project(QVMCompiler C) diff --git a/src/tools/asm/cmdlib.c b/src/tools/asm/cmdlib.c index 5929d95..9d530b3 100644 --- a/src/tools/asm/cmdlib.c +++ b/src/tools/asm/cmdlib.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cmdlib.c diff --git a/src/tools/asm/cmdlib.h b/src/tools/asm/cmdlib.h index 3b2f2db..78aeabd 100644 --- a/src/tools/asm/cmdlib.h +++ b/src/tools/asm/cmdlib.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ // cmdlib.h diff --git a/src/tools/asm/mathlib.h b/src/tools/asm/mathlib.h index 71bbabb..32e43e7 100644 --- a/src/tools/asm/mathlib.h +++ b/src/tools/asm/mathlib.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #ifndef __MATHLIB__ diff --git a/src/tools/asm/opstrings.h b/src/tools/asm/opstrings.h index 0bf81ab..ce8837c 100644 --- a/src/tools/asm/opstrings.h +++ b/src/tools/asm/opstrings.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ { "BREAK", OP_BREAK }, @@ -172,5 +173,3 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA { "LOADI4", OP_UNDEF }, { "LOADP4", OP_UNDEF }, { "LOADU4", OP_UNDEF }, - - diff --git a/src/tools/asm/q3asm.c b/src/tools/asm/q3asm.c index 0e35246..b18e5e9 100644 --- a/src/tools/asm/q3asm.c +++ b/src/tools/asm/q3asm.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,15 +17,16 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ -#include "../../qcommon/q_platform.h" +#include "qcommon/q_platform.h" + #include "cmdlib.h" #include "mathlib.h" -#include "../../qcommon/qfiles.h" +#include "qfiles.h" /* 19079 total symbols in FI, 2002 Jan 23 */ #define DEFAULT_HASHTABLE_SIZE 2048 @@ -478,7 +480,7 @@ static unsigned int HashString (const char *key) acc = (acc << 2) | (acc >> 30); acc &= 0xffffffffU; } - return abs((int)acc); + return acc; } @@ -1645,4 +1647,3 @@ Motivation: not wanting to scrollback for pages to find asm error. return errorCount; } - diff --git a/src/tools/asm/qfiles.h b/src/tools/asm/qfiles.h new file mode 100644 index 0000000..6b0070e --- /dev/null +++ b/src/tools/asm/qfiles.h @@ -0,0 +1,480 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous 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 3 of the License, +or (at your option) any later version. + +Tremulous 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 Tremulous; if not, see + +=========================================================================== +*/ + +// qfiles.h: quake file formats + +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// This file must be identical in the quake and utils directories + +// Ignore __attribute__ on non-gcc platforms +#ifndef __GNUC__ +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6 * SHADER_MAX_VERTEXES) + +// the maximum size of game relative pathnames +#define MAX_QPATH 64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +#define VM_MAGIC_VER2 0x12721445 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength + + //!!! below here is VM_MAGIC_VER2 !!! + int jtrgLength; // number of jump table targets +} vmHeader_t; + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3' << 24) + ('P' << 16) + ('D' << 8) + 'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 3 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE (1.0 / 64) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +/* +============================================================================== + +MDR file format + +============================================================================== +*/ + +/* + * Here are the definitions for Ravensoft's model format of md4. Raven stores their + * playermodels in .mdr files, in some games, which are pretty much like the md4 + * format implemented by ID soft. It seems like ID's original md4 stuff is not used at all. + * MDR is being used in EliteForce, JediKnight2 and Soldiers of Fortune2 (I think). + * So this comes in handy for anyone who wants to make it possible to load player + * models from these games. + * This format has bone tags, which is similar to the thing you have in md3 I suppose. + * Raven has released their version of md3view under GPL enabling me to add support + * to this codebase. Thanks to Steven Howes aka Skinner for helping with example + * source code. + * + * - Thilo Schulz (arny@ats.s.bawue.de) + */ + +#define MDR_IDENT (('5' << 24) + ('M' << 16) + ('D' << 8) + 'R') +#define MDR_VERSION 2 +#define MDR_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; +} mdrWeight_t; + +typedef struct { + vec3_t normal; + vec2_t texCoords; + int numWeights; + mdrWeight_t weights[1]; // variable sized +} mdrVertex_t; + +typedef struct { + int indexes[3]; +} mdrTriangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} mdrSurface_t; + +typedef struct { + float matrix[3][4]; +} mdrBone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + mdrBone_t bones[1]; // [numBones] +} mdrFrame_t; + +typedef struct { + unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple +} mdrCompBone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + mdrCompBone_t bones[1]; // [numBones] +} mdrCompFrame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} mdrLOD_t; + +typedef struct { + int boneIndex; + char name[32]; +} mdrTag_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // mdrFrame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int numTags; + int ofsTags; + + int ofsEnd; // end of file +} mdrHeader_t; + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define BSP_IDENT (('P' << 24) + ('S' << 16) + ('B' << 8) + 'I') +// little-endian "IBSP" + +#define BSP_VERSION 46 + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define MAX_WORLD_COORD (128 * 1024) +#define MIN_WORLD_COORD (-128 * 1024) +#define WORLD_SIZE (MAX_WORLD_COORD - MIN_WORLD_COORD) + +//============================================================================= + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} drawVert_t; + +#define drawVert_t_cleared(x) drawVert_t(x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}} + +typedef enum { MST_BAD, MST_PLANAR, MST_PATCH, MST_TRIANGLE_SOUP, MST_FLARE } mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + +#endif diff --git a/src/tools/lcc/cpp/cpp.c b/src/tools/lcc/cpp/cpp.c index 5c0cfd7..c379a72 100644 --- a/src/tools/lcc/cpp/cpp.c +++ b/src/tools/lcc/cpp/cpp.c @@ -1,326 +1,339 @@ +#include "cpp.h" +#include #include #include #include #include -#include -#include "cpp.h" char rcsid[] = "cpp.c - faked rcsid"; -#define OUTS 16384 -char outbuf[OUTS]; -char *outbufp = outbuf; -Source *cursource; -int nerrs; -struct token nltoken = { NL, 0, 0, 0, 1, (uchar*)"\n" }; -char *curtime; -int incdepth; -int ifdepth; -int ifsatisfied[NIF]; -int skipping; - - -int -main(int argc, char **argv) +#define OUTS 16384 +char outbuf[OUTS]; +char *outbufp = outbuf; +Source *cursource; +int nerrs; +struct token nltoken = {NL, 0, 0, 0, 1, (uchar *)"\n"}; +char *curtime; +int incdepth; +int ifdepth; +int ifsatisfied[NIF]; +int skipping; + +int main(int argc, char **argv) { - Tokenrow tr; - time_t t; - char ebuf[BUFSIZ]; - - setbuf(stderr, ebuf); - t = time(NULL); - curtime = ctime(&t); - maketokenrow(3, &tr); - expandlex(); - setup(argc, argv); - fixlex(); - iniths(); - genline(); - process(&tr); - flushout(); - fflush(stderr); - exit(nerrs > 0); - return 0; + Tokenrow tr; + time_t t; + char ebuf[BUFSIZ]; + + setbuf(stderr, ebuf); + t = time(NULL); + curtime = ctime(&t); + maketokenrow(3, &tr); + expandlex(); + setup(argc, argv); + fixlex(); + iniths(); + genline(); + process(&tr); + flushout(); + fflush(stderr); + exit(nerrs > 0); + return 0; } -void -process(Tokenrow *trp) +void process(Tokenrow *trp) { - int anymacros = 0; - - for (;;) { - if (trp->tp >= trp->lp) { - trp->tp = trp->lp = trp->bp; - outbufp = outbuf; - anymacros |= gettokens(trp, 1); - trp->tp = trp->bp; - } - if (trp->tp->type == END) { - if (--incdepth>=0) { - if (cursource->ifdepth) - error(ERROR, - "Unterminated conditional in #include"); - unsetsource(); - cursource->line += cursource->lineinc; - trp->tp = trp->lp; - genline(); - continue; - } - if (ifdepth) - error(ERROR, "Unterminated #if/#ifdef/#ifndef"); - break; - } - if (trp->tp->type==SHARP) { - trp->tp += 1; - control(trp); - } else if (!skipping && anymacros) - expandrow(trp, NULL); - if (skipping) - setempty(trp); - puttokens(trp); - anymacros = 0; - cursource->line += cursource->lineinc; - if (cursource->lineinc>1) { - genline(); - } - } + int anymacros = 0; + + for (;;) + { + if (trp->tp >= trp->lp) + { + trp->tp = trp->lp = trp->bp; + outbufp = outbuf; + anymacros |= gettokens(trp, 1); + trp->tp = trp->bp; + } + if (trp->tp->type == END) + { + if (--incdepth >= 0) + { + if (cursource->ifdepth) + error(ERROR, "Unterminated conditional in #include"); + unsetsource(); + cursource->line += cursource->lineinc; + trp->tp = trp->lp; + genline(); + continue; + } + if (ifdepth) + error(ERROR, "Unterminated #if/#ifdef/#ifndef"); + break; + } + if (trp->tp->type == SHARP) + { + trp->tp += 1; + control(trp); + } + else if (!skipping && anymacros) + expandrow(trp, NULL); + if (skipping) + setempty(trp); + puttokens(trp); + anymacros = 0; + cursource->line += cursource->lineinc; + if (cursource->lineinc > 1) + { + genline(); + } + } } - -void -control(Tokenrow *trp) -{ - Nlist *np; - Token *tp; - - tp = trp->tp; - if (tp->type!=NAME) { - if (tp->type==NUMBER) - goto kline; - if (tp->type != NL) - error(ERROR, "Unidentifiable control line"); - return; /* else empty line */ - } - if ((np = lookup(tp, 0))==NULL || ((np->flag&ISKW)==0 && !skipping)) { - error(WARNING, "Unknown preprocessor control %t", tp); - return; - } - if (skipping) { - switch (np->val) { - case KENDIF: - if (--ifdepthifdepth; - setempty(trp); - return; - - case KIFDEF: - case KIFNDEF: - case KIF: - if (++ifdepth >= NIF) - error(FATAL, "#if too deeply nested"); - ++cursource->ifdepth; - return; - - case KELIF: - case KELSE: - if (ifdepth<=skipping) - break; - return; - - default: - return; - } - } - switch (np->val) { - case KDEFINE: - dodefine(trp); - break; - - case KUNDEF: - tp += 1; - if (tp->type!=NAME || trp->lp - trp->bp != 4) { - error(ERROR, "Syntax error in #undef"); - break; - } - if ((np = lookup(tp, 0)) != NULL) - np->flag &= ~ISDEFINED; - break; - - case KPRAGMA: - return; - - case KIFDEF: - case KIFNDEF: - case KIF: - if (++ifdepth >= NIF) - error(FATAL, "#if too deeply nested"); - ++cursource->ifdepth; - ifsatisfied[ifdepth] = 0; - if (eval(trp, np->val)) - ifsatisfied[ifdepth] = 1; - else - skipping = ifdepth; - break; - case KELIF: - if (ifdepth==0) { - error(ERROR, "#elif with no #if"); - return; - } - if (ifsatisfied[ifdepth]==2) - error(ERROR, "#elif after #else"); - if (eval(trp, np->val)) { - if (ifsatisfied[ifdepth]) - skipping = ifdepth; - else { - skipping = 0; - ifsatisfied[ifdepth] = 1; - } - } else - skipping = ifdepth; - break; - - case KELSE: - if (ifdepth==0 || cursource->ifdepth==0) { - error(ERROR, "#else with no #if"); - return; - } - if (ifsatisfied[ifdepth]==2) - error(ERROR, "#else after #else"); - if (trp->lp - trp->bp != 3) - error(ERROR, "Syntax error in #else"); - skipping = ifsatisfied[ifdepth]? ifdepth: 0; - ifsatisfied[ifdepth] = 2; - break; - - case KENDIF: - if (ifdepth==0 || cursource->ifdepth==0) { - error(ERROR, "#endif with no #if"); - return; - } - --ifdepth; - --cursource->ifdepth; - if (trp->lp - trp->bp != 3) - error(WARNING, "Syntax error in #endif"); - break; - - case KWARNING: - trp->tp = tp+1; - error(WARNING, "#warning directive: %r", trp); - break; - - case KERROR: - trp->tp = tp+1; - error(ERROR, "#error directive: %r", trp); - break; - - case KLINE: - trp->tp = tp+1; - expandrow(trp, ""); - tp = trp->bp+2; - kline: - if (tp+1>=trp->lp || tp->type!=NUMBER || tp+3lp - || ((tp+3==trp->lp && ((tp+1)->type!=STRING))||*(tp+1)->t=='L')){ - error(ERROR, "Syntax error in #line"); - return; - } - cursource->line = atol((char*)tp->t)-1; - if (cursource->line<0 || cursource->line>=32768) - error(WARNING, "#line specifies number out of range"); - tp = tp+1; - if (tp+1lp) - cursource->filename=(char*)newstring(tp->t+1,tp->len-2,0); - return; - - case KDEFINED: - error(ERROR, "Bad syntax for control line"); - break; - - case KINCLUDE: - doinclude(trp); - trp->lp = trp->bp; - return; - - case KEVAL: - eval(trp, np->val); - break; - - default: - error(ERROR, "Preprocessor control `%t' not yet implemented", tp); - break; - } - setempty(trp); +void control(Tokenrow *trp) +{ + Nlist *np; + Token *tp; + + tp = trp->tp; + if (tp->type != NAME) + { + if (tp->type == NUMBER) + goto kline; + if (tp->type != NL) + error(ERROR, "Unidentifiable control line"); + return; /* else empty line */ + } + if ((np = lookup(tp, 0)) == NULL || ((np->flag & ISKW) == 0 && !skipping)) + { + error(WARNING, "Unknown preprocessor control %t", tp); + return; + } + if (skipping) + { + switch (np->val) + { + case KENDIF: + if (--ifdepth < skipping) + skipping = 0; + --cursource->ifdepth; + setempty(trp); + return; + + case KIFDEF: + case KIFNDEF: + case KIF: + if (++ifdepth >= NIF) + error(FATAL, "#if too deeply nested"); + ++cursource->ifdepth; + return; + + case KELIF: + case KELSE: + if (ifdepth <= skipping) + break; + return; + + default: + return; + } + } + switch (np->val) + { + case KDEFINE: + dodefine(trp); + break; + + case KUNDEF: + tp += 1; + if (tp->type != NAME || trp->lp - trp->bp != 4) + { + error(ERROR, "Syntax error in #undef"); + break; + } + if ((np = lookup(tp, 0)) != NULL) + np->flag &= ~ISDEFINED; + break; + + case KPRAGMA: + return; + + case KIFDEF: + case KIFNDEF: + case KIF: + if (++ifdepth >= NIF) + error(FATAL, "#if too deeply nested"); + ++cursource->ifdepth; + ifsatisfied[ifdepth] = 0; + if (eval(trp, np->val)) + ifsatisfied[ifdepth] = 1; + else + skipping = ifdepth; + break; + + case KELIF: + if (ifdepth == 0) + { + error(ERROR, "#elif with no #if"); + return; + } + if (ifsatisfied[ifdepth] == 2) + error(ERROR, "#elif after #else"); + if (eval(trp, np->val)) + { + if (ifsatisfied[ifdepth]) + skipping = ifdepth; + else + { + skipping = 0; + ifsatisfied[ifdepth] = 1; + } + } + else + skipping = ifdepth; + break; + + case KELSE: + if (ifdepth == 0 || cursource->ifdepth == 0) + { + error(ERROR, "#else with no #if"); + return; + } + if (ifsatisfied[ifdepth] == 2) + error(ERROR, "#else after #else"); + if (trp->lp - trp->bp != 3) + error(ERROR, "Syntax error in #else"); + skipping = ifsatisfied[ifdepth] ? ifdepth : 0; + ifsatisfied[ifdepth] = 2; + break; + + case KENDIF: + if (ifdepth == 0 || cursource->ifdepth == 0) + { + error(ERROR, "#endif with no #if"); + return; + } + --ifdepth; + --cursource->ifdepth; + if (trp->lp - trp->bp != 3) + error(WARNING, "Syntax error in #endif"); + break; + + case KWARNING: + trp->tp = tp + 1; + error(WARNING, "#warning directive: %r", trp); + break; + + case KERROR: + trp->tp = tp + 1; + error(ERROR, "#error directive: %r", trp); + break; + + case KLINE: + trp->tp = tp + 1; + expandrow(trp, ""); + tp = trp->bp + 2; + kline: + if (tp + 1 >= trp->lp || tp->type != NUMBER || tp + 3 < trp->lp || + ((tp + 3 == trp->lp && ((tp + 1)->type != STRING)) || *(tp + 1)->t == 'L')) + { + error(ERROR, "Syntax error in #line"); + return; + } + cursource->line = atol((char *)tp->t) - 1; + if (cursource->line < 0 || cursource->line >= 32768) + error(WARNING, "#line specifies number out of range"); + tp = tp + 1; + if (tp + 1 < trp->lp) + cursource->filename = (char *)newstring(tp->t + 1, tp->len - 2, 0); + return; + + case KDEFINED: + error(ERROR, "Bad syntax for control line"); + break; + + case KINCLUDE: + doinclude(trp); + trp->lp = trp->bp; + return; + + case KEVAL: + eval(trp, np->val); + break; + + default: + error(ERROR, "Preprocessor control `%t' not yet implemented", tp); + break; + } + setempty(trp); } -void * -domalloc(int size) +void *domalloc(int size) { - void *p = malloc(size); + void *p = malloc(size); - if (p==NULL) - error(FATAL, "Out of memory from malloc"); - return p; + if (p == NULL) + error(FATAL, "Out of memory from malloc"); + return p; } -void -dofree(void *p) -{ - free(p); -} +void dofree(void *p) { free(p); } -void -error(enum errtype type, char *string, ...) +void error(enum errtype type, char *string, ...) { - va_list ap; - char *cp, *ep; - Token *tp; - Tokenrow *trp; - Source *s; - int i; - - fprintf(stderr, "cpp: "); - for (s=cursource; s; s=s->next) - if (*s->filename) - fprintf(stderr, "%s:%d ", s->filename, s->line); - va_start(ap, string); - for (ep=string; *ep; ep++) { - if (*ep=='%') { - switch (*++ep) { - - case 's': - cp = va_arg(ap, char *); - fprintf(stderr, "%s", cp); - break; - case 'd': - i = va_arg(ap, int); - fprintf(stderr, "%d", i); - break; - case 't': - tp = va_arg(ap, Token *); - fprintf(stderr, "%.*s", tp->len, tp->t); - break; - - case 'r': - trp = va_arg(ap, Tokenrow *); - for (tp=trp->tp; tplp&&tp->type!=NL; tp++) { - if (tp>trp->tp && tp->wslen) - fputc(' ', stderr); - fprintf(stderr, "%.*s", tp->len, tp->t); - } - break; - - default: - fputc(*ep, stderr); - break; - } - } else - fputc(*ep, stderr); - } - va_end(ap); - fputc('\n', stderr); - if (type==FATAL) - exit(1); - if (type!=WARNING) - nerrs = 1; - fflush(stderr); + va_list ap; + char *cp, *ep; + Token *tp; + Tokenrow *trp; + Source *s; + int i; + + fprintf(stderr, "cpp: "); + for (s = cursource; s; s = s->next) + if (*s->filename) + fprintf(stderr, "%s:%d ", s->filename, s->line); + va_start(ap, string); + for (ep = string; *ep; ep++) + { + if (*ep == '%') + { + switch (*++ep) + { + case 's': + cp = va_arg(ap, char *); + fprintf(stderr, "%s", cp); + break; + case 'd': + i = va_arg(ap, int); + fprintf(stderr, "%d", i); + break; + case 't': + tp = va_arg(ap, Token *); + fprintf(stderr, "%.*s", tp->len, tp->t); + break; + + case 'r': + trp = va_arg(ap, Tokenrow *); + for (tp = trp->tp; tp < trp->lp && tp->type != NL; tp++) + { + if (tp > trp->tp && tp->wslen) + fputc(' ', stderr); + fprintf(stderr, "%.*s", tp->len, tp->t); + } + break; + + default: + fputc(*ep, stderr); + break; + } + } + else + fputc(*ep, stderr); + } + va_end(ap); + fputc('\n', stderr); + if (type == FATAL) + exit(1); + if (type != WARNING) + nerrs = 1; + fflush(stderr); } diff --git a/src/tools/lcc/cpp/include.c b/src/tools/lcc/cpp/include.c index 5ecd8b3..778cea2 100644 --- a/src/tools/lcc/cpp/include.c +++ b/src/tools/lcc/cpp/include.c @@ -3,151 +3,171 @@ #include #include "cpp.h" -Includelist includelist[NINCLUDE]; +Includelist includelist[NINCLUDE]; -extern char *objname; +extern char *objname; -void appendDirToIncludeList( char *dir ) +void appendDirToIncludeList(char *dir) { - int i; - char *fqdir; + int i; + char *fqdir; - fqdir = (char *)newstring( (uchar *)includelist[NINCLUDE-1].file, 256, 0 ); - strcat( fqdir, "/" ); - strcat( fqdir, dir ); + fqdir = (char *)newstring((uchar *)includelist[NINCLUDE - 1].file, 256, 0); + strcat(fqdir, "/"); + strcat(fqdir, dir); - //avoid adding it more than once - for (i=NINCLUDE-2; i>=0; i--) { - if (includelist[i].file && - !strcmp (includelist[i].file, fqdir)) { - return; - } - } + // avoid adding it more than once + for (i = NINCLUDE - 2; i >= 0; i--) + { + if (includelist[i].file && !strcmp(includelist[i].file, fqdir)) + { + return; + } + } - for (i=NINCLUDE-2; i>=0; i--) { - if (includelist[i].file==NULL) { - includelist[i].always = 1; - includelist[i].file = fqdir; - break; - } - } - if (i<0) - error(FATAL, "Too many -I directives"); + for (i = NINCLUDE - 2; i >= 0; i--) + { + if (includelist[i].file == NULL) + { + includelist[i].always = 1; + includelist[i].file = fqdir; + break; + } + } + if (i < 0) + error(FATAL, "Too many -I directives"); } -void -doinclude(Tokenrow *trp) +void doinclude(Tokenrow *trp) { - char fname[256], iname[256]; - Includelist *ip; - int angled, len, fd, i; + char fname[256], iname[256]; + Includelist *ip; + int angled, len, fd, i; - trp->tp += 1; - if (trp->tp>=trp->lp) - goto syntax; - if (trp->tp->type!=STRING && trp->tp->type!=LT) { - len = trp->tp - trp->bp; - expandrow(trp, ""); - trp->tp = trp->bp+len; - } - if (trp->tp->type==STRING) { - len = trp->tp->len-2; - if (len > sizeof(fname) - 1) - len = sizeof(fname) - 1; - strncpy(fname, (char*)trp->tp->t+1, len); - angled = 0; - } else if (trp->tp->type==LT) { - len = 0; - trp->tp++; - while (trp->tp->type!=GT) { - if (trp->tp>trp->lp || len+trp->tp->len+2 >= sizeof(fname)) - goto syntax; - strncpy(fname+len, (char*)trp->tp->t, trp->tp->len); - len += trp->tp->len; - trp->tp++; - } - angled = 1; - } else - goto syntax; - trp->tp += 2; - if (trp->tp < trp->lp || len==0) - goto syntax; - fname[len] = '\0'; + trp->tp += 1; + if (trp->tp >= trp->lp) + goto syntax; + if (trp->tp->type != STRING && trp->tp->type != LT) + { + len = trp->tp - trp->bp; + expandrow(trp, ""); + trp->tp = trp->bp + len; + } + if (trp->tp->type == STRING) + { + len = trp->tp->len - 2; + if (len > sizeof(fname) - 1) + len = sizeof(fname) - 1; + strncpy(fname, (char *)trp->tp->t + 1, len); + angled = 0; + } + else if (trp->tp->type == LT) + { + len = 0; + trp->tp++; + while (trp->tp->type != GT) + { + if (trp->tp > trp->lp || len + trp->tp->len + 2 >= sizeof(fname)) + goto syntax; + strncpy(fname + len, (char *)trp->tp->t, trp->tp->len); + len += trp->tp->len; + trp->tp++; + } + angled = 1; + } + else + goto syntax; + trp->tp += 2; + if (trp->tp < trp->lp || len == 0) + goto syntax; + fname[len] = '\0'; - appendDirToIncludeList( basepath( fname ) ); + appendDirToIncludeList(basepath(fname)); - if (fname[0]=='/') { - fd = open(fname, 0); - strcpy(iname, fname); - } else for (fd = -1,i=NINCLUDE-1; i>=0; i--) { - ip = &includelist[i]; - if (ip->file==NULL || ip->deleted || (angled && ip->always==0)) - continue; - if (strlen(fname)+strlen(ip->file)+2 > sizeof(iname)) - continue; - strcpy(iname, ip->file); - strcat(iname, "/"); - strcat(iname, fname); - if ((fd = open(iname, 0)) >= 0) - break; - } - if ( Mflag>1 || (!angled&&Mflag==1) ) { - write(1,objname,strlen(objname)); - write(1,iname,strlen(iname)); - write(1,"\n",1); - } - if (fd >= 0) { - if (++incdepth > 10) - error(FATAL, "#include too deeply nested"); - setsource((char*)newstring((uchar*)iname, strlen(iname), 0), fd, NULL); - genline(); - } else { - trp->tp = trp->bp+2; - error(ERROR, "Could not find include file %r", trp); - } - return; + if (fname[0] == '/') + { + fd = open(fname, 0); + strcpy(iname, fname); + } + else + for (fd = -1, i = NINCLUDE - 1; i >= 0; i--) + { + ip = &includelist[i]; + if (ip->file == NULL || ip->deleted || (angled && ip->always == 0)) + continue; + if (strlen(fname) + strlen(ip->file) + 2 > sizeof(iname)) + continue; + strcpy(iname, ip->file); + strcat(iname, "/"); + strcat(iname, fname); + if ((fd = open(iname, 0)) >= 0) + break; + } + if (Mflag > 1 || (!angled && Mflag == 1)) + { + write(1, objname, strlen(objname)); + write(1, iname, strlen(iname)); + write(1, "\n", 1); + } + if (fd >= 0) + { + if (++incdepth > 10) + error(FATAL, "#include too deeply nested"); + setsource((char *)newstring((uchar *)iname, strlen(iname), 0), fd, NULL); + genline(); + } + else + { + trp->tp = trp->bp + 2; + error(ERROR, "Could not find include file %r", trp); + } + return; syntax: - error(ERROR, "Syntax error in #include"); + error(ERROR, "Syntax error in #include"); } /* * Generate a line directive for cursource */ -void -genline(void) +void genline(void) { - static Token ta = { UNCLASS }; - static Tokenrow tr = { &ta, &ta, &ta+1, 1 }; - uchar *p; + static Token ta = {UNCLASS}; + static Tokenrow tr = {&ta, &ta, &ta + 1, 1}; + uchar *p; - ta.t = p = (uchar*)outbufp; - strcpy((char*)p, "#line "); - p += sizeof("#line ")-1; - p = (uchar*)outnum((char*)p, cursource->line); - *p++ = ' '; *p++ = '"'; - if (cursource->filename[0]!='/' && wd[0]) { - strcpy((char*)p, wd); - p += strlen(wd); - *p++ = '/'; - } - strcpy((char*)p, cursource->filename); - p += strlen((char*)p); - *p++ = '"'; *p++ = '\n'; - ta.len = (char*)p-outbufp; - outbufp = (char*)p; - tr.tp = tr.bp; - puttokens(&tr); + ta.t = p = (uchar *)outbufp; + strcpy((char *)p, "#line "); + p += sizeof("#line ") - 1; + p = (uchar *)outnum((char *)p, cursource->line); + *p++ = ' '; + *p++ = '"'; + if (cursource->filename[0] != '/' && wd[0]) + { + strcpy((char *)p, wd); + p += strlen(wd); + *p++ = '/'; + } + strcpy((char *)p, cursource->filename); + p += strlen((char *)p); + *p++ = '"'; + *p++ = '\n'; + ta.len = (char *)p - outbufp; + outbufp = (char *)p; + tr.tp = tr.bp; + puttokens(&tr); } -void -setobjname(char *f) +void setobjname(char *f) { - int n = strlen(f); - objname = (char*)domalloc(n+5); - strcpy(objname,f); - if(objname[n-2]=='.'){ - strcpy(objname+n-1,"$O: "); - }else{ - strcpy(objname+n,"$O: "); - } + int n = strlen(f); + objname = (char *)domalloc(n + 5); + strcpy(objname, f); + if (objname[n - 2] == '.') + { + strcpy(objname + n - 1, "$O: "); + } + else + { + strcpy(objname + n, "$O: "); + } } diff --git a/src/tools/lcc/cpp/unix.c b/src/tools/lcc/cpp/unix.c index ff1496a..ee975aa 100644 --- a/src/tools/lcc/cpp/unix.c +++ b/src/tools/lcc/cpp/unix.c @@ -1,134 +1,141 @@ -#include #include +#include #include #include #include #include "cpp.h" -extern int lcc_getopt(int, char *const *, const char *); -extern char *optarg, rcsid[]; -extern int optind; -int verbose; -int Mflag; /* only print active include files */ -char *objname; /* "src.$O: " */ -int Cplusplus = 1; +extern int lcc_getopt(int, char *const *, const char *); +extern char *optarg, rcsid[]; +extern int optind; +int verbose; +int Mflag; /* only print active include files */ +char *objname; /* "src.$O: " */ +int Cplusplus = 1; -void -setup(int argc, char **argv) +void setup(int argc, char **argv) { - int c, fd, i; - char *fp, *dp; - Tokenrow tr; - extern void setup_kwtab(void); - uchar *includeDirs[ NINCLUDE ] = { 0 }; - int numIncludeDirs = 0; + int c, fd, i; + char *fp, *dp; + Tokenrow tr; + extern void setup_kwtab(void); + uchar *includeDirs[NINCLUDE] = {0}; + int numIncludeDirs = 0; - setup_kwtab(); - while ((c = lcc_getopt(argc, argv, "MNOVv+I:D:U:F:lg")) != -1) - switch (c) { - case 'N': - for (i=0; i", -1, optarg); - maketokenrow(3, &tr); - gettokens(&tr, 1); - doadefine(&tr, c); - unsetsource(); - break; - case 'M': - Mflag++; - break; - case 'v': - fprintf(stderr, "%s %s\n", argv[0], rcsid); - break; - case 'V': - verbose++; - break; - case '+': - Cplusplus++; - break; - default: - break; - } - dp = "."; - fp = ""; - fd = 0; - if (optind", -1, optarg); + maketokenrow(3, &tr); + gettokens(&tr, 1); + doadefine(&tr, c); + unsetsource(); + break; + case 'M': + Mflag++; + break; + case 'v': + fprintf(stderr, "%s %s\n", argv[0], rcsid); + break; + case 'V': + verbose++; + break; + case '+': + Cplusplus++; + break; + default: + break; + } + dp = "."; + fp = ""; + fd = 0; + if (optind < argc) + { + dp = basepath(argv[optind]); + fp = (char *)newstring((uchar *)argv[optind], strlen(argv[optind]), 0); + if ((fd = open(fp, 0)) <= 0) + error(FATAL, "Can't open input file %s", fp); + } + if (optind + 1 < argc) + { + int fdo; #ifdef WIN32 - fdo = creat(argv[optind+1], _S_IREAD | _S_IWRITE); + fdo = creat(argv[optind + 1], _S_IREAD | _S_IWRITE); #else - fdo = creat(argv[optind+1], 0666); + fdo = creat(argv[optind + 1], 0666); #endif - if (fdo<0) - error(FATAL, "Can't open output file %s", argv[optind+1]); - dup2(fdo, 1); - } - if(Mflag) - setobjname(fp); - includelist[NINCLUDE-1].always = 0; - includelist[NINCLUDE-1].file = dp; + if (fdo < 0) + error(FATAL, "Can't open output file %s", argv[optind + 1]); + dup2(fdo, 1); + } + if (Mflag) + setobjname(fp); + includelist[NINCLUDE - 1].always = 0; + includelist[NINCLUDE - 1].file = dp; - for( i = 0; i < numIncludeDirs; i++ ) - appendDirToIncludeList( (char *)includeDirs[ i ] ); + for (i = 0; i < numIncludeDirs; i++) + appendDirToIncludeList((char *)includeDirs[i]); - setsource(fp, fd, NULL); + setsource(fp, fd, NULL); } - -char *basepath( char *fname ) +char *basepath(char *fname) { - char *dp = "."; - char *p; - if ((p = strrchr(fname, '/')) != NULL) { - int dlen = p - fname; - dp = (char*)newstring((uchar*)fname, dlen+1, 0); - dp[dlen] = '\0'; - } + char *dp = "."; + char *p; + if ((p = strrchr(fname, '/')) != NULL) + { + int dlen = p - fname; + dp = (char *)newstring((uchar *)fname, dlen + 1, 0); + dp[dlen] = '\0'; + } - return dp; + return dp; } /* memmove is defined here because some vendors don't provide it at all and others do a terrible job (like calling malloc) */ // -- ouch, that hurts -- ln -#ifndef __APPLE__ /* always use the system memmove() on Mac OS X. --ryan. */ +/* always use the system memmove() on Mac OS X. --ryan. */ +#if !defined(__APPLE__) && !defined(_MSC_VER) #ifdef memmove #undef memmove #endif -void * -memmove(void *dp, const void *sp, size_t n) +void *memmove(void *dp, const void *sp, size_t n) { - unsigned char *cdp, *csp; + unsigned char *cdp, *csp; - if (n<=0) - return dp; - cdp = dp; - csp = (unsigned char *)sp; - if (cdp < csp) { - do { - *cdp++ = *csp++; - } while (--n); - } else { - cdp += n; - csp += n; - do { - *--cdp = *--csp; - } while (--n); - } - return dp; + if (n <= 0) + return dp; + cdp = dp; + csp = (unsigned char *)sp; + if (cdp < csp) + { + do + { + *cdp++ = *csp++; + } while (--n); + } + else + { + cdp += n; + csp += n; + do + { + *--cdp = *--csp; + } while (--n); + } + return dp; } #endif diff --git a/src/tools/lcc/etc/bytecode.c b/src/tools/lcc/etc/bytecode.c index a5855de..1bbee64 100644 --- a/src/tools/lcc/etc/bytecode.c +++ b/src/tools/lcc/etc/bytecode.c @@ -2,7 +2,7 @@ #include #include -#include "../../../qcommon/q_platform.h" +#include "qcommon/q_platform.h" #ifdef _WIN32 #define BINEXT ".exe" diff --git a/src/tools/lcc/etc/lcc.c b/src/tools/lcc/etc/lcc.c index aa3e789..9ebc47f 100644 --- a/src/tools/lcc/etc/lcc.c +++ b/src/tools/lcc/etc/lcc.c @@ -4,16 +4,16 @@ */ static char rcsid[] = "Id: dummy rcsid"; -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include #ifdef WIN32 -#include /* getpid() */ #include /* access() */ +#include /* getpid() */ #else #include #endif @@ -23,13 +23,13 @@ static char rcsid[] = "Id: dummy rcsid"; #endif typedef struct list *List; -struct list { /* circular list nodes: */ - char *str; /* option or file name */ - List link; /* next list element */ +struct list { /* circular list nodes: */ + char *str; /* option or file name */ + List link; /* next list element */ }; static void *alloc(int); -static List append(char *,List); +static List append(char *, List); extern char *basename(char *); static int callsys(char *[]); extern char *concat(char *, char *); @@ -57,228 +57,258 @@ extern char *tempname(char *); extern int getpid(void); #endif -extern char *cpp[], *include[], *com[], *as[],*ld[], inputs[], *suffixes[]; +extern char *cpp[], *include[], *com[], *as[], *ld[], inputs[], *suffixes[]; extern int option(char *); -static int errcnt; /* number of errors */ -static int Eflag; /* -E specified */ -static int Sflag = 1; /* -S specified */ //for Q3 we always generate asm -static int cflag; /* -c specified */ -static int verbose; /* incremented for each -v */ -static List llist[2]; /* loader files, flags */ -static List alist; /* assembler flags */ -static List clist; /* compiler flags */ -static List plist; /* preprocessor flags */ -static List ilist; /* list of additional includes from LCCINPUTS */ -static List rmlist; /* list of files to remove */ -static char *outfile; /* ld output file or -[cS] object file */ -static int ac; /* argument count */ -static char **av; /* argument vector */ -char *tempdir = TEMPDIR; /* directory for temporary files */ +static int errcnt; /* number of errors */ +static int Eflag; /* -E specified */ +static int Sflag = 1; /* -S specified */ // for Q3 we always generate asm +static int cflag; /* -c specified */ +static int verbose; /* incremented for each -v */ +static List llist[2]; /* loader files, flags */ +static List alist; /* assembler flags */ +static List clist; /* compiler flags */ +static List plist; /* preprocessor flags */ +static List ilist; /* list of additional includes from LCCINPUTS */ +static List rmlist; /* list of files to remove */ +static char *outfile; /* ld output file or -[cS] object file */ +static int ac; /* argument count */ +static char **av; /* argument vector */ +char *tempdir = TEMPDIR; /* directory for temporary files */ static char *progname; -static List lccinputs; /* list of input directories */ +static List lccinputs; /* list of input directories */ -extern void UpdatePaths( const char *lccBinary ); +extern void UpdatePaths(const char *lccBinary); -int main(int argc, char *argv[]) { - int i, j, nf; +int main(int argc, char *argv[]) +{ + int i, j, nf; - progname = argv[0]; + progname = argv[0]; - UpdatePaths( progname ); + UpdatePaths(progname); - ac = argc + 50; - av = alloc(ac*sizeof(char *)); - if (signal(SIGINT, SIG_IGN) != SIG_IGN) - signal(SIGINT, interrupt); - if (signal(SIGTERM, SIG_IGN) != SIG_IGN) - signal(SIGTERM, interrupt); + ac = argc + 50; + av = alloc(ac * sizeof(char *)); + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, interrupt); + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) + signal(SIGTERM, interrupt); #ifdef SIGHUP - if (signal(SIGHUP, SIG_IGN) != SIG_IGN) - signal(SIGHUP, interrupt); + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) + signal(SIGHUP, interrupt); #endif - if (getenv("TMP")) - tempdir = getenv("TMP"); - else if (getenv("TEMP")) - tempdir = getenv("TEMP"); - else if (getenv("TMPDIR")) - tempdir = getenv("TMPDIR"); - assert(tempdir); - i = strlen(tempdir); - for (; (i > 0 && tempdir[i-1] == '/') || tempdir[i-1] == '\\'; i--) - tempdir[i-1] = '\0'; - if (argc <= 1) { - help(); - exit(0); - } - plist = append("-D__LCC__", 0); - initinputs(); - if (getenv("LCCDIR")) - option(stringf("-lccdir=%s", getenv("LCCDIR"))); - for (nf = 0, i = j = 1; i < argc; i++) { - if (strcmp(argv[i], "-o") == 0) { - if (++i < argc) { - if (suffix(argv[i], suffixes, 2) >= 0) { - error("-o would overwrite %s", argv[i]); - exit(8); - } - outfile = argv[i]; - continue; - } else { - error("unrecognized option `%s'", argv[i-1]); - exit(8); - } - } else if (strcmp(argv[i], "-target") == 0) { - if (argv[i+1] && *argv[i+1] != '-') - i++; - continue; - } else if (*argv[i] == '-' && argv[i][1] != 'l') { - opt(argv[i]); - continue; - } else if (*argv[i] != '-' && suffix(argv[i], suffixes, 3) >= 0) - nf++; - argv[j++] = argv[i]; - } - if ((cflag || Sflag) && outfile && nf != 1) { - fprintf(stderr, "%s: -o %s ignored\n", progname, outfile); - outfile = 0; - } - argv[j] = 0; - for (i = 0; include[i]; i++) - plist = append(include[i], plist); - if (ilist) { - List b = ilist; - do { - b = b->link; - plist = append(b->str, plist); - } while (b != ilist); - } - ilist = 0; - for (i = 1; argv[i]; i++) - if (*argv[i] == '-') - opt(argv[i]); - else { - char *name = exists(argv[i]); - if (name) { - if (strcmp(name, argv[i]) != 0 - || (nf > 1 && suffix(name, suffixes, 3) >= 0)) - fprintf(stderr, "%s:\n", name); - filename(name, 0); - } else - error("can't find `%s'", argv[i]); - } - if (errcnt == 0 && !Eflag && !Sflag && !cflag && llist[1]) { - compose(ld, llist[0], llist[1], - append(outfile ? outfile : concat("a", first(suffixes[4])), 0)); - if (callsys(av)) - errcnt++; - } - rm(rmlist); - return errcnt ? EXIT_FAILURE : EXIT_SUCCESS; + if (getenv("TMP")) + tempdir = getenv("TMP"); + else if (getenv("TEMP")) + tempdir = getenv("TEMP"); + else if (getenv("TMPDIR")) + tempdir = getenv("TMPDIR"); + assert(tempdir); + i = strlen(tempdir); + for (; (i > 0 && tempdir[i - 1] == '/') || tempdir[i - 1] == '\\'; i--) + tempdir[i - 1] = '\0'; + if (argc <= 1) + { + help(); + exit(0); + } + plist = append("-D__LCC__", 0); + initinputs(); + if (getenv("LCCDIR")) + option(stringf("-lccdir=%s", getenv("LCCDIR"))); + for (nf = 0, i = j = 1; i < argc; i++) + { + if (strcmp(argv[i], "-o") == 0) + { + if (++i < argc) + { + if (suffix(argv[i], suffixes, 2) >= 0) + { + error("-o would overwrite %s", argv[i]); + exit(8); + } + outfile = argv[i]; + continue; + } + else + { + error("unrecognized option `%s'", argv[i - 1]); + exit(8); + } + } + else if (strcmp(argv[i], "-target") == 0) + { + if (argv[i + 1] && *argv[i + 1] != '-') + i++; + continue; + } + else if (*argv[i] == '-' && argv[i][1] != 'l') + { + opt(argv[i]); + continue; + } + else if (*argv[i] != '-' && suffix(argv[i], suffixes, 3) >= 0) + nf++; + argv[j++] = argv[i]; + } + if ((cflag || Sflag) && outfile && nf != 1) + { + fprintf(stderr, "%s: -o %s ignored\n", progname, outfile); + outfile = 0; + } + argv[j] = 0; + for (i = 0; include[i]; i++) + plist = append(include[i], plist); + if (ilist) + { + List b = ilist; + do + { + b = b->link; + plist = append(b->str, plist); + } while (b != ilist); + } + ilist = 0; + for (i = 1; argv[i]; i++) + if (*argv[i] == '-') + opt(argv[i]); + else + { + char *name = exists(argv[i]); + if (name) + { + if (strcmp(name, argv[i]) != 0 || (nf > 1 && suffix(name, suffixes, 3) >= 0)) + fprintf(stderr, "%s:\n", name); + filename(name, 0); + } + else + error("can't find `%s'", argv[i]); + } + if (errcnt == 0 && !Eflag && !Sflag && !cflag && llist[1]) + { + compose(ld, llist[0], llist[1], append(outfile ? outfile : concat("a", first(suffixes[4])), 0)); + if (callsys(av)) + errcnt++; + } + rm(rmlist); + return errcnt ? EXIT_FAILURE : EXIT_SUCCESS; } /* alloc - allocate n bytes or die */ -static void *alloc(int n) { - static char *avail, *limit; - - n = (n + sizeof(char *) - 1)&~(sizeof(char *) - 1); - if (n >= limit - avail) { - avail = malloc(n + 4*1024); - assert(avail); - limit = avail + n + 4*1024; - } - avail += n; - return avail - n; +static void *alloc(int n) +{ + static char *avail, *limit; + + n = (n + sizeof(char *) - 1) & ~(sizeof(char *) - 1); + if (n >= limit - avail) + { + avail = malloc(n + 4 * 1024); + assert(avail); + limit = avail + n + 4 * 1024; + } + avail += n; + return avail - n; } -/* append - append a node with string str onto list, return new list */ -static List append(char *str, List list) { - List p = alloc(sizeof *p); - - p->str = str; - if (list) { - p->link = list->link; - list->link = p; - } else - p->link = p; - return p; +/* append - append a node with string str onto list, return new list */ +static List append(char *str, List list) +{ + List p = alloc(sizeof *p); + + p->str = str; + if (list) + { + p->link = list->link; + list->link = p; + } + else + p->link = p; + return p; } /* basename - return base name for name, e.g. /usr/drh/foo.c => foo */ -char *basename(char *name) { - char *s, *b, *t = 0; - - for (b = s = name; *s; s++) - if (*s == '/' || *s == '\\') { - b = s + 1; - t = 0; - } else if (*s == '.') - t = s; - s = strsave(b); - if (t) - s[t-b] = 0; - return s; +char *basename(char *name) +{ + char *s, *b, *t = 0; + + for (b = s = name; *s; s++) + if (*s == '/' || *s == '\\') + { + b = s + 1; + t = 0; + } + else if (*s == '.') + t = s; + s = strsave(b); + if (t) + s[t - b] = 0; + return s; } #ifdef WIN32 #include -static char *escapeDoubleQuotes(const char *string) { - int stringLength = strlen(string); - int bufferSize = stringLength + 1; - int i, j; - char *newString; +static char *escapeDoubleQuotes(const char *string) +{ + int stringLength = strlen(string); + int bufferSize = stringLength + 1; + int i, j; + char *newString; - if (string == NULL) - return NULL; + if (string == NULL) + return NULL; - for (i = 0; i < stringLength; i++) { - if (string[i] == '"') - bufferSize++; - } + for (i = 0; i < stringLength; i++) + { + if (string[i] == '"') + bufferSize++; + } - newString = (char*)malloc(bufferSize); + newString = (char *)malloc(bufferSize); - if (newString == NULL) - return NULL; + if (newString == NULL) + return NULL; - for (i = 0, j = 0; i < stringLength; i++) { - if (string[i] == '"') - newString[j++] = '\\'; + for (i = 0, j = 0; i < stringLength; i++) + { + if (string[i] == '"') + newString[j++] = '\\'; - newString[j++] = string[i]; - } + newString[j++] = string[i]; + } - newString[j] = '\0'; + newString[j] = '\0'; - return newString; + return newString; } -static int spawn(const char *cmdname, char **argv) { - int argc = 0; - char **newArgv = argv; - int i; - intptr_t exitStatus; +static int spawn(const char *cmdname, char **argv) +{ + int argc = 0; + char **newArgv = argv; + int i; + intptr_t exitStatus; - // _spawnvp removes double quotes from arguments, so we - // have to escape them manually - while (*newArgv++ != NULL) - argc++; + // _spawnvp removes double quotes from arguments, so we + // have to escape them manually + while (*newArgv++ != NULL) + argc++; - newArgv = (char **)malloc(sizeof(char*) * (argc + 1)); + newArgv = (char **)malloc(sizeof(char *) * (argc + 1)); - for (i = 0; i < argc; i++) - newArgv[i] = escapeDoubleQuotes(argv[i]); + for (i = 0; i < argc; i++) + newArgv[i] = escapeDoubleQuotes(argv[i]); - newArgv[argc] = NULL; + newArgv[argc] = NULL; - exitStatus = _spawnvp(_P_WAIT, cmdname, (const char *const *)newArgv); + exitStatus = _spawnvp(_P_WAIT, cmdname, (const char *const *)newArgv); - for (i = 0; i < argc; i++) - free(newArgv[i]); + for (i = 0; i < argc; i++) + free(newArgv[i]); - free(newArgv); - return exitStatus; + free(newArgv); + return exitStatus; } #else @@ -289,569 +319,628 @@ extern int fork(void); #endif extern int wait(int *); -static int spawn(const char *cmdname, char **argv) { - int pid, n, status; - - switch (pid = fork()) { - case -1: - fprintf(stderr, "%s: no more processes\n", progname); - return 100; - case 0: - // TTimo removing hardcoded paths, searching in $PATH - execvp(cmdname, argv); - fprintf(stderr, "%s: ", progname); - perror(cmdname); - fflush(stdout); - exit(100); - } - while ((n = wait(&status)) != pid && n != -1) - ; - if (n == -1) - status = -1; - if (status&0377) { - fprintf(stderr, "%s: fatal error in %s\n", progname, cmdname); - status |= 0400; - } - return (status>>8)&0377; +static int spawn(const char *cmdname, char **argv) +{ + int pid, n, status; + + switch (pid = fork()) + { + case -1: + fprintf(stderr, "%s: no more processes\n", progname); + return 100; + case 0: + // TTimo removing hardcoded paths, searching in $PATH + execvp(cmdname, argv); + fprintf(stderr, "%s: ", progname); + perror(cmdname); + fflush(stdout); + exit(100); + } + while ((n = wait(&status)) != pid && n != -1) + ; + if (n == -1) + status = -1; + if (status & 0377) + { + fprintf(stderr, "%s: fatal error in %s\n", progname, cmdname); + status |= 0400; + } + return (status >> 8) & 0377; } #endif /* callsys - execute the command described by av[0...], return status */ -static int callsys(char **av) { - int i, status = 0; - static char **argv; - static int argc; - char *executable; - - for (i = 0; av[i] != NULL; i++) - ; - if (i + 1 > argc) { - argc = i + 1; - if (argv == NULL) - argv = malloc(argc*sizeof *argv); - else - argv = realloc(argv, argc*sizeof *argv); - assert(argv); - } - for (i = 0; status == 0 && av[i] != NULL; ) { - int j = 0; - char *s = NULL; - for ( ; av[i] != NULL && (s = strchr(av[i], '\n')) == NULL; i++) - argv[j++] = av[i]; - if (s != NULL) { - if (s > av[i]) - argv[j++] = stringf("%.*s", s - av[i], av[i]); - if (s[1] != '\0') - av[i] = s + 1; - else - i++; - } - argv[j] = NULL; - executable = strsave( argv[0] ); - argv[0] = stringf( "\"%s\"", argv[0] ); - if (verbose > 0) { - int k; - fprintf(stderr, "%s", argv[0]); - for (k = 1; argv[k] != NULL; k++) - fprintf(stderr, " %s", argv[k]); - fprintf(stderr, "\n"); - } - if (verbose < 2) - status = spawn(executable, argv); - if (status == -1) { - fprintf(stderr, "%s: ", progname); - perror(argv[0]); - } - } - return status; +static int callsys(char **av) +{ + int i, status = 0; + static char **argv; + static int argc; + char *executable; + + for (i = 0; av[i] != NULL; i++) + ; + if (i + 1 > argc) + { + argc = i + 1; + if (argv == NULL) + argv = malloc(argc * sizeof *argv); + else + argv = realloc(argv, argc * sizeof *argv); + assert(argv); + } + for (i = 0; status == 0 && av[i] != NULL;) + { + int j = 0; + char *s = NULL; + for (; av[i] != NULL && (s = strchr(av[i], '\n')) == NULL; i++) + argv[j++] = av[i]; + if (s != NULL) + { + if (s > av[i]) + argv[j++] = stringf("%.*s", s - av[i], av[i]); + if (s[1] != '\0') + av[i] = s + 1; + else + i++; + } + argv[j] = NULL; + executable = strsave(argv[0]); + argv[0] = stringf("\"%s\"", argv[0]); + if (verbose > 0) + { + int k; + fprintf(stderr, "%s", argv[0]); + for (k = 1; argv[k] != NULL; k++) + fprintf(stderr, " %s", argv[k]); + fprintf(stderr, "\n"); + } + if (verbose < 2) + status = spawn(executable, argv); + if (status == -1) + { + fprintf(stderr, "%s: ", progname); + perror(argv[0]); + } + } + return status; } /* concat - return concatenation of strings s1 and s2 */ -char *concat(char *s1, char *s2) { - int n = strlen(s1); - char *s = alloc(n + strlen(s2) + 1); - - strcpy(s, s1); - strcpy(s + n, s2); - return s; +char *concat(char *s1, char *s2) +{ + int n = strlen(s1); + char *s = alloc(n + strlen(s2) + 1); + + strcpy(s, s1); + strcpy(s + n, s2); + return s; } /* compile - compile src into dst, return status */ -static int compile(char *src, char *dst) { - compose(com, clist, append(src, 0), append(dst, 0)); - return callsys(av); +static int compile(char *src, char *dst) +{ + compose(com, clist, append(src, 0), append(dst, 0)); + return callsys(av); } /* compose - compose cmd into av substituting a, b, c for $1, $2, $3, resp. */ -static void compose(char *cmd[], List a, List b, List c) { - int i, j; - List lists[3]; - - lists[0] = a; - lists[1] = b; - lists[2] = c; - for (i = j = 0; cmd[i]; i++) { - char *s = strchr(cmd[i], '$'); - if (s && isdigit(s[1])) { - int k = s[1] - '0'; - assert(k >=1 && k <= 3); - if ((b = lists[k-1])) { - b = b->link; - av[j] = alloc(strlen(cmd[i]) + strlen(b->str) - 1); - strncpy(av[j], cmd[i], s - cmd[i]); - av[j][s-cmd[i]] = '\0'; - strcat(av[j], b->str); - strcat(av[j++], s + 2); - while (b != lists[k-1]) { - b = b->link; - assert(j < ac); - av[j++] = b->str; - }; - } - } else if (*cmd[i]) { - assert(j < ac); - av[j++] = cmd[i]; - } - } - av[j] = NULL; +static void compose(char *cmd[], List a, List b, List c) +{ + int i, j; + List lists[3]; + + lists[0] = a; + lists[1] = b; + lists[2] = c; + for (i = j = 0; cmd[i]; i++) + { + char *s = strchr(cmd[i], '$'); + if (s && isdigit(s[1])) + { + int k = s[1] - '0'; + assert(k >= 1 && k <= 3); + if ((b = lists[k - 1])) + { + b = b->link; + av[j] = alloc(strlen(cmd[i]) + strlen(b->str) - 1); + strncpy(av[j], cmd[i], s - cmd[i]); + av[j][s - cmd[i]] = '\0'; + strcat(av[j], b->str); + strcat(av[j++], s + 2); + while (b != lists[k - 1]) + { + b = b->link; + assert(j < ac); + av[j++] = b->str; + }; + } + } + else if (*cmd[i]) + { + assert(j < ac); + av[j++] = cmd[i]; + } + } + av[j] = NULL; } /* error - issue error msg according to fmt, bump error count */ -static void error(char *fmt, char *msg) { - fprintf(stderr, "%s: ", progname); - fprintf(stderr, fmt, msg); - fprintf(stderr, "\n"); - errcnt++; +static void error(char *fmt, char *msg) +{ + fprintf(stderr, "%s: ", progname); + fprintf(stderr, fmt, msg); + fprintf(stderr, "\n"); + errcnt++; } /* exists - if `name' readable return its path name or return null */ -static char *exists(char *name) { - List b; - - if ( (name[0] == '/' || name[0] == '\\' || name[2] == ':') - && access(name, 4) == 0) - return name; - if (!(name[0] == '/' || name[0] == '\\' || name[2] == ':') - && (b = lccinputs)) - do { - b = b->link; - if (b->str[0]) { - char buf[1024]; - sprintf(buf, "%s/%s", b->str, name); - if (access(buf, 4) == 0) - return strsave(buf); - } else if (access(name, 4) == 0) - return name; - } while (b != lccinputs); - if (verbose > 1) - return name; - return 0; +static char *exists(char *name) +{ + List b; + + if ((name[0] == '/' || name[0] == '\\' || name[2] == ':') && access(name, 4) == 0) + return name; + if (!(name[0] == '/' || name[0] == '\\' || name[2] == ':') && (b = lccinputs)) + do + { + b = b->link; + if (b->str[0]) + { + char buf[1024]; + sprintf(buf, "%s/%s", b->str, name); + if (access(buf, 4) == 0) + return strsave(buf); + } + else if (access(name, 4) == 0) + return name; + } while (b != lccinputs); + if (verbose > 1) + return name; + return 0; } /* first - return first component in semicolon separated list */ -static char *first(char *list) { - char *s = strchr(list, ';'); - - if (s) { - char buf[1024]; - strncpy(buf, list, s-list); - buf[s-list] = '\0'; - return strsave(buf); - } else - return list; +static char *first(char *list) +{ + char *s = strchr(list, ';'); + + if (s) + { + char buf[1024]; + strncpy(buf, list, s - list); + buf[s - list] = '\0'; + return strsave(buf); + } + else + return list; } /* filename - process file name argument `name', return status */ -static int filename(char *name, char *base) { - int status = 0; - static char *stemp, *itemp; - - if (base == 0) - base = basename(name); - switch (suffix(name, suffixes, 4)) { - case 0: /* C source files */ - compose(cpp, plist, append(name, 0), 0); - if (Eflag) { - status = callsys(av); - break; - } - if (itemp == NULL) - itemp = tempname(first(suffixes[1])); - compose(cpp, plist, append(name, 0), append(itemp, 0)); - status = callsys(av); - if (status == 0) - return filename(itemp, base); - break; - case 1: /* preprocessed source files */ - if (Eflag) - break; - if (Sflag) - status = compile(name, outfile ? outfile : concat(base, first(suffixes[2]))); - else if ((status = compile(name, stemp?stemp:(stemp=tempname(first(suffixes[2]))))) == 0) - return filename(stemp, base); - break; - case 2: /* assembly language files */ - if (Eflag) - break; - if (!Sflag) { - char *ofile; - if (cflag && outfile) - ofile = outfile; - else if (cflag) - ofile = concat(base, first(suffixes[3])); - else - ofile = tempname(first(suffixes[3])); - compose(as, alist, append(name, 0), append(ofile, 0)); - status = callsys(av); - if (!find(ofile, llist[1])) - llist[1] = append(ofile, llist[1]); - } - break; - case 3: /* object files */ - if (!find(name, llist[1])) - llist[1] = append(name, llist[1]); - break; - default: - if (Eflag) { - compose(cpp, plist, append(name, 0), 0); - status = callsys(av); - } - llist[1] = append(name, llist[1]); - break; - } - if (status) - errcnt++; - return status; +static int filename(char *name, char *base) +{ + int status = 0; + static char *stemp, *itemp; + + if (base == 0) + base = basename(name); + switch (suffix(name, suffixes, 4)) + { + case 0: /* C source files */ + compose(cpp, plist, append(name, 0), 0); + if (Eflag) + { + status = callsys(av); + break; + } + if (itemp == NULL) + itemp = tempname(first(suffixes[1])); + compose(cpp, plist, append(name, 0), append(itemp, 0)); + status = callsys(av); + if (status == 0) + return filename(itemp, base); + break; + case 1: /* preprocessed source files */ + if (Eflag) + break; + if (Sflag) + status = compile(name, outfile ? outfile : concat(base, first(suffixes[2]))); + else if ((status = compile(name, stemp ? stemp : (stemp = tempname(first(suffixes[2]))))) == 0) + return filename(stemp, base); + break; + case 2: /* assembly language files */ + if (Eflag) + break; + if (!Sflag) + { + char *ofile; + if (cflag && outfile) + ofile = outfile; + else if (cflag) + ofile = concat(base, first(suffixes[3])); + else + ofile = tempname(first(suffixes[3])); + compose(as, alist, append(name, 0), append(ofile, 0)); + status = callsys(av); + if (!find(ofile, llist[1])) + llist[1] = append(ofile, llist[1]); + } + break; + case 3: /* object files */ + if (!find(name, llist[1])) + llist[1] = append(name, llist[1]); + break; + default: + if (Eflag) + { + compose(cpp, plist, append(name, 0), 0); + status = callsys(av); + } + llist[1] = append(name, llist[1]); + break; + } + if (status) + errcnt++; + return status; } /* find - find 1st occurrence of str in list, return list node or 0 */ -static List find(char *str, List list) { - List b; - - if ((b = list)) - do { - if (strcmp(str, b->str) == 0) - return b; - } while ((b = b->link) != list); - return 0; +static List find(char *str, List list) +{ + List b; + + if ((b = list)) + do + { + if (strcmp(str, b->str) == 0) + return b; + } while ((b = b->link) != list); + return 0; } /* help - print help message */ -static void help(void) { - static char *msgs[] = { -"", " [ option | file ]...\n", -" except for -l, options are processed left-to-right before files\n", -" unrecognized options are taken to be linker options\n", -"-A warn about nonANSI usage; 2nd -A warns more\n", -"-b emit expression-level profiling code; see bprint(1)\n", +static void help(void) +{ + static char *msgs[] = {"", " [ option | file ]...\n", + " except for -l, options are processed left-to-right before files\n", + " unrecognized options are taken to be linker options\n", + "-A warn about nonANSI usage; 2nd -A warns more\n", + "-b emit expression-level profiling code; see bprint(1)\n", #ifdef sparc -"-Bstatic -Bdynamic specify static or dynamic libraries\n", + "-Bstatic -Bdynamic specify static or dynamic libraries\n", #endif -"-Bdir/ use the compiler named `dir/rcc'\n", -"-c compile only\n", -"-dn set switch statement density to `n'\n", -"-Dname -Dname=def define the preprocessor symbol `name'\n", -"-E run only the preprocessor on the named C programs and unsuffixed files\n", -"-g produce symbol table information for debuggers\n", -"-help or -? print this message\n", -"-Idir add `dir' to the beginning of the list of #include directories\n", -"-lx search library `x'\n", -"-N do not search the standard directories for #include files\n", -"-n emit code to check for dereferencing zero pointers\n", -"-O is ignored\n", -"-o file leave the output in `file'\n", -"-P print ANSI-style declarations for globals\n", -"-p -pg emit profiling code; see prof(1) and gprof(1)\n", -"-S compile to assembly language\n", + "-Bdir/ use the compiler named `dir/rcc'\n", "-c compile only\n", + "-dn set switch statement density to `n'\n", + "-Dname -Dname=def define the preprocessor symbol `name'\n", + "-E run only the preprocessor on the named C programs and unsuffixed files\n", + "-g produce symbol table information for debuggers\n", "-help or -? print this message\n", + "-Idir add `dir' to the beginning of the list of #include directories\n", "-lx search library `x'\n", + "-N do not search the standard directories for #include files\n", + "-n emit code to check for dereferencing zero pointers\n", "-O is ignored\n", + "-o file leave the output in `file'\n", "-P print ANSI-style declarations for globals\n", + "-p -pg emit profiling code; see prof(1) and gprof(1)\n", "-S compile to assembly language\n", #ifdef linux -"-static specify static libraries (default is dynamic)\n", + "-static specify static libraries (default is dynamic)\n", #endif -"-t -tname emit function tracing calls to printf or to `name'\n", -"-target name is ignored\n", -"-tempdir=dir place temporary files in `dir/'", "\n" -"-Uname undefine the preprocessor symbol `name'\n", -"-v show commands as they are executed; 2nd -v suppresses execution\n", -"-w suppress warnings\n", -"-Woarg specify system-specific `arg'\n", -"-W[pfal]arg pass `arg' to the preprocessor, compiler, assembler, or linker\n", - 0 }; - int i; - char *s; - - msgs[0] = progname; - for (i = 0; msgs[i]; i++) { - fprintf(stderr, "%s", msgs[i]); - if (strncmp("-tempdir", msgs[i], 8) == 0 && tempdir) - fprintf(stderr, "; default=%s", tempdir); - } -#define xx(v) if ((s = getenv(#v))) fprintf(stderr, #v "=%s\n", s) - xx(LCCINPUTS); - xx(LCCDIR); + "-t -tname emit function tracing calls to printf or to `name'\n", "-target name is ignored\n", + "-tempdir=dir place temporary files in `dir/'", + "\n" + "-Uname undefine the preprocessor symbol `name'\n", + "-v show commands as they are executed; 2nd -v suppresses execution\n", "-w suppress warnings\n", + "-Woarg specify system-specific `arg'\n", + "-W[pfal]arg pass `arg' to the preprocessor, compiler, assembler, or linker\n", 0}; + int i; + char *s; + + msgs[0] = progname; + for (i = 0; msgs[i]; i++) + { + fprintf(stderr, "%s", msgs[i]); + if (strncmp("-tempdir", msgs[i], 8) == 0 && tempdir) + fprintf(stderr, "; default=%s", tempdir); + } +#define xx(v) \ + if ((s = getenv(#v))) \ + fprintf(stderr, #v "=%s\n", s) + xx(LCCINPUTS); + xx(LCCDIR); #undef xx } /* initinputs - if LCCINPUTS or include is defined, use them to initialize various lists */ -static void initinputs(void) { - char *s = getenv("LCCINPUTS"); - List b; - - if (s == 0 || (s = inputs)[0] == 0) - s = "."; - if (s) { - lccinputs = path2list(s); - if ((b = lccinputs)) - do { - b = b->link; - if (strcmp(b->str, ".") != 0) { - ilist = append(concat("-I", b->str), ilist); - if (strstr(com[1], "win32") == NULL) - llist[0] = append(concat("-L", b->str), llist[0]); - } else - b->str = ""; - } while (b != lccinputs); - } +static void initinputs(void) +{ + char *s = getenv("LCCINPUTS"); + List b; + + if (s == 0 || (s = inputs)[0] == 0) + s = "."; + if (s) + { + lccinputs = path2list(s); + if ((b = lccinputs)) + do + { + b = b->link; + if (strcmp(b->str, ".") != 0) + { + ilist = append(concat("-I", b->str), ilist); + if (strstr(com[1], "win32") == NULL) + llist[0] = append(concat("-L", b->str), llist[0]); + } + else + b->str = ""; + } while (b != lccinputs); + } } /* interrupt - catch interrupt signals */ -static void interrupt(int n) { - rm(rmlist); - exit(n = 100); +static void interrupt(int n) +{ + rm(rmlist); + exit(n = 100); } /* opt - process option in arg */ -static void opt(char *arg) { - switch (arg[1]) { /* multi-character options */ - case 'W': /* -Wxarg */ - if (arg[2] && arg[3]) - switch (arg[2]) { - case 'o': - if (option(&arg[3])) - return; - break; - case 'p': - plist = append(&arg[3], plist); - return; - case 'f': - if (strcmp(&arg[3], "-C") || option("-b")) { - clist = append(&arg[3], clist); - return; - } - break; /* and fall thru */ - case 'a': - alist = append(&arg[3], alist); - return; - case 'l': - llist[0] = append(&arg[3], llist[0]); - return; - } - fprintf(stderr, "%s: %s ignored\n", progname, arg); - return; - case 'd': /* -dn */ - arg[1] = 's'; - clist = append(arg, clist); - return; - case 't': /* -t -tname -tempdir=dir */ - if (strncmp(arg, "-tempdir=", 9) == 0) - tempdir = arg + 9; - else - clist = append(arg, clist); - return; - case 'p': /* -p -pg */ - if (option(arg)) - clist = append(arg, clist); - else - fprintf(stderr, "%s: %s ignored\n", progname, arg); - return; - case 'D': /* -Dname -Dname=def */ - case 'U': /* -Uname */ - case 'I': /* -Idir */ - plist = append(arg, plist); - return; - case 'B': /* -Bdir -Bstatic -Bdynamic */ +static void opt(char *arg) +{ + switch (arg[1]) + { /* multi-character options */ + case 'W': /* -Wxarg */ + if (arg[2] && arg[3]) + switch (arg[2]) + { + case 'o': + if (option(&arg[3])) + return; + break; + case 'p': + plist = append(&arg[3], plist); + return; + case 'f': + if (strcmp(&arg[3], "-C") || option("-b")) + { + clist = append(&arg[3], clist); + return; + } + break; /* and fall thru */ + case 'a': + alist = append(&arg[3], alist); + return; + case 'l': + llist[0] = append(&arg[3], llist[0]); + return; + } + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'd': /* -dn */ + arg[1] = 's'; + clist = append(arg, clist); + return; + case 't': /* -t -tname -tempdir=dir */ + if (strncmp(arg, "-tempdir=", 9) == 0) + tempdir = arg + 9; + else + clist = append(arg, clist); + return; + case 'p': /* -p -pg */ + if (option(arg)) + clist = append(arg, clist); + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'D': /* -Dname -Dname=def */ + case 'U': /* -Uname */ + case 'I': /* -Idir */ + plist = append(arg, plist); + return; + case 'B': /* -Bdir -Bstatic -Bdynamic */ #ifdef sparc - if (strcmp(arg, "-Bstatic") == 0 || strcmp(arg, "-Bdynamic") == 0) - llist[1] = append(arg, llist[1]); - else -#endif - { - static char *path; - if (path) - error("-B overwrites earlier option", 0); - path = arg + 2; - if (strstr(com[1], "win32") != NULL) - com[0] = concat(replace(path, '/', '\\'), concat("rcc", first(suffixes[4]))); - else - com[0] = concat(path, "rcc"); - if (path[0] == 0) - error("missing directory in -B option", 0); - } - return; - case 'h': - if (strcmp(arg, "-help") == 0) { - static int printed = 0; - case '?': - if (!printed) - help(); - printed = 1; - return; - } + if (strcmp(arg, "-Bstatic") == 0 || strcmp(arg, "-Bdynamic") == 0) + llist[1] = append(arg, llist[1]); + else +#endif + { + static char *path; + if (path) + error("-B overwrites earlier option", 0); + path = arg + 2; + if (strstr(com[1], "win32") != NULL) + com[0] = concat(replace(path, '/', '\\'), concat("rcc", first(suffixes[4]))); + else + com[0] = concat(path, "rcc"); + if (path[0] == 0) + error("missing directory in -B option", 0); + } + return; + case 'h': + if (strcmp(arg, "-help") == 0) + { + static int printed = 0; + case '?': + if (!printed) + help(); + printed = 1; + return; + } #ifdef linux - case 's': - if (strcmp(arg,"-static") == 0) { - if (!option(arg)) - fprintf(stderr, "%s: %s ignored\n", progname, arg); - return; - } -#endif - } - if (arg[2] == 0) - switch (arg[1]) { /* single-character options */ - case 'S': - Sflag++; - return; - case 'O': - fprintf(stderr, "%s: %s ignored\n", progname, arg); - return; - case 'A': case 'n': case 'w': case 'P': - clist = append(arg, clist); - return; - case 'g': case 'b': - if (option(arg)) - clist = append(arg[1] == 'g' ? "-g2" : arg, clist); - else - fprintf(stderr, "%s: %s ignored\n", progname, arg); - return; - case 'G': - if (option(arg)) { - clist = append("-g3", clist); - llist[0] = append("-N", llist[0]); - } else - fprintf(stderr, "%s: %s ignored\n", progname, arg); - return; - case 'E': - Eflag++; - return; - case 'c': - cflag++; - return; - case 'N': - if (strcmp(basename(cpp[0]), "gcc-cpp") == 0) - plist = append("-nostdinc", plist); - include[0] = 0; - ilist = 0; - return; - case 'v': - if (verbose++ == 0) { - if (strcmp(basename(cpp[0]), "gcc-cpp") == 0) - plist = append(arg, plist); - clist = append(arg, clist); - fprintf(stderr, "%s %s\n", progname, rcsid); - } - return; - } - if (cflag || Sflag || Eflag) - fprintf(stderr, "%s: %s ignored\n", progname, arg); - else - llist[1] = append(arg, llist[1]); + case 's': + if (strcmp(arg, "-static") == 0) + { + if (!option(arg)) + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + } +#endif + } + if (arg[2] == 0) + switch (arg[1]) + { /* single-character options */ + case 'S': + Sflag++; + return; + case 'O': + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'A': + case 'n': + case 'w': + case 'P': + clist = append(arg, clist); + return; + case 'g': + case 'b': + if (option(arg)) + clist = append(arg[1] == 'g' ? "-g2" : arg, clist); + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'G': + if (option(arg)) + { + clist = append("-g3", clist); + llist[0] = append("-N", llist[0]); + } + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'E': + Eflag++; + return; + case 'c': + cflag++; + return; + case 'N': + if (strcmp(basename(cpp[0]), "gcc-cpp") == 0) + plist = append("-nostdinc", plist); + include[0] = 0; + ilist = 0; + return; + case 'v': + if (verbose++ == 0) + { + if (strcmp(basename(cpp[0]), "gcc-cpp") == 0) + plist = append(arg, plist); + clist = append(arg, clist); + fprintf(stderr, "%s %s\n", progname, rcsid); + } + return; + } + if (cflag || Sflag || Eflag) + fprintf(stderr, "%s: %s ignored\n", progname, arg); + else + llist[1] = append(arg, llist[1]); } /* path2list - convert a colon- or semicolon-separated list to a list */ -static List path2list(const char *path) { - List list = NULL; - char sep = ':'; - - if (path == NULL) - return NULL; - if (strchr(path, ';')) - sep = ';'; - while (*path) { - char *p, buf[512]; - if ((p = strchr(path, sep))) { - assert(p - path < sizeof buf); - strncpy(buf, path, p - path); - buf[p-path] = '\0'; - } else { - assert(strlen(path) < sizeof buf); - strcpy(buf, path); - } - if (!find(buf, list)) - list = append(strsave(buf), list); - if (p == 0) - break; - path = p + 1; - } - return list; +static List path2list(const char *path) +{ + List list = NULL; + char sep = ':'; + + if (path == NULL) + return NULL; + if (strchr(path, ';')) + sep = ';'; + while (*path) + { + char *p, buf[512]; + if ((p = strchr(path, sep))) + { + assert(p - path < sizeof buf); + strncpy(buf, path, p - path); + buf[p - path] = '\0'; + } + else + { + assert(strlen(path) < sizeof buf); + strcpy(buf, path); + } + if (!find(buf, list)) + list = append(strsave(buf), list); + if (p == 0) + break; + path = p + 1; + } + return list; } /* replace - copy str, then replace occurrences of from with to, return the copy */ -char *replace(const char *str, int from, int to) { - char *s = strsave(str), *p = s; +char *replace(const char *str, int from, int to) +{ + char *s = strsave(str), *p = s; - for ( ; (p = strchr(p, from)) != NULL; p++) - *p = to; - return s; + for (; (p = strchr(p, from)) != NULL; p++) + *p = to; + return s; } /* rm - remove files in list */ -static void rm(List list) { - if (list) { - List b = list; - if (verbose) - fprintf(stderr, "rm"); - do { - if (verbose) - fprintf(stderr, " %s", b->str); - if (verbose < 2) - remove(b->str); - } while ((b = b->link) != list); - if (verbose) - fprintf(stderr, "\n"); - } +static void rm(List list) +{ + if (list) + { + List b = list; + if (verbose) + fprintf(stderr, "rm"); + do + { + if (verbose) + fprintf(stderr, " %s", b->str); + if (verbose < 2) + remove(b->str); + } while ((b = b->link) != list); + if (verbose) + fprintf(stderr, "\n"); + } } /* strsave - return a saved copy of string str */ -char *strsave(const char *str) { - return strcpy(alloc(strlen(str)+1), str); -} +char *strsave(const char *str) { return strcpy(alloc(strlen(str) + 1), str); } /* stringf - format and return a string */ -char *stringf(const char *fmt, ...) { - char buf[1024]; - va_list ap; - - va_start(ap, fmt); - vsprintf(buf, fmt, ap); - va_end(ap); - return strsave(buf); +char *stringf(const char *fmt, ...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + return strsave(buf); } /* suffix - if one of tails[0..n-1] holds a proper suffix of name, return its index */ -int suffix(char *name, char *tails[], int n) { - int i, len = strlen(name); - - for (i = 0; i < n; i++) { - char *s = tails[i], *t; - for ( ; (t = strchr(s, ';')); s = t + 1) { - int m = t - s; - if (len > m && strncmp(&name[len-m], s, m) == 0) - return i; - } - if (*s) { - int m = strlen(s); - if (len > m && strncmp(&name[len-m], s, m) == 0) - return i; - } - } - return -1; +int suffix(char *name, char *tails[], int n) +{ + int i, len = strlen(name); + + for (i = 0; i < n; i++) + { + char *s = tails[i], *t; + for (; (t = strchr(s, ';')); s = t + 1) + { + int m = t - s; + if (len > m && strncmp(&name[len - m], s, m) == 0) + return i; + } + if (*s) + { + int m = strlen(s); + if (len > m && strncmp(&name[len - m], s, m) == 0) + return i; + } + } + return -1; } /* tempname - generate a temporary file name in tempdir with given suffix */ -char *tempname(char *suffix) { - static int n; - char *name = stringf("%s/lcc%d%d%s", tempdir, getpid(), n++, suffix); - - if (strstr(com[1], "win32") != NULL) - name = replace(name, '/', '\\'); - rmlist = append(name, rmlist); - return name; +char *tempname(char *suffix) +{ + static int n; + char *name = stringf("%s/lcc%d%d%s", tempdir, getpid(), n++, suffix); + + if (strstr(com[1], "win32") != NULL) + name = replace(name, '/', '\\'); + rmlist = append(name, rmlist); + return name; } diff --git a/src/tools/lcc/lburg/gram.c b/src/tools/lcc/lburg/gram.c index f6ee9f9..94fd3b3 100644 --- a/src/tools/lcc/lburg/gram.c +++ b/src/tools/lcc/lburg/gram.c @@ -1,8 +1,8 @@ -/* A Bison parser, made by GNU Bison 3.0.4. */ +/* A Bison parser, made by GNU Bison 3.0.2. */ /* Bison implementation for Yacc-like parsers in C - Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, 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 @@ -44,7 +44,7 @@ #define YYBISON 1 /* Bison version. */ -#define YYBISON_VERSION "3.0.4" +#define YYBISON_VERSION "3.0.2" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" @@ -62,11 +62,11 @@ /* Copy the first part of user declarations. */ -#line 1 "src/tools/lcc/lburg/gram.y" /* yacc.c:339 */ +#line 1 "code/tools/lcc/lburg/gram.y" /* yacc.c:339 */ #include #include "lburg.h" -static char rcsid[] = "$Id: gram.y 145 2001-10-17 21:53:10Z timo $"; +//static char rcsid[] = "$Id: gram.y 145 2001-10-17 21:53:10Z timo $"; /*lint -e616 -e527 -e652 -esym(552,yynerrs) -esym(563,yynewstate,yyerrlab) */ static int yylineno = 0; @@ -122,10 +122,10 @@ extern int yydebug; /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED - +typedef union YYSTYPE YYSTYPE; union YYSTYPE { -#line 8 "src/tools/lcc/lburg/gram.y" /* yacc.c:355 */ +#line 8 "code/tools/lcc/lburg/gram.y" /* yacc.c:355 */ int n; char *string; @@ -133,8 +133,6 @@ union YYSTYPE #line 135 "y.tab.c" /* yacc.c:355 */ }; - -typedef union YYSTYPE YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif @@ -143,12 +141,13 @@ typedef union YYSTYPE YYSTYPE; extern YYSTYPE yylval; int yyparse (void); +int yylex(void); /* Copy the second part of user declarations. */ -#line 152 "y.tab.c" /* yacc.c:358 */ +#line 150 "y.tab.c" /* yacc.c:358 */ #ifdef short # undef short @@ -1236,82 +1235,82 @@ yyreduce: switch (yyn) { case 2: -#line 22 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 22 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { yylineno = 0; } -#line 1242 "y.tab.c" /* yacc.c:1646 */ +#line 1240 "y.tab.c" /* yacc.c:1646 */ break; case 3: -#line 23 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 23 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { yylineno = 0; } -#line 1248 "y.tab.c" /* yacc.c:1646 */ +#line 1246 "y.tab.c" /* yacc.c:1646 */ break; case 7: -#line 31 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 31 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { if (nonterm((yyvsp[-1].string))->number != 1) yyerror("redeclaration of the start symbol\n"); } -#line 1257 "y.tab.c" /* yacc.c:1646 */ +#line 1255 "y.tab.c" /* yacc.c:1646 */ break; case 9: -#line 36 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 36 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { yyerrok; } -#line 1263 "y.tab.c" /* yacc.c:1646 */ +#line 1261 "y.tab.c" /* yacc.c:1646 */ break; case 11: -#line 40 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 40 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { term((yyvsp[-2].string), (yyvsp[0].n)); } -#line 1269 "y.tab.c" /* yacc.c:1646 */ +#line 1267 "y.tab.c" /* yacc.c:1646 */ break; case 13: -#line 44 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 44 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { rule((yyvsp[-5].string), (yyvsp[-3].tree), (yyvsp[-2].string), (yyvsp[-1].string)); } -#line 1275 "y.tab.c" /* yacc.c:1646 */ +#line 1273 "y.tab.c" /* yacc.c:1646 */ break; case 15: -#line 46 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 46 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { yyerrok; } -#line 1281 "y.tab.c" /* yacc.c:1646 */ +#line 1279 "y.tab.c" /* yacc.c:1646 */ break; case 16: -#line 49 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 49 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { nonterm((yyval.string) = (yyvsp[0].string)); } -#line 1287 "y.tab.c" /* yacc.c:1646 */ +#line 1285 "y.tab.c" /* yacc.c:1646 */ break; case 17: -#line 52 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 52 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { (yyval.tree) = tree((yyvsp[0].string), 0, 0); } -#line 1293 "y.tab.c" /* yacc.c:1646 */ +#line 1291 "y.tab.c" /* yacc.c:1646 */ break; case 18: -#line 53 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 53 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { (yyval.tree) = tree((yyvsp[-3].string), (yyvsp[-1].tree), 0); } -#line 1299 "y.tab.c" /* yacc.c:1646 */ +#line 1297 "y.tab.c" /* yacc.c:1646 */ break; case 19: -#line 54 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 54 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { (yyval.tree) = tree((yyvsp[-5].string), (yyvsp[-3].tree), (yyvsp[-1].tree)); } -#line 1305 "y.tab.c" /* yacc.c:1646 */ +#line 1303 "y.tab.c" /* yacc.c:1646 */ break; case 20: -#line 57 "src/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ +#line 57 "code/tools/lcc/lburg/gram.y" /* yacc.c:1646 */ { if (*(yyvsp[0].string) == 0) (yyval.string) = "0"; } -#line 1311 "y.tab.c" /* yacc.c:1646 */ +#line 1309 "y.tab.c" /* yacc.c:1646 */ break; -#line 1315 "y.tab.c" /* yacc.c:1646 */ +#line 1313 "y.tab.c" /* yacc.c:1646 */ default: break; } /* User semantic actions sometimes alter yychar, and that requires @@ -1539,7 +1538,7 @@ yyreturn: #endif return yyresult; } -#line 59 "src/tools/lcc/lburg/gram.y" /* yacc.c:1906 */ +#line 59 "code/tools/lcc/lburg/gram.y" /* yacc.c:1906 */ #include #include @@ -1683,4 +1682,5 @@ void yywarn(char *fmt, ...) { fprintf(stderr, "line %d: ", yylineno); fprintf(stderr, "warning: "); vfprintf(stderr, fmt, ap); + va_end(ap); } diff --git a/src/tools/lcc/lburg/gram.y b/src/tools/lcc/lburg/gram.y index 1ecd8a9..c0a9c94 100644 --- a/src/tools/lcc/lburg/gram.y +++ b/src/tools/lcc/lburg/gram.y @@ -199,4 +199,5 @@ void yywarn(char *fmt, ...) { fprintf(stderr, "line %d: ", yylineno); fprintf(stderr, "warning: "); vfprintf(stderr, fmt, ap); + va_end(ap); } diff --git a/src/tools/lcc/src/dag.c b/src/tools/lcc/src/dag.c index 420cbe7..ad0ea1c 100644 --- a/src/tools/lcc/src/dag.c +++ b/src/tools/lcc/src/dag.c @@ -1,15 +1,13 @@ #include "c.h" - -#define iscall(op) (generic(op) == CALL \ - || (IR->mulops_calls \ - && (generic(op)==DIV||generic(op)==MOD||generic(op)==MUL) \ - && ( optype(op)==U || optype(op)==I))) +#define iscall(op) \ + (generic(op) == CALL || (IR->mulops_calls && (generic(op) == DIV || generic(op) == MOD || generic(op) == MUL) && \ + (optype(op) == U || optype(op) == I))) static Node forest; static struct dag { - struct node node; - struct dag *hlink; -} *buckets[16]; + struct node node; + struct dag *hlink; +} * buckets[16]; int nodecount; static Tree firstarg; int assignargs = 1; @@ -35,702 +33,884 @@ static void typestab(Symbol, void *); static Node undag(Node); static Node visit(Node, int); static void unlist(void); -void walk(Tree tp, int tlab, int flab) { - listnodes(tp, tlab, flab); - if (forest) { - Node list = forest->link; - forest->link = NULL; - if (!IR->wants_dag) - list = undag(list); - code(Gen)->u.forest = list; - forest = NULL; - } - reset(); - deallocate(STMT); +void walk(Tree tp, int tlab, int flab) +{ + listnodes(tp, tlab, flab); + if (forest) + { + Node list = forest->link; + forest->link = NULL; + if (!IR->wants_dag) list = undag(list); + code(Gen)->u.forest = list; + forest = NULL; + } + reset(); + deallocate(STMT); } -static Node node(int op, Node l, Node r, Symbol sym) { - int i; - struct dag *p; +static Node node(int op, Node l, Node r, Symbol sym) +{ + int i; + struct dag *p; - i = (opindex(op)^((unsigned long)sym>>2))&(NELEMS(buckets)-1); - for (p = buckets[i]; p; p = p->hlink) - if (p->node.op == op && p->node.syms[0] == sym - && p->node.kids[0] == l && p->node.kids[1] == r) - return &p->node; - p = dagnode(op, l, r, sym); - p->hlink = buckets[i]; - buckets[i] = p; - ++nodecount; - return &p->node; + i = (opindex(op) ^ ((unsigned long)sym >> 2)) & (NELEMS(buckets) - 1); + for (p = buckets[i]; p; p = p->hlink) + if (p->node.op == op && p->node.syms[0] == sym && p->node.kids[0] == l && p->node.kids[1] == r) return &p->node; + p = dagnode(op, l, r, sym); + p->hlink = buckets[i]; + buckets[i] = p; + ++nodecount; + return &p->node; } -static struct dag *dagnode(int op, Node l, Node r, Symbol sym) { - struct dag *p; - NEW0(p, FUNC); - p->node.op = op; - if ((p->node.kids[0] = l) != NULL) - ++l->count; - if ((p->node.kids[1] = r) != NULL) - ++r->count; - p->node.syms[0] = sym; - return p; +static Node constnode(int op, Symbol sym) +{ + struct dag *p; + p = dagnode(op, NULL, NULL, sym); + ++nodecount; + return &p->node; } -Node newnode(int op, Node l, Node r, Symbol sym) { - return &dagnode(op, l, r, sym)->node; + +static struct dag *dagnode(int op, Node l, Node r, Symbol sym) +{ + struct dag *p; + + NEW0(p, FUNC); + p->node.op = op; + if ((p->node.kids[0] = l) != NULL) ++l->count; + if ((p->node.kids[1] = r) != NULL) ++r->count; + p->node.syms[0] = sym; + return p; } -static void kill(Symbol p) { - int i; - struct dag **q; +Node newnode(int op, Node l, Node r, Symbol sym) { return &dagnode(op, l, r, sym)->node; } +static void kill(Symbol p) +{ + int i; + struct dag **q; - for (i = 0; i < NELEMS(buckets); i++) - for (q = &buckets[i]; *q; ) - if (generic((*q)->node.op) == INDIR && - (!isaddrop((*q)->node.kids[0]->op) - || (*q)->node.kids[0]->syms[0] == p)) { - *q = (*q)->hlink; - --nodecount; - } else - q = &(*q)->hlink; + for (i = 0; i < NELEMS(buckets); i++) + for (q = &buckets[i]; *q;) + if (generic((*q)->node.op) == INDIR && + (!isaddrop((*q)->node.kids[0]->op) || (*q)->node.kids[0]->syms[0] == p)) + { + *q = (*q)->hlink; + --nodecount; + } + else + q = &(*q)->hlink; } -static void reset(void) { - if (nodecount > 0) - memset(buckets, 0, sizeof buckets); - nodecount = 0; +static void reset(void) +{ + if (nodecount > 0) memset(buckets, 0, sizeof buckets); + nodecount = 0; } -Node listnodes(Tree tp, int tlab, int flab) { - Node p = NULL, l, r; - int op; +Node listnodes(Tree tp, int tlab, int flab) +{ + Node p = NULL, l, r; + int op; - assert(tlab || flab || (tlab == 0 && flab == 0)); - if (tp == NULL) - return NULL; - if (tp->node) - return tp->node; - op = tp->op + sizeop(tp->type->size); - switch (generic(tp->op)) { - case AND: { if (depth++ == 0) reset(); - if (flab) { - listnodes(tp->kids[0], 0, flab); - listnodes(tp->kids[1], 0, flab); - } else { - listnodes(tp->kids[0], 0, flab = genlabel(1)); - listnodes(tp->kids[1], tlab, 0); - labelnode(flab); - } - depth--; } break; - case OR: { if (depth++ == 0) - reset(); - if (tlab) { - listnodes(tp->kids[0], tlab, 0); - listnodes(tp->kids[1], tlab, 0); - } else { - tlab = genlabel(1); - listnodes(tp->kids[0], tlab, 0); - listnodes(tp->kids[1], 0, flab); - labelnode(tlab); - } - depth--; - } break; - case NOT: { return listnodes(tp->kids[0], flab, tlab); } - case COND: { Tree q = tp->kids[1]; - assert(tlab == 0 && flab == 0); - if (tp->u.sym) - addlocal(tp->u.sym); - flab = genlabel(2); - listnodes(tp->kids[0], 0, flab); - assert(q && q->op == RIGHT); - reset(); - listnodes(q->kids[0], 0, 0); - if (forest->op == LABEL+V) { - equatelab(forest->syms[0], findlabel(flab + 1)); - unlist(); - } - list(jump(flab + 1)); - labelnode(flab); - listnodes(q->kids[1], 0, 0); - if (forest->op == LABEL+V) { - equatelab(forest->syms[0], findlabel(flab + 1)); - unlist(); - } - labelnode(flab + 1); + assert(tlab || flab || (tlab == 0 && flab == 0)); + if (tp == NULL) return NULL; + if (tp->node) return tp->node; + op = tp->op + sizeop(tp->type->size); + switch (generic(tp->op)) + { + case AND: + { + if (depth++ == 0) reset(); + if (flab) + { + listnodes(tp->kids[0], 0, flab); + listnodes(tp->kids[1], 0, flab); + } + else + { + listnodes(tp->kids[0], 0, flab = genlabel(1)); + listnodes(tp->kids[1], tlab, 0); + labelnode(flab); + } + depth--; + } + break; + case OR: + { + if (depth++ == 0) reset(); + if (tlab) + { + listnodes(tp->kids[0], tlab, 0); + listnodes(tp->kids[1], tlab, 0); + } + else + { + tlab = genlabel(1); + listnodes(tp->kids[0], tlab, 0); + listnodes(tp->kids[1], 0, flab); + labelnode(tlab); + } + depth--; + } + break; + case NOT: + { + return listnodes(tp->kids[0], flab, tlab); + } + case COND: + { + Tree q = tp->kids[1]; + assert(tlab == 0 && flab == 0); + if (tp->u.sym) addlocal(tp->u.sym); + flab = genlabel(2); + listnodes(tp->kids[0], 0, flab); + assert(q && q->op == RIGHT); + reset(); + listnodes(q->kids[0], 0, 0); + if (forest->op == LABEL + V) + { + equatelab(forest->syms[0], findlabel(flab + 1)); + unlist(); + } + list(jump(flab + 1)); + labelnode(flab); + listnodes(q->kids[1], 0, 0); + if (forest->op == LABEL + V) + { + equatelab(forest->syms[0], findlabel(flab + 1)); + unlist(); + } + labelnode(flab + 1); - if (tp->u.sym) - p = listnodes(idtree(tp->u.sym), 0, 0); } break; - case CNST: { Type ty = unqual(tp->type); - assert(ty->u.sym); - if (tlab || flab) { - assert(ty == inttype); - if (tlab && tp->u.v.i != 0) - list(jump(tlab)); - else if (flab && tp->u.v.i == 0) - list(jump(flab)); - } - else if (ty->u.sym->addressed) - p = listnodes(cvtconst(tp), 0, 0); - else - p = node(op, NULL, NULL, constant(ty, tp->u.v)); } break; - case RIGHT: { if ( tp->kids[0] && tp->kids[1] - && generic(tp->kids[1]->op) == ASGN - && ((generic(tp->kids[0]->op) == INDIR - && tp->kids[0]->kids[0] == tp->kids[1]->kids[0]) - || (tp->kids[0]->op == FIELD - && tp->kids[0] == tp->kids[1]->kids[0]))) { - assert(tlab == 0 && flab == 0); - if (generic(tp->kids[0]->op) == INDIR) { - p = listnodes(tp->kids[0], 0, 0); - list(p); - listnodes(tp->kids[1], 0, 0); - } - else { - assert(generic(tp->kids[0]->kids[0]->op) == INDIR); - list(listnodes(tp->kids[0]->kids[0], 0, 0)); - p = listnodes(tp->kids[0], 0, 0); - listnodes(tp->kids[1], 0, 0); - } - } else if (tp->kids[1]) { - listnodes(tp->kids[0], 0, 0); - p = listnodes(tp->kids[1], tlab, flab); - } else - p = listnodes(tp->kids[0], tlab, flab); } break; - case JUMP: { assert(tlab == 0 && flab == 0); - assert(tp->u.sym == 0); - assert(tp->kids[0]); - l = listnodes(tp->kids[0], 0, 0); - list(newnode(JUMP+V, l, NULL, NULL)); - reset(); } break; - case CALL: { Tree save = firstarg; - firstarg = NULL; - assert(tlab == 0 && flab == 0); - if (tp->op == CALL+B && !IR->wants_callb) { - Tree arg0 = tree(ARG+P, tp->kids[1]->type, - tp->kids[1], NULL); - if (IR->left_to_right) - firstarg = arg0; - l = listnodes(tp->kids[0], 0, 0); - if (!IR->left_to_right || firstarg) { - firstarg = NULL; - listnodes(arg0, 0, 0); - } - p = newnode(CALL+V, l, NULL, NULL); - } else { - l = listnodes(tp->kids[0], 0, 0); - r = listnodes(tp->kids[1], 0, 0); - p = newnode(tp->op == CALL+B ? tp->op : op, l, r, NULL); - } - NEW0(p->syms[0], FUNC); - assert(isptr(tp->kids[0]->type)); - assert(isfunc(tp->kids[0]->type->type)); - p->syms[0]->type = tp->kids[0]->type->type; - list(p); - reset(); - cfunc->u.f.ncalls++; - firstarg = save; - } break; - case ARG: { assert(tlab == 0 && flab == 0); - if (IR->left_to_right) - listnodes(tp->kids[1], 0, 0); - if (firstarg) { - Tree arg = firstarg; - firstarg = NULL; - listnodes(arg, 0, 0); - } - l = listnodes(tp->kids[0], 0, 0); - list(newnode(tp->op == ARG+B ? tp->op : op, l, NULL, NULL)); - forest->syms[0] = intconst(tp->type->size); - forest->syms[1] = intconst(tp->type->align); - if (!IR->left_to_right) - listnodes(tp->kids[1], 0, 0); } break; - case EQ: case NE: case GT: case GE: case LE: - case LT: { assert(tp->u.sym == 0); - assert(errcnt || tlab || flab); - l = listnodes(tp->kids[0], 0, 0); - r = listnodes(tp->kids[1], 0, 0); - assert(errcnt || opkind(l->op) == opkind(r->op)); - assert(errcnt || optype(op) == optype(l->op)); - if (tlab) - assert(flab == 0), - list(newnode(generic(tp->op) + opkind(l->op), l, r, findlabel(tlab))); - else if (flab) { - switch (generic(tp->op)) { - case EQ: op = NE; break; - case NE: op = EQ; break; - case GT: op = LE; break; - case LT: op = GE; break; - case GE: op = LT; break; - case LE: op = GT; break; - default: assert(0); - } - list(newnode(op + opkind(l->op), l, r, findlabel(flab))); - } - if (forest && forest->syms[0]) - forest->syms[0]->ref++; } break; - case ASGN: { assert(tlab == 0 && flab == 0); - if (tp->kids[0]->op == FIELD) { - Tree x = tp->kids[0]->kids[0]; - Field f = tp->kids[0]->u.field; - assert(generic(x->op) == INDIR); - reset(); - l = listnodes(lvalue(x), 0, 0); - if (fieldsize(f) < 8*f->type->size) { - unsigned int fmask = fieldmask(f); - unsigned int mask = fmask<kids[1]; - if ((q->op == CNST+I && q->u.v.i == 0) - || (q->op == CNST+U && q->u.v.u == 0)) - q = bittree(BAND, x, cnsttree(unsignedtype, (unsigned long)~mask)); - else if ((q->op == CNST+I && (q->u.v.i&fmask) == fmask) - || (q->op == CNST+U && (q->u.v.u&fmask) == fmask)) - q = bittree(BOR, x, cnsttree(unsignedtype, (unsigned long)mask)); - else { - listnodes(q, 0, 0); - q = bittree(BOR, - bittree(BAND, rvalue(lvalue(x)), - cnsttree(unsignedtype, (unsigned long)~mask)), - bittree(BAND, shtree(LSH, cast(q, unsignedtype), - cnsttree(unsignedtype, (unsigned long)fieldright(f))), - cnsttree(unsignedtype, (unsigned long)mask))); - } - r = listnodes(q, 0, 0); - op = ASGN + ttob(q->type); - } else { - r = listnodes(tp->kids[1], 0, 0); - op = ASGN + ttob(tp->kids[1]->type); - } - } else { - l = listnodes(tp->kids[0], 0, 0); - r = listnodes(tp->kids[1], 0, 0); - } - list(newnode(tp->op == ASGN+B ? tp->op : op, l, r, NULL)); - forest->syms[0] = intconst(tp->kids[1]->type->size); - forest->syms[1] = intconst(tp->kids[1]->type->align); - if (isaddrop(tp->kids[0]->op) - && !tp->kids[0]->u.sym->computed) - kill(tp->kids[0]->u.sym); - else - reset(); - p = listnodes(tp->kids[1], 0, 0); } break; - case BOR: case BAND: case BXOR: - case ADD: case SUB: case RSH: - case LSH: { assert(tlab == 0 && flab == 0); - l = listnodes(tp->kids[0], 0, 0); - r = listnodes(tp->kids[1], 0, 0); - p = node(op, l, r, NULL); } break; - case DIV: case MUL: - case MOD: { assert(tlab == 0 && flab == 0); - l = listnodes(tp->kids[0], 0, 0); - r = listnodes(tp->kids[1], 0, 0); - p = node(op, l, r, NULL); - if (IR->mulops_calls && isint(tp->type)) { - list(p); - cfunc->u.f.ncalls++; - } } break; - case RET: { assert(tlab == 0 && flab == 0); - l = listnodes(tp->kids[0], 0, 0); - list(newnode(op, l, NULL, NULL)); } break; - case CVF: case CVI: case CVP: - case CVU: { assert(tlab == 0 && flab == 0); - assert(optype(tp->kids[0]->op) != optype(tp->op) || tp->kids[0]->type->size != tp->type->size); - l = listnodes(tp->kids[0], 0, 0); - p = node(op, l, NULL, intconst(tp->kids[0]->type->size)); - } break; - case BCOM: - case NEG: { assert(tlab == 0 && flab == 0); - l = listnodes(tp->kids[0], 0, 0); - p = node(op, l, NULL, NULL); } break; - case INDIR: { Type ty = tp->kids[0]->type; - assert(tlab == 0 && flab == 0); - l = listnodes(tp->kids[0], 0, 0); - if (isptr(ty)) - ty = unqual(ty)->type; - if (isvolatile(ty) - || (isstruct(ty) && unqual(ty)->u.sym->u.s.vfields)) - p = newnode(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); - else - p = node(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); } break; - case FIELD: { Tree q = tp->kids[0]; - if (tp->type == inttype) { - long n = fieldleft(tp->u.field); - q = shtree(RSH, - shtree(LSH, q, cnsttree(inttype, n)), - cnsttree(inttype, n + fieldright(tp->u.field))); - } else if (fieldsize(tp->u.field) < 8*tp->u.field->type->size) - q = bittree(BAND, - shtree(RSH, q, cnsttree(inttype, (long)fieldright(tp->u.field))), - cnsttree(unsignedtype, (unsigned long)fieldmask(tp->u.field))); - assert(tlab == 0 && flab == 0); - p = listnodes(q, 0, 0); } break; - case ADDRG: - case ADDRF: { assert(tlab == 0 && flab == 0); - p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); - } break; - case ADDRL: { assert(tlab == 0 && flab == 0); - if (tp->u.sym->temporary) - addlocal(tp->u.sym); - p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); } break; - default:assert(0); - } - tp->node = p; - return p; + if (tp->u.sym) p = listnodes(idtree(tp->u.sym), 0, 0); + } + break; + case CNST: + { + Type ty = unqual(tp->type); + assert(ty->u.sym); + if (tlab || flab) + { + assert(ty == inttype); + if (tlab && tp->u.v.i != 0) + list(jump(tlab)); + else if (flab && tp->u.v.i == 0) + list(jump(flab)); + } + else if (ty->u.sym->addressed) + p = listnodes(cvtconst(tp), 0, 0); + else + /* always generate new node for constants */ + if (isscalar(ty)) + p = constnode(op, constant(ty, tp->u.v)); + else + p = node(op, NULL, NULL, constant(ty, tp->u.v)); + } + break; + case RIGHT: + { + if (tp->kids[0] && tp->kids[1] && generic(tp->kids[1]->op) == ASGN && + ((generic(tp->kids[0]->op) == INDIR && tp->kids[0]->kids[0] == tp->kids[1]->kids[0]) || + (tp->kids[0]->op == FIELD && tp->kids[0] == tp->kids[1]->kids[0]))) + { + assert(tlab == 0 && flab == 0); + if (generic(tp->kids[0]->op) == INDIR) + { + p = listnodes(tp->kids[0], 0, 0); + list(p); + listnodes(tp->kids[1], 0, 0); + } + else + { + assert(generic(tp->kids[0]->kids[0]->op) == INDIR); + list(listnodes(tp->kids[0]->kids[0], 0, 0)); + p = listnodes(tp->kids[0], 0, 0); + listnodes(tp->kids[1], 0, 0); + } + } + else if (tp->kids[1]) + { + listnodes(tp->kids[0], 0, 0); + p = listnodes(tp->kids[1], tlab, flab); + } + else + p = listnodes(tp->kids[0], tlab, flab); + } + break; + case JUMP: + { + assert(tlab == 0 && flab == 0); + assert(tp->u.sym == 0); + assert(tp->kids[0]); + l = listnodes(tp->kids[0], 0, 0); + list(newnode(JUMP + V, l, NULL, NULL)); + reset(); + } + break; + case CALL: + { + Tree save = firstarg; + firstarg = NULL; + assert(tlab == 0 && flab == 0); + if (tp->op == CALL + B && !IR->wants_callb) + { + Tree arg0 = tree(ARG + P, tp->kids[1]->type, tp->kids[1], NULL); + if (IR->left_to_right) firstarg = arg0; + l = listnodes(tp->kids[0], 0, 0); + if (!IR->left_to_right || firstarg) + { + firstarg = NULL; + listnodes(arg0, 0, 0); + } + p = newnode(CALL + V, l, NULL, NULL); + } + else + { + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + p = newnode(tp->op == CALL + B ? tp->op : op, l, r, NULL); + } + NEW0(p->syms[0], FUNC); + assert(isptr(tp->kids[0]->type)); + assert(isfunc(tp->kids[0]->type->type)); + p->syms[0]->type = tp->kids[0]->type->type; + list(p); + reset(); + cfunc->u.f.ncalls++; + firstarg = save; + } + break; + case ARG: + { + assert(tlab == 0 && flab == 0); + if (IR->left_to_right) listnodes(tp->kids[1], 0, 0); + if (firstarg) + { + Tree arg = firstarg; + firstarg = NULL; + listnodes(arg, 0, 0); + } + l = listnodes(tp->kids[0], 0, 0); + list(newnode(tp->op == ARG + B ? tp->op : op, l, NULL, NULL)); + forest->syms[0] = intconst(tp->type->size); + forest->syms[1] = intconst(tp->type->align); + if (!IR->left_to_right) listnodes(tp->kids[1], 0, 0); + } + break; + case EQ: + case NE: + case GT: + case GE: + case LE: + case LT: + { + assert(tp->u.sym == 0); + assert(errcnt || tlab || flab); + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + assert(errcnt || opkind(l->op) == opkind(r->op)); + assert(errcnt || optype(op) == optype(l->op)); + if (tlab) + assert(flab == 0), list(newnode(generic(tp->op) + opkind(l->op), l, r, findlabel(tlab))); + else if (flab) + { + switch (generic(tp->op)) + { + case EQ: + op = NE; + break; + case NE: + op = EQ; + break; + case GT: + op = LE; + break; + case LT: + op = GE; + break; + case GE: + op = LT; + break; + case LE: + op = GT; + break; + default: + assert(0); + } + list(newnode(op + opkind(l->op), l, r, findlabel(flab))); + } + if (forest && forest->syms[0]) forest->syms[0]->ref++; + } + break; + case ASGN: + { + assert(tlab == 0 && flab == 0); + if (tp->kids[0]->op == FIELD) + { + Tree x = tp->kids[0]->kids[0]; + Field f = tp->kids[0]->u.field; + assert(generic(x->op) == INDIR); + reset(); + l = listnodes(lvalue(x), 0, 0); + if (fieldsize(f) < 8 * f->type->size) + { + unsigned int fmask = fieldmask(f); + unsigned int mask = fmask << fieldright(f); + Tree q = tp->kids[1]; + if ((q->op == CNST + I && q->u.v.i == 0) || (q->op == CNST + U && q->u.v.u == 0)) + q = bittree(BAND, x, cnsttree(unsignedtype, (unsigned long)~mask)); + else if ((q->op == CNST + I && (q->u.v.i & fmask) == fmask) || + (q->op == CNST + U && (q->u.v.u & fmask) == fmask)) + q = bittree(BOR, x, cnsttree(unsignedtype, (unsigned long)mask)); + else + { + listnodes(q, 0, 0); + q = bittree(BOR, bittree(BAND, rvalue(lvalue(x)), cnsttree(unsignedtype, (unsigned long)~mask)), + bittree(BAND, shtree(LSH, cast(q, unsignedtype), + cnsttree(unsignedtype, (unsigned long)fieldright(f))), + cnsttree(unsignedtype, (unsigned long)mask))); + } + r = listnodes(q, 0, 0); + op = ASGN + ttob(q->type); + } + else + { + r = listnodes(tp->kids[1], 0, 0); + op = ASGN + ttob(tp->kids[1]->type); + } + } + else + { + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + } + list(newnode(tp->op == ASGN + B ? tp->op : op, l, r, NULL)); + forest->syms[0] = intconst(tp->kids[1]->type->size); + forest->syms[1] = intconst(tp->kids[1]->type->align); + if (isaddrop(tp->kids[0]->op) && !tp->kids[0]->u.sym->computed) + kill(tp->kids[0]->u.sym); + else + reset(); + p = listnodes(tp->kids[1], 0, 0); + } + break; + case BOR: + case BAND: + case BXOR: + case ADD: + case SUB: + case RSH: + case LSH: + { + assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + p = node(op, l, r, NULL); + } + break; + case DIV: + case MUL: + case MOD: + { + assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + p = node(op, l, r, NULL); + if (IR->mulops_calls && isint(tp->type)) + { + list(p); + cfunc->u.f.ncalls++; + } + } + break; + case RET: + { + assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + list(newnode(op, l, NULL, NULL)); + } + break; + case CVF: + case CVI: + case CVP: + case CVU: + { + assert(tlab == 0 && flab == 0); + assert(optype(tp->kids[0]->op) != optype(tp->op) || tp->kids[0]->type->size != tp->type->size); + l = listnodes(tp->kids[0], 0, 0); + p = node(op, l, NULL, intconst(tp->kids[0]->type->size)); + } + break; + case BCOM: + case NEG: + { + assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + p = node(op, l, NULL, NULL); + } + break; + case INDIR: + { + Type ty = tp->kids[0]->type; + assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + if (isptr(ty)) ty = unqual(ty)->type; + if (isvolatile(ty) || (isstruct(ty) && unqual(ty)->u.sym->u.s.vfields)) + p = newnode(tp->op == INDIR + B ? tp->op : op, l, NULL, NULL); + else + p = node(tp->op == INDIR + B ? tp->op : op, l, NULL, NULL); + } + break; + case FIELD: + { + Tree q = tp->kids[0]; + if (tp->type == inttype) + { + long n = fieldleft(tp->u.field); + q = shtree(RSH, shtree(LSH, q, cnsttree(inttype, n)), cnsttree(inttype, n + fieldright(tp->u.field))); + } + else if (fieldsize(tp->u.field) < 8 * tp->u.field->type->size) + q = bittree(BAND, shtree(RSH, q, cnsttree(inttype, (long)fieldright(tp->u.field))), + cnsttree(unsignedtype, (unsigned long)fieldmask(tp->u.field))); + assert(tlab == 0 && flab == 0); + p = listnodes(q, 0, 0); + } + break; + case ADDRG: + case ADDRF: + { + assert(tlab == 0 && flab == 0); + p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); + } + break; + case ADDRL: + { + assert(tlab == 0 && flab == 0); + if (tp->u.sym->temporary) addlocal(tp->u.sym); + p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); + } + break; + default: + assert(0); + } + tp->node = p; + return p; } -static void list(Node p) { - if (p && p->link == NULL) { - if (forest) { - p->link = forest->link; - forest->link = p; - } else - p->link = p; - forest = p; - } +static void list(Node p) +{ + if (p && p->link == NULL) + { + if (forest) + { + p->link = forest->link; + forest->link = p; + } + else + p->link = p; + forest = p; + } } -static void labelnode(int lab) { - assert(lab); - if (forest && forest->op == LABEL+V) - equatelab(findlabel(lab), forest->syms[0]); - else - list(newnode(LABEL+V, NULL, NULL, findlabel(lab))); - reset(); +static void labelnode(int lab) +{ + assert(lab); + if (forest && forest->op == LABEL + V) + equatelab(findlabel(lab), forest->syms[0]); + else + list(newnode(LABEL + V, NULL, NULL, findlabel(lab))); + reset(); } -static void unlist(void) { - Node p; +static void unlist(void) +{ + Node p; - assert(forest); - assert(forest != forest->link); - p = forest->link; - while (p->link != forest) - p = p->link; - p->link = forest->link; - forest = p; + assert(forest); + assert(forest != forest->link); + p = forest->link; + while (p->link != forest) p = p->link; + p->link = forest->link; + forest = p; } -Tree cvtconst(Tree p) { - Symbol q = constant(p->type, p->u.v); - Tree e; +Tree cvtconst(Tree p) +{ + Symbol q = constant(p->type, p->u.v); + Tree e; - if (q->u.c.loc == NULL) - q->u.c.loc = genident(STATIC, p->type, GLOBAL); - if (isarray(p->type)) { - e = simplify(ADDRG, atop(p->type), NULL, NULL); - e->u.sym = q->u.c.loc; - } else - e = idtree(q->u.c.loc); - return e; + if (q->u.c.loc == NULL) q->u.c.loc = genident(STATIC, p->type, GLOBAL); + if (isarray(p->type)) + { + e = simplify(ADDRG, atop(p->type), NULL, NULL); + e->u.sym = q->u.c.loc; + } + else + e = idtree(q->u.c.loc); + return e; } -void gencode(Symbol caller[], Symbol callee[]) { - Code cp; - Coordinate save; +void gencode(Symbol caller[], Symbol callee[]) +{ + Code cp; + Coordinate save; - if (prunetemps == -1) - prunetemps = !IR->wants_dag; - save = src; - if (assignargs) { - int i; - Symbol p, q; - cp = codehead.next->next; - codelist = codehead.next; - for (i = 0; (p = callee[i]) != NULL - && (q = caller[i]) != NULL; i++) - if (p->sclass != q->sclass || p->type != q->type) - walk(asgn(p, idtree(q)), 0, 0); - codelist->next = cp; - cp->prev = codelist; - } - if (glevel && IR->stabsym) { - int i; - Symbol p, q; - for (i = 0; (p = callee[i]) != NULL - && (q = caller[i]) != NULL; i++) { - (*IR->stabsym)(p); - if (p->sclass != q->sclass || p->type != q->type) - (*IR->stabsym)(q); - } - swtoseg(CODE); - } - cp = codehead.next; - for ( ; errcnt <= 0 && cp; cp = cp->next) - switch (cp->kind) { - case Address: (*IR->address)(cp->u.addr.sym, cp->u.addr.base, - cp->u.addr.offset); break; - case Blockbeg: { - Symbol *p = cp->u.block.locals; - (*IR->blockbeg)(&cp->u.block.x); - for ( ; *p; p++) - if ((*p)->ref != 0.0) - (*IR->local)(*p); - else if (glevel) (*IR->local)(*p); - } - break; - case Blockend: (*IR->blockend)(&cp->u.begin->u.block.x); break; - case Defpoint: src = cp->u.point.src; break; - case Gen: case Jump: - case Label: if (prunetemps) - cp->u.forest = prune(cp->u.forest); - fixup(cp->u.forest); - cp->u.forest = (*IR->gen)(cp->u.forest); break; - case Local: (*IR->local)(cp->u.var); break; - case Switch: break; - default: assert(0); - } - src = save; + if (prunetemps == -1) prunetemps = !IR->wants_dag; + save = src; + if (assignargs) + { + int i; + Symbol p, q; + cp = codehead.next->next; + codelist = codehead.next; + for (i = 0; (p = callee[i]) != NULL && (q = caller[i]) != NULL; i++) + if (p->sclass != q->sclass || p->type != q->type) walk(asgn(p, idtree(q)), 0, 0); + codelist->next = cp; + cp->prev = codelist; + } + if (glevel && IR->stabsym) + { + int i; + Symbol p, q; + for (i = 0; (p = callee[i]) != NULL && (q = caller[i]) != NULL; i++) + { + (*IR->stabsym)(p); + if (p->sclass != q->sclass || p->type != q->type) (*IR->stabsym)(q); + } + swtoseg(CODE); + } + cp = codehead.next; + for (; errcnt <= 0 && cp; cp = cp->next) switch (cp->kind) + { + case Address: + (*IR->address)(cp->u.addr.sym, cp->u.addr.base, cp->u.addr.offset); + break; + case Blockbeg: + { + Symbol *p = cp->u.block.locals; + (*IR->blockbeg)(&cp->u.block.x); + for (; *p; p++) + if ((*p)->ref != 0.0) + (*IR->local)(*p); + else if (glevel) + (*IR->local)(*p); + } + break; + case Blockend: + (*IR->blockend)(&cp->u.begin->u.block.x); + break; + case Defpoint: + src = cp->u.point.src; + break; + case Gen: + case Jump: + case Label: + if (prunetemps) cp->u.forest = prune(cp->u.forest); + fixup(cp->u.forest); + cp->u.forest = (*IR->gen)(cp->u.forest); + break; + case Local: + (*IR->local)(cp->u.var); + break; + case Switch: + break; + default: + assert(0); + } + src = save; } -static void fixup(Node p) { - for ( ; p; p = p->link) - switch (generic(p->op)) { - case JUMP: - if (specific(p->kids[0]->op) == ADDRG+P) - p->kids[0]->syms[0] = - equated(p->kids[0]->syms[0]); - break; - case LABEL: assert(p->syms[0] == equated(p->syms[0])); break; - case EQ: case GE: case GT: case LE: case LT: case NE: - assert(p->syms[0]); - p->syms[0] = equated(p->syms[0]); - } +static void fixup(Node p) +{ + for (; p; p = p->link) switch (generic(p->op)) + { + case JUMP: + if (specific(p->kids[0]->op) == ADDRG + P) p->kids[0]->syms[0] = equated(p->kids[0]->syms[0]); + break; + case LABEL: + assert(p->syms[0] == equated(p->syms[0])); + break; + case EQ: + case GE: + case GT: + case LE: + case LT: + case NE: + assert(p->syms[0]); + p->syms[0] = equated(p->syms[0]); + } } -static Symbol equated(Symbol p) { - { Symbol q; for (q = p->u.l.equatedto; q; q = q->u.l.equatedto) assert(p != q); } - while (p->u.l.equatedto) - p = p->u.l.equatedto; - return p; +static Symbol equated(Symbol p) +{ + { + Symbol q; + for (q = p->u.l.equatedto; q; q = q->u.l.equatedto) assert(p != q); + } + while (p->u.l.equatedto) p = p->u.l.equatedto; + return p; } -void emitcode(void) { - Code cp; - Coordinate save; +void emitcode(void) +{ + Code cp; + Coordinate save; - save = src; - cp = codehead.next; - for ( ; errcnt <= 0 && cp; cp = cp->next) - switch (cp->kind) { - case Address: break; - case Blockbeg: if (glevel && IR->stabblock) { - (*IR->stabblock)('{', cp->u.block.level - LOCAL, cp->u.block.locals); - swtoseg(CODE); - } - break; - case Blockend: if (glevel && IR->stabblock) { - Code bp = cp->u.begin; - foreach(bp->u.block.identifiers, bp->u.block.level, typestab, NULL); - foreach(bp->u.block.types, bp->u.block.level, typestab, NULL); - (*IR->stabblock)('}', bp->u.block.level - LOCAL, bp->u.block.locals); - swtoseg(CODE); - } - break; - case Defpoint: src = cp->u.point.src; - if (glevel > 0 && IR->stabline) { - (*IR->stabline)(&cp->u.point.src); swtoseg(CODE); } break; - case Gen: case Jump: - case Label: if (cp->u.forest) - (*IR->emit)(cp->u.forest); break; - case Local: if (glevel && IR->stabsym) { - (*IR->stabsym)(cp->u.var); - swtoseg(CODE); - } break; - case Switch: { int i; - defglobal(cp->u.swtch.table, LIT); - (*IR->defaddress)(equated(cp->u.swtch.labels[0])); - for (i = 1; i < cp->u.swtch.size; i++) { - long k = cp->u.swtch.values[i-1]; - while (++k < cp->u.swtch.values[i]) - assert(k < LONG_MAX), - (*IR->defaddress)(equated(cp->u.swtch.deflab)); - (*IR->defaddress)(equated(cp->u.swtch.labels[i])); - } - swtoseg(CODE); - } break; - default: assert(0); - } - src = save; + save = src; + cp = codehead.next; + for (; errcnt <= 0 && cp; cp = cp->next) switch (cp->kind) + { + case Address: + break; + case Blockbeg: + if (glevel && IR->stabblock) + { + (*IR->stabblock)('{', cp->u.block.level - LOCAL, cp->u.block.locals); + swtoseg(CODE); + } + break; + case Blockend: + if (glevel && IR->stabblock) + { + Code bp = cp->u.begin; + foreach (bp->u.block.identifiers, bp->u.block.level, typestab, NULL) + ; + foreach (bp->u.block.types, bp->u.block.level, typestab, NULL) + ; + (*IR->stabblock)('}', bp->u.block.level - LOCAL, bp->u.block.locals); + swtoseg(CODE); + } + break; + case Defpoint: + src = cp->u.point.src; + if (glevel > 0 && IR->stabline) + { + (*IR->stabline)(&cp->u.point.src); + swtoseg(CODE); + } + break; + case Gen: + case Jump: + case Label: + if (cp->u.forest) (*IR->emit)(cp->u.forest); + break; + case Local: + if (glevel && IR->stabsym) + { + (*IR->stabsym)(cp->u.var); + swtoseg(CODE); + } + break; + case Switch: + { + int i; + defglobal(cp->u.swtch.table, LIT); + (*IR->defaddress)(equated(cp->u.swtch.labels[0])); + for (i = 1; i < cp->u.swtch.size; i++) + { + long k = cp->u.swtch.values[i - 1]; + while (++k < cp->u.swtch.values[i]) + assert(k < LONG_MAX), (*IR->defaddress)(equated(cp->u.swtch.deflab)); + (*IR->defaddress)(equated(cp->u.swtch.labels[i])); + } + swtoseg(CODE); + } + break; + default: + assert(0); + } + src = save; } -static Node undag(Node forest) { - Node p; +static Node undag(Node forest) +{ + Node p; - tail = &forest; - for (p = forest; p; p = p->link) - if (generic(p->op) == INDIR) { - assert(p->count >= 1); - visit(p, 1); - if (p->syms[2]) { - assert(p->syms[2]->u.t.cse); - p->syms[2]->u.t.cse = NULL; - addlocal(p->syms[2]); - } - } else if (iscall(p->op) && p->count >= 1) - visit(p, 1); - else { - assert(p->count == 0), - visit(p, 1); - *tail = p; - tail = &p->link; - } - *tail = NULL; - return forest; + tail = &forest; + for (p = forest; p; p = p->link) + if (generic(p->op) == INDIR) + { + assert(p->count >= 1); + visit(p, 1); + if (p->syms[2]) + { + assert(p->syms[2]->u.t.cse); + p->syms[2]->u.t.cse = NULL; + addlocal(p->syms[2]); + } + } + else if (iscall(p->op) && p->count >= 1) + visit(p, 1); + else + { + assert(p->count == 0), visit(p, 1); + *tail = p; + tail = &p->link; + } + *tail = NULL; + return forest; } -static Node replace(Node p) { - if (p && ( generic(p->op) == INDIR - && generic(p->kids[0]->op) == ADDRL - && p->kids[0]->syms[0]->temporary - && p->kids[0]->syms[0]->u.t.replace)) { - p = p->kids[0]->syms[0]->u.t.cse; - if (generic(p->op) == INDIR && isaddrop(p->kids[0]->op)) - p = newnode(p->op, newnode(p->kids[0]->op, NULL, NULL, - p->kids[0]->syms[0]), NULL, NULL); - else if (generic(p->op) == ADDRG) - p = newnode(p->op, NULL, NULL, p->syms[0]); - else - assert(0); - p->count = 1; - } else if (p) { - p->kids[0] = replace(p->kids[0]); - p->kids[1] = replace(p->kids[1]); - } - return p; +static Node replace(Node p) +{ + if (p && (generic(p->op) == INDIR && generic(p->kids[0]->op) == ADDRL && p->kids[0]->syms[0]->temporary && + p->kids[0]->syms[0]->u.t.replace)) + { + p = p->kids[0]->syms[0]->u.t.cse; + if (generic(p->op) == INDIR && isaddrop(p->kids[0]->op)) + p = newnode(p->op, newnode(p->kids[0]->op, NULL, NULL, p->kids[0]->syms[0]), NULL, NULL); + else if (generic(p->op) == ADDRG) + p = newnode(p->op, NULL, NULL, p->syms[0]); + else + assert(0); + p->count = 1; + } + else if (p) + { + p->kids[0] = replace(p->kids[0]); + p->kids[1] = replace(p->kids[1]); + } + return p; } -static Node prune(Node forest) { - Node p, *tail = &forest; - int count = 0; +static Node prune(Node forest) +{ + Node p, *tail = &forest; + int count = 0; - for (p = forest; p; p = p->link) { - if (count > 0) { - p->kids[0] = replace(p->kids[0]); - p->kids[1] = replace(p->kids[1]); - } - if (( generic(p->op) == ASGN - && generic(p->kids[0]->op) == ADDRL - && p->kids[0]->syms[0]->temporary - && p->kids[0]->syms[0]->u.t.cse == p->kids[1])) { - Symbol tmp = p->kids[0]->syms[0]; - if (!tmp->defined) - (*IR->local)(tmp); - tmp->defined = 1; - if (( generic(p->kids[1]->op) == INDIR - && isaddrop(p->kids[1]->kids[0]->op) - && p->kids[1]->kids[0]->syms[0]->sclass == REGISTER) - || (( generic(p->kids[1]->op) == INDIR - && isaddrop(p->kids[1]->kids[0]->op)) && tmp->sclass == AUTO) - || (generic(p->kids[1]->op) == ADDRG && tmp->sclass == AUTO)) { - tmp->u.t.replace = 1; - count++; - continue; /* and omit the assignment */ - } - } - /* keep the assignment and other roots */ - *tail = p; - tail = &(*tail)->link; - } - assert(*tail == NULL); - return forest; + for (p = forest; p; p = p->link) + { + if (count > 0) + { + p->kids[0] = replace(p->kids[0]); + p->kids[1] = replace(p->kids[1]); + } + if ((generic(p->op) == ASGN && generic(p->kids[0]->op) == ADDRL && p->kids[0]->syms[0]->temporary && + p->kids[0]->syms[0]->u.t.cse == p->kids[1])) + { + Symbol tmp = p->kids[0]->syms[0]; + if (!tmp->defined) (*IR->local)(tmp); + tmp->defined = 1; + if ((generic(p->kids[1]->op) == INDIR && isaddrop(p->kids[1]->kids[0]->op) && + p->kids[1]->kids[0]->syms[0]->sclass == REGISTER) || + ((generic(p->kids[1]->op) == INDIR && isaddrop(p->kids[1]->kids[0]->op)) && tmp->sclass == AUTO) || + (generic(p->kids[1]->op) == ADDRG && tmp->sclass == AUTO)) + { + tmp->u.t.replace = 1; + count++; + continue; /* and omit the assignment */ + } + } + /* keep the assignment and other roots */ + *tail = p; + tail = &(*tail)->link; + } + assert(*tail == NULL); + return forest; } -static Node visit(Node p, int listed) { - if (p) { - if (p->syms[2]) - p = tmpnode(p); - else if ((p->count <= 1 && !iscall(p->op)) - || (p->count == 0 && iscall(p->op))) { - p->kids[0] = visit(p->kids[0], 0); - p->kids[1] = visit(p->kids[1], 0); - } +static Node visit(Node p, int listed) +{ + if (p) + { + if (p->syms[2]) + p = tmpnode(p); + else if ((p->count <= 1 && !iscall(p->op)) || (p->count == 0 && iscall(p->op))) + { + p->kids[0] = visit(p->kids[0], 0); + p->kids[1] = visit(p->kids[1], 0); + } - else if (specific(p->op) == ADDRL+P || specific(p->op) == ADDRF+P) { - assert(!listed); - p = newnode(p->op, NULL, NULL, p->syms[0]); - p->count = 1; - } - else if (p->op == INDIR+B) { - p = newnode(p->op, p->kids[0], NULL, NULL); - p->count = 1; - p->kids[0] = visit(p->kids[0], 0); - p->kids[1] = visit(p->kids[1], 0); - } - else { - p->kids[0] = visit(p->kids[0], 0); - p->kids[1] = visit(p->kids[1], 0); - p->syms[2] = temporary(REGISTER, btot(p->op, opsize(p->op))); - assert(!p->syms[2]->defined); - p->syms[2]->ref = 1; - p->syms[2]->u.t.cse = p; + else if (specific(p->op) == ADDRL + P || specific(p->op) == ADDRF + P) + { + assert(!listed); + p = newnode(p->op, NULL, NULL, p->syms[0]); + p->count = 1; + } + else if (p->op == INDIR + B) + { + p = newnode(p->op, p->kids[0], NULL, NULL); + p->count = 1; + p->kids[0] = visit(p->kids[0], 0); + p->kids[1] = visit(p->kids[1], 0); + } + else + { + p->kids[0] = visit(p->kids[0], 0); + p->kids[1] = visit(p->kids[1], 0); + p->syms[2] = temporary(REGISTER, btot(p->op, opsize(p->op))); + assert(!p->syms[2]->defined); + p->syms[2]->ref = 1; + p->syms[2]->u.t.cse = p; - *tail = asgnnode(p->syms[2], p); - tail = &(*tail)->link; - if (!listed) - p = tmpnode(p); - }; - } - return p; + *tail = asgnnode(p->syms[2], p); + tail = &(*tail)->link; + if (!listed) p = tmpnode(p); + }; + } + return p; } -static Node tmpnode(Node p) { - Symbol tmp = p->syms[2]; +static Node tmpnode(Node p) +{ + Symbol tmp = p->syms[2]; - assert(tmp); - if (--p->count == 0) - p->syms[2] = NULL; - p = newnode(INDIR + ttob(tmp->type), - newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), NULL, NULL); - p->count = 1; - return p; + assert(tmp); + if (--p->count == 0) p->syms[2] = NULL; + p = newnode(INDIR + ttob(tmp->type), newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), NULL, NULL); + p->count = 1; + return p; } -static Node asgnnode(Symbol tmp, Node p) { - p = newnode(ASGN + ttob(tmp->type), - newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), p, NULL); - p->syms[0] = intconst(tmp->type->size); - p->syms[1] = intconst(tmp->type->align); - return p; +static Node asgnnode(Symbol tmp, Node p) +{ + p = newnode(ASGN + ttob(tmp->type), newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), p, NULL); + p->syms[0] = intconst(tmp->type->size); + p->syms[1] = intconst(tmp->type->align); + return p; } /* printdag - print dag p on fd, or the node list if p == 0 */ -void printdag(Node p, int fd) { - FILE *f = fd == 1 ? stdout : stderr; +void printdag(Node p, int fd) +{ + FILE *f = fd == 1 ? stdout : stderr; - printed(0); - if (p == 0) { - if ((p = forest) != NULL) - do { - p = p->link; - printdag1(p, fd, 0); - } while (p != forest); - } else if (*printed(nodeid((Tree)p))) - fprint(f, "node'%d printed above\n", nodeid((Tree)p)); - else - printdag1(p, fd, 0); + printed(0); + if (p == 0) + { + if ((p = forest) != NULL) do + { + p = p->link; + printdag1(p, fd, 0); + } while (p != forest); + } + else if (*printed(nodeid((Tree)p))) + fprint(f, "node'%d printed above\n", nodeid((Tree)p)); + else + printdag1(p, fd, 0); } /* printdag1 - recursively print dag p */ -static void printdag1(Node p, int fd, int lev) { - int id, i; +static void printdag1(Node p, int fd, int lev) +{ + int id, i; - if (p == 0 || *printed(id = nodeid((Tree)p))) - return; - *printed(id) = 1; - for (i = 0; i < NELEMS(p->kids); i++) - printdag1(p->kids[i], fd, lev + 1); - printnode(p, fd, lev); + if (p == 0 || *printed(id = nodeid((Tree)p))) return; + *printed(id) = 1; + for (i = 0; i < NELEMS(p->kids); i++) printdag1(p->kids[i], fd, lev + 1); + printnode(p, fd, lev); } /* printnode - print fields of dag p */ -static void printnode(Node p, int fd, int lev) { - if (p) { - FILE *f = fd == 1 ? stdout : stderr; - int i, id = nodeid((Tree)p); - fprint(f, "%c%d%s", lev == 0 ? '\'' : '#', id, - &" "[id < 10 ? 0 : id < 100 ? 1 : 2]); - fprint(f, "%s count=%d", opname(p->op), p->count); - for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++) - fprint(f, " #%d", nodeid((Tree)p->kids[i])); - if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type) - fprint(f, " {%t}", p->syms[0]->type); - else - for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++) - if (p->syms[i]->name) - fprint(f, " %s", p->syms[i]->name); - else - fprint(f, " %p", p->syms[i]); - fprint(f, "\n"); - } +static void printnode(Node p, int fd, int lev) +{ + if (p) + { + FILE *f = fd == 1 ? stdout : stderr; + int i, id = nodeid((Tree)p); + fprint(f, "%c%d%s", lev == 0 ? '\'' : '#', id, &" "[id < 10 ? 0 : id < 100 ? 1 : 2]); + fprint(f, "%s count=%d", opname(p->op), p->count); + for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++) fprint(f, " #%d", nodeid((Tree)p->kids[i])); + if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type) + fprint(f, " {%t}", p->syms[0]->type); + else + for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++) + if (p->syms[i]->name) + fprint(f, " %s", p->syms[i]->name); + else + fprint(f, " %p", p->syms[i]); + fprint(f, "\n"); + } } /* typestab - emit stab entries for p */ -static void typestab(Symbol p, void *cl) { - if (!isfunc(p->type) && (p->sclass == EXTERN || p->sclass == STATIC) && IR->stabsym) - (*IR->stabsym)(p); - else if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype) - (*IR->stabtype)(p); +static void typestab(Symbol p, void *cl) +{ + if (!isfunc(p->type) && (p->sclass == EXTERN || p->sclass == STATIC) && IR->stabsym) + (*IR->stabsym)(p); + else if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype) + (*IR->stabtype)(p); } - diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt new file mode 100644 index 0000000..18ddfe1 --- /dev/null +++ b/src/ui/CMakeLists.txt @@ -0,0 +1,67 @@ +# +## _ _ ___ ____ _ +## | | | |_ _| / ___|___ __| | ___ +## | | | || | | | / _ \ / _` |/ _ \ +## | |_| || | | |__| (_) | (_| | __/ +## \___/|___| \____\___/ \__,_|\___| +## +# + +set(CMAKE_INSTALL_NAME_DIR ${PROJECT_BINARY_DIR}/gpp) + +set(BG_SOURCE_DIR ../game) +set(QC_SOURCE_DIR ../qcommon) +set(RC_SOURCE_DIR ../renderercommon) +set(CLIENT_SOURCE_DIR ../client) + +add_definitions( -DUI ) + +#add_custom_command( +# OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/menudef.h +# COMMAND ${CMAKE_COMMAND} +# ARGS -E copy ${CMAKE_BINARY_DIR}/assets/ui/menudef.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/menudef.h +# DEPENDS ${CMAKE_BINARY_DIR}/assets/ui/menudef.h +# ) +#set_source_files_properties(menudef.h PROPERTIES GENERATED TRUE) + +set(UI_SOURCES + ui_main.c # Must be listed first! + ui_atoms.c + ui_gameinfo.c + ui_local.h + ui_shared.c + ui_shared.h + ${CMAKE_SOURCE_DIR}/assets/ui/menudef.h + ${BG_SOURCE_DIR}/bg_lib.h + ${BG_SOURCE_DIR}/bg_public.h + ${BG_SOURCE_DIR}/bg_alloc.c + ${BG_SOURCE_DIR}/bg_lib.c + ${BG_SOURCE_DIR}/bg_misc.c + ${BG_SOURCE_DIR}/bg_voice.c + ${QC_SOURCE_DIR}/q_shared.h + ${QC_SOURCE_DIR}/q_shared.c + ${QC_SOURCE_DIR}/q_math.c + ${RC_SOURCE_DIR}/tr_types.h + ${CLIENT_SOURCE_DIR}/keycodes.h + ) + +add_library( + ui SHARED + ${UI_SOURCES} + ui_syscalls.c + ) + +target_include_directories( + ui PUBLIC + ${QC_SOURCE_DIR} + ${BG_SOURCE_DIR} + ) + +include(${CMAKE_SOURCE_DIR}/cmake/AddQVM.cmake) +add_qvm( ui ${UI_SOURCES} ui_syscalls.asm ) + +add_custom_command( + TARGET ui POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_CURRENT_BINARY_DIR}/libui${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gpp/ui${CMAKE_SHARED_LIBRARY_SUFFIX} + ) diff --git a/src/ui/menudef.h b/src/ui/menudef.h deleted file mode 100644 index dbd0996..0000000 --- a/src/ui/menudef.h +++ /dev/null @@ -1,363 +0,0 @@ - -#define ITEM_TYPE_TEXT 0 // simple text -#define ITEM_TYPE_BUTTON 1 // button, basically text with a border -#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped -#define ITEM_TYPE_CHECKBOX 3 // check box -#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar -#define ITEM_TYPE_SAYFIELD 5 // the chat field -#define ITEM_TYPE_COMBO 6 // drop down list -#define ITEM_TYPE_LISTBOX 7 // scrollable list -#define ITEM_TYPE_MODEL 8 // model -#define ITEM_TYPE_OWNERDRAW 9 // owner draw, name specs what it is -#define ITEM_TYPE_NUMERICFIELD 10 // editable text, associated with a cvar -#define ITEM_TYPE_SLIDER 11 // mouse speed, volume, etc. -#define ITEM_TYPE_YESNO 12 // yes no cvar setting -#define ITEM_TYPE_MULTI 13 // multiple list setting, enumerated -#define ITEM_TYPE_BIND 14 // multiple list setting, enumerated - -#define ITEM_ALIGN_LEFT 0 // left alignment -#define ITEM_ALIGN_CENTER 1 // center alignment -#define ITEM_ALIGN_RIGHT 2 // right alignment - -#define ITEM_TEXTSTYLE_NORMAL 0 // normal text -#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking -#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing -#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this ) -#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this ) -#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this ) -#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this ) -#define ITEM_TEXTSTYLE_NEON 7 // drop shadow ( need a color for this ) - -#define WINDOW_BORDER_NONE 0 // no border -#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel ) -#define WINDOW_BORDER_HORZ 2 // horizontal borders only -#define WINDOW_BORDER_VERT 3 // vertical borders only -#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars - -#define WINDOW_STYLE_EMPTY 0 // no background -#define WINDOW_STYLE_FILLED 1 // filled with background color -#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color -#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color -#define WINDOW_STYLE_TEAMCOLOR 4 // team color -#define WINDOW_STYLE_CINEMATIC 5 // cinematic - -#define MENU_TRUE 1 // uh.. true -#define MENU_FALSE 0 // and false - -#define HUD_VERTICAL 0x00 -#define HUD_HORIZONTAL 0x01 - -// list box element types -#define LISTBOX_TEXT 0x00 -#define LISTBOX_IMAGE 0x01 - -// list feeders -#define FEEDER_HEADS 0x00 // model heads -#define FEEDER_MAPS 0x01 // text maps based on game type -#define FEEDER_SERVERS 0x02 // servers -#define FEEDER_CLANS 0x03 // clan names -#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format -#define FEEDER_ALIENTEAM_LIST 0x05 // red team members -#define FEEDER_HUMANTEAM_LIST 0x06 // blue team members -#define FEEDER_PLAYER_LIST 0x07 // players -#define FEEDER_TEAM_LIST 0x08 // team members for team voting -#define FEEDER_MODS 0x09 // team members for team voting -#define FEEDER_DEMOS 0x0a // team members for team voting -#define FEEDER_SCOREBOARD 0x0b // team members for team voting -#define FEEDER_Q3HEADS 0x0c // model heads -#define FEEDER_SERVERSTATUS 0x0d // server status -#define FEEDER_FINDPLAYER 0x0e // find player -#define FEEDER_CINEMATICS 0x0f // cinematics - -//TA: tremulous menus -#define FEEDER_TREMTEAMS 0x10 //teams -#define FEEDER_TREMALIENCLASSES 0x11 //alien classes -#define FEEDER_TREMHUMANITEMS 0x12 //human items -#define FEEDER_TREMHUMANARMOURYBUY 0x13 //human buy -#define FEEDER_TREMHUMANARMOURYSELL 0x14 //human sell -#define FEEDER_TREMALIENUPGRADE 0x15 //alien upgrade -#define FEEDER_TREMALIENBUILD 0x16 //alien buildables -#define FEEDER_TREMHUMANBUILD 0x17 //human buildables -//TA: tremulous menus -#define FEEDER_IGNORE_LIST 0x18 //ignored players - -// display flags -#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001 -#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002 -#define CG_SHOW_ANYTEAMGAME 0x00000004 -#define CG_SHOW_HARVESTER 0x00000008 -#define CG_SHOW_ONEFLAG 0x00000010 -#define CG_SHOW_CTF 0x00000020 -#define CG_SHOW_OBELISK 0x00000040 -#define CG_SHOW_HEALTHCRITICAL 0x00000080 -#define CG_SHOW_SINGLEPLAYER 0x00000100 -#define CG_SHOW_TOURNAMENT 0x00000200 -#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400 -#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800 -#define CG_SHOW_LANPLAYONLY 0x00001000 -#define CG_SHOW_MINED 0x00002000 -#define CG_SHOW_HEALTHOK 0x00004000 -#define CG_SHOW_TEAMINFO 0x00008000 -#define CG_SHOW_NOTEAMINFO 0x00010000 -#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000 -#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000 -#define CG_SHOW_ANYNONTEAMGAME 0x00080000 -#define CG_SHOW_2DONLY 0x10000000 - - -#define UI_SHOW_LEADER 0x00000001 -#define UI_SHOW_NOTLEADER 0x00000002 -#define UI_SHOW_FAVORITESERVERS 0x00000004 -#define UI_SHOW_ANYNONTEAMGAME 0x00000008 -#define UI_SHOW_ANYTEAMGAME 0x00000010 -#define UI_SHOW_NEWHIGHSCORE 0x00000020 -#define UI_SHOW_DEMOAVAILABLE 0x00000040 -#define UI_SHOW_NEWBESTTIME 0x00000080 -#define UI_SHOW_FFA 0x00000100 -#define UI_SHOW_NOTFFA 0x00000200 -#define UI_SHOW_NETANYNONTEAMGAME 0x00000400 -#define UI_SHOW_NETANYTEAMGAME 0x00000800 -#define UI_SHOW_NOTFAVORITESERVERS 0x00001000 - -#define UI_SHOW_VOTEACTIVE 0x00002000 -#define UI_SHOW_CANVOTE 0x00004000 -#define UI_SHOW_TEAMVOTEACTIVE 0x00008000 -#define UI_SHOW_CANTEAMVOTE 0x00010000 - -#define UI_SHOW_NOTSPECTATING 0x00020000 - -// owner draw types -// ideally these should be done outside of this file but -// this makes it much easier for the macro expansion to -// convert them for the designers ( from the .menu files ) -#define CG_OWNERDRAW_BASE 1 -#define CG_PLAYER_ARMOR_ICON 1 -#define CG_PLAYER_ARMOR_VALUE 2 -#define CG_PLAYER_HEAD 3 -#define CG_PLAYER_HEALTH 4 -#define CG_PLAYER_HEALTH_BAR 92 -#define CG_PLAYER_HEALTH_CROSS 99 -#define CG_PLAYER_AMMO_ICON 5 -#define CG_PLAYER_AMMO_VALUE 6 -#define CG_PLAYER_CLIPS_VALUE 70 -#define CG_PLAYER_BUILD_TIMER 115 -#define CG_PLAYER_CREDITS_VALUE 71 -#define CG_PLAYER_BANK_VALUE 72 -#define CG_PLAYER_CREDITS_VALUE_NOPAD 106 -#define CG_PLAYER_BANK_VALUE_NOPAD 107 -#define CG_PLAYER_STAMINA 73 -#define CG_PLAYER_STAMINA_1 93 -#define CG_PLAYER_STAMINA_2 94 -#define CG_PLAYER_STAMINA_3 95 -#define CG_PLAYER_STAMINA_4 96 -#define CG_PLAYER_STAMINA_BOLT 97 -#define CG_PLAYER_BOOST_BOLT 112 -#define CG_PLAYER_CLIPS_RING 98 -#define CG_PLAYER_BUILD_TIMER_RING 113 -#define CG_PLAYER_SELECT 74 -#define CG_PLAYER_SELECTTEXT 75 -#define CG_PLAYER_WEAPONICON 111 -#define CG_PLAYER_WALLCLIMBING 103 -#define CG_PLAYER_BOOSTED 104 -#define CG_PLAYER_POISON_BARBS 105 -#define CG_PLAYER_ALIEN_SENSE 108 -#define CG_PLAYER_HUMAN_SCANNER 109 -#define CG_PLAYER_USABLE_BUILDABLE 110 -#define CG_SELECTEDPLAYER_HEAD 7 -#define CG_SELECTEDPLAYER_NAME 8 -#define CG_SELECTEDPLAYER_LOCATION 9 -#define CG_SELECTEDPLAYER_STATUS 10 -#define CG_SELECTEDPLAYER_WEAPON 11 -#define CG_SELECTEDPLAYER_POWERUP 12 - -#define CG_FLAGCARRIER_HEAD 13 -#define CG_FLAGCARRIER_NAME 14 -#define CG_FLAGCARRIER_LOCATION 15 -#define CG_FLAGCARRIER_STATUS 16 -#define CG_FLAGCARRIER_WEAPON 17 -#define CG_FLAGCARRIER_POWERUP 18 - -#define CG_PLAYER_ITEM 19 -#define CG_PLAYER_SCORE 20 - -#define CG_BLUE_FLAGHEAD 21 -#define CG_BLUE_FLAGSTATUS 22 -#define CG_BLUE_FLAGNAME 23 -#define CG_RED_FLAGHEAD 24 -#define CG_RED_FLAGSTATUS 25 -#define CG_RED_FLAGNAME 26 - -#define CG_BLUE_SCORE 27 -#define CG_RED_SCORE 28 -#define CG_RED_NAME 29 -#define CG_BLUE_NAME 30 -#define CG_HARVESTER_SKULLS 31 // only shows in harvester -#define CG_ONEFLAG_STATUS 32 // only shows in one flag -#define CG_PLAYER_LOCATION 33 -#define CG_TEAM_COLOR 34 -#define CG_CTF_POWERUP 35 - -#define CG_AREA_POWERUP 36 -#define CG_AREA_LAGOMETER 37 // painted with old system -#define CG_PLAYER_HASFLAG 38 -#define CG_GAME_TYPE 39 // not done - -#define CG_SELECTEDPLAYER_ARMOR 40 -#define CG_SELECTEDPLAYER_HEALTH 41 -#define CG_PLAYER_STATUS 42 -#define CG_FRAGGED_MSG 43 // painted with old system -#define CG_PROXMINED_MSG 44 // painted with old system -#define CG_AREA_FPSINFO 45 // painted with old system -#define CG_GAME_STATUS 49 -#define CG_KILLER 50 -#define CG_PLAYER_ARMOR_ICON2D 51 -#define CG_PLAYER_AMMO_ICON2D 52 -#define CG_ACCURACY 53 -#define CG_ASSISTS 54 -#define CG_DEFEND 55 -#define CG_EXCELLENT 56 -#define CG_IMPRESSIVE 57 -#define CG_PERFECT 58 -#define CG_GAUNTLET 59 -#define CG_SPECTATORS 60 -#define CG_TEAMINFO 61 -#define CG_VOICE_HEAD 62 -#define CG_VOICE_NAME 63 -#define CG_PLAYER_HASFLAG2D 64 -#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester -#define CG_CAPFRAGLIMIT 66 -#define CG_1STPLACE 67 -#define CG_2NDPLACE 68 -#define CG_CAPTURES 69 - -//TA: loading screen -#define CG_LOAD_LEVELSHOT 76 -#define CG_LOAD_MEDIA 77 -#define CG_LOAD_MEDIA_LABEL 78 -#define CG_LOAD_BUILDABLES 79 -#define CG_LOAD_BUILDABLES_LABEL 80 -#define CG_LOAD_CHARMODEL 81 -#define CG_LOAD_CHARMODEL_LABEL 82 -#define CG_LOAD_OVERALL 83 -#define CG_LOAD_LEVELNAME 84 -#define CG_LOAD_MOTD 85 -#define CG_LOAD_HOSTNAME 86 - -#define CG_FPS 87 -#define CG_FPS_FIXED 100 -#define CG_TIMER 88 -#define CG_TIMER_MINS 101 -#define CG_TIMER_SECS 102 -#define CG_SNAPSHOT 89 -#define CG_LAGOMETER 90 -#define CG_PLAYER_CROSSHAIRNAMES 114 -#define CG_STAGE_REPORT_TEXT 116 -#define CG_DEMO_PLAYBACK 117 -#define CG_DEMO_RECORDING 118 - -#define CG_CONSOLE 91 -#define CG_TUTORIAL 119 -#define CG_CLOCK 120 - - - -#define UI_OWNERDRAW_BASE 200 -#define UI_HANDICAP 200 -#define UI_PLAYERMODEL 202 -#define UI_CLANNAME 203 -#define UI_CLANLOGO 204 -#define UI_GAMETYPE 205 -#define UI_MAPPREVIEW 206 -#define UI_SKILL 207 -#define UI_BLUETEAMNAME 208 -#define UI_REDTEAMNAME 209 -#define UI_BLUETEAM1 210 -#define UI_BLUETEAM2 211 -#define UI_BLUETEAM3 212 -#define UI_BLUETEAM4 213 -#define UI_BLUETEAM5 214 -#define UI_REDTEAM1 215 -#define UI_REDTEAM2 216 -#define UI_REDTEAM3 217 -#define UI_REDTEAM4 218 -#define UI_REDTEAM5 219 -#define UI_NETSOURCE 220 -#define UI_NETMAPPREVIEW 221 -#define UI_NETFILTER 222 -#define UI_TIER 223 -#define UI_OPPONENTMODEL 224 -#define UI_TIERMAP1 225 -#define UI_TIERMAP2 226 -#define UI_TIERMAP3 227 -#define UI_PLAYERLOGO 228 -#define UI_OPPONENTLOGO 229 -#define UI_PLAYERLOGO_METAL 230 -#define UI_OPPONENTLOGO_METAL 231 -#define UI_PLAYERLOGO_NAME 232 -#define UI_OPPONENTLOGO_NAME 233 -#define UI_TIER_MAPNAME 234 -#define UI_TIER_GAMETYPE 235 -#define UI_ALLMAPS_SELECTION 236 -#define UI_OPPONENT_NAME 237 -#define UI_VOTE_KICK 238 -#define UI_BOTNAME 239 -#define UI_BOTSKILL 240 -#define UI_REDBLUE 241 -#define UI_SELECTEDPLAYER 243 -#define UI_MAPCINEMATIC 244 -#define UI_NETGAMETYPE 245 -#define UI_NETMAPCINEMATIC 246 -#define UI_SERVERREFRESHDATE 247 -#define UI_SERVERMOTD 248 -#define UI_GLINFO 249 -#define UI_KEYBINDSTATUS 250 -#define UI_CLANCINEMATIC 251 -#define UI_MAP_TIMETOBEAT 252 -#define UI_JOINGAMETYPE 253 -#define UI_PREVIEWCINEMATIC 254 -#define UI_STARTMAPCINEMATIC 255 -#define UI_MAPS_SELECTION 256 - -//TA: -//#define UI_DIALOG 257 -#define UI_TEAMINFOPANE 258 -#define UI_ACLASSINFOPANE 259 -#define UI_AUPGRADEINFOPANE 260 -#define UI_HITEMINFOPANE 261 -#define UI_HBUYINFOPANE 262 -#define UI_HSELLINFOPANE 263 -#define UI_ABUILDINFOPANE 264 -#define UI_HBUILDINFOPANE 265 - -#define UI_PLAYERLIST_SELECTION 266 -#define UI_TEAMLIST_SELECTION 267 - -#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag -#define VOICECHAT_OFFENSE "offense" // command someone to go on offense -#define VOICECHAT_DEFEND "defend" // command someone to go on defense -#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag -#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam) -#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one) -#define VOICECHAT_FOLLOWME "followme" // command someone to follow you -#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag -#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier -#define VOICECHAT_YES "yes" // yes, affirmative, etc. -#define VOICECHAT_NO "no" // no, negative, etc. -#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag -#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense -#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense -#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming) -#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere -#define VOICECHAT_ONFOLLOW "onfollow" // I'm following -#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier -#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag -#define VOICECHAT_INPOSITION "inposition" // I'm in position -#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag -#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack -#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF) -#define VOICECHAT_STARTLEADER "startleader" // I'm the leader -#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership -#define VOICECHAT_TRASH "trash" // lots of trash talk -#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader -#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense -#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c index a3033c4..b70d34d 100644 --- a/src/ui/ui_atoms.c +++ b/src/ui/ui_atoms.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -28,43 +29,46 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA **********************************************************************/ #include "ui_local.h" -qboolean m_entersound; // after a frame, so caching won't disrupt the sound +qboolean m_entersound; // after a frame, so caching won't disrupt the sound -void QDECL Com_Error( int level, const char *error, ... ) { - va_list argptr; - char text[1024]; +void QDECL Com_Error(int level, const char *error, ...) +{ + va_list argptr; + char text[1024]; - va_start (argptr, error); - vsprintf (text, error, argptr); - va_end (argptr); + va_start(argptr, error); + Q_vsnprintf(text, sizeof(text), error, argptr); + va_end(argptr); - trap_Error( va("%s", text) ); + trap_Error(text); } -void QDECL Com_Printf( const char *msg, ... ) { - va_list argptr; - char text[1024]; +void QDECL Com_Printf(const char *msg, ...) +{ + va_list argptr; + char text[1024]; - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); + va_start(argptr, msg); + Q_vsnprintf(text, sizeof(text), msg, argptr); + va_end(argptr); - trap_Print( va("%s", text) ); + trap_Print(text); } -qboolean newUI = qfalse; - - /* ================= UI_ClampCvar ================= */ -float UI_ClampCvar( float min, float max, float value ) +float UI_ClampCvar(float min, float max, float value) { - if ( value < min ) return min; - if ( value > max ) return max; - return value; + if (value < min) + return min; + + if (value > max) + return max; + + return value; } /* @@ -72,418 +76,196 @@ float UI_ClampCvar( float min, float max, float value ) UI_StartDemoLoop ================= */ -void UI_StartDemoLoop( void ) { - trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" ); -} +void UI_StartDemoLoop(void) { trap_Cmd_ExecuteText(EXEC_APPEND, "d1\n"); } -char *UI_Argv( int arg ) { - static char buffer[MAX_STRING_CHARS]; +char *UI_Argv(int arg) +{ + static char buffer[MAX_STRING_CHARS]; - trap_Argv( arg, buffer, sizeof( buffer ) ); + trap_Argv(arg, buffer, sizeof(buffer)); - return buffer; + return buffer; } +char *UI_ConcatArgs(int arg, char *buf, int len) +{ + char *p; + int c; -char *UI_Cvar_VariableString( const char *var_name ) { - static char buffer[MAX_STRING_CHARS]; + if (len <= 0) + return buf; - trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) ); + p = buf; + c = trap_Argc(); - return buffer; -} + for (; arg < c; arg++) + { + char *argp = UI_Argv(arg); + while (*argp && p < &buf[len - 1]) + *p++ = *argp++; + if (p < &buf[len - 2]) + *p++ = ' '; + else + break; + } -void UI_SetBestScores(postGameInfo_t *newInfo, qboolean postGame) { - trap_Cvar_Set("ui_scoreAccuracy", va("%i%%", newInfo->accuracy)); - trap_Cvar_Set("ui_scoreImpressives", va("%i", newInfo->impressives)); - trap_Cvar_Set("ui_scoreExcellents", va("%i", newInfo->excellents)); - trap_Cvar_Set("ui_scoreDefends", va("%i", newInfo->defends)); - trap_Cvar_Set("ui_scoreAssists", va("%i", newInfo->assists)); - trap_Cvar_Set("ui_scoreGauntlets", va("%i", newInfo->gauntlets)); - trap_Cvar_Set("ui_scoreScore", va("%i", newInfo->score)); - trap_Cvar_Set("ui_scorePerfect", va("%i", newInfo->perfects)); - trap_Cvar_Set("ui_scoreTeam", va("%i to %i", newInfo->redScore, newInfo->blueScore)); - trap_Cvar_Set("ui_scoreBase", va("%i", newInfo->baseScore)); - trap_Cvar_Set("ui_scoreTimeBonus", va("%i", newInfo->timeBonus)); - trap_Cvar_Set("ui_scoreSkillBonus", va("%i", newInfo->skillBonus)); - trap_Cvar_Set("ui_scoreShutoutBonus", va("%i", newInfo->shutoutBonus)); - trap_Cvar_Set("ui_scoreTime", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60)); - trap_Cvar_Set("ui_scoreCaptures", va("%i", newInfo->captures)); - if (postGame) { - trap_Cvar_Set("ui_scoreAccuracy2", va("%i%%", newInfo->accuracy)); - trap_Cvar_Set("ui_scoreImpressives2", va("%i", newInfo->impressives)); - trap_Cvar_Set("ui_scoreExcellents2", va("%i", newInfo->excellents)); - trap_Cvar_Set("ui_scoreDefends2", va("%i", newInfo->defends)); - trap_Cvar_Set("ui_scoreAssists2", va("%i", newInfo->assists)); - trap_Cvar_Set("ui_scoreGauntlets2", va("%i", newInfo->gauntlets)); - trap_Cvar_Set("ui_scoreScore2", va("%i", newInfo->score)); - trap_Cvar_Set("ui_scorePerfect2", va("%i", newInfo->perfects)); - trap_Cvar_Set("ui_scoreTeam2", va("%i to %i", newInfo->redScore, newInfo->blueScore)); - trap_Cvar_Set("ui_scoreBase2", va("%i", newInfo->baseScore)); - trap_Cvar_Set("ui_scoreTimeBonus2", va("%i", newInfo->timeBonus)); - trap_Cvar_Set("ui_scoreSkillBonus2", va("%i", newInfo->skillBonus)); - trap_Cvar_Set("ui_scoreShutoutBonus2", va("%i", newInfo->shutoutBonus)); - trap_Cvar_Set("ui_scoreTime2", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60)); - trap_Cvar_Set("ui_scoreCaptures2", va("%i", newInfo->captures)); - } -} + *p = '\0'; -void UI_LoadBestScores(const char *map, int game) { - char fileName[MAX_QPATH]; - fileHandle_t f; - postGameInfo_t newInfo; - memset(&newInfo, 0, sizeof(postGameInfo_t)); - Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game); - if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { - int size = 0; - trap_FS_Read(&size, sizeof(int), f); - if (size == sizeof(postGameInfo_t)) { - trap_FS_Read(&newInfo, sizeof(postGameInfo_t), f); - } - trap_FS_FCloseFile(f); - } - UI_SetBestScores(&newInfo, qfalse); - - Com_sprintf(fileName, MAX_QPATH, "demos/%s_%d.dm_%d", map, game, (int)trap_Cvar_VariableValue("protocol")); - uiInfo.demoAvailable = qfalse; - if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { - uiInfo.demoAvailable = qtrue; - trap_FS_FCloseFile(f); - } + return buf; } -/* -=============== -UI_ClearScores -=============== -*/ -void UI_ClearScores( void ) { - char gameList[4096]; - char *gameFile; - int i, len, count, size; - fileHandle_t f; - postGameInfo_t newInfo; - - count = trap_FS_GetFileList( "games", "game", gameList, sizeof(gameList) ); - - size = sizeof(postGameInfo_t); - memset(&newInfo, 0, size); - - if (count > 0) { - gameFile = gameList; - for ( i = 0; i < count; i++ ) { - len = strlen(gameFile); - if (trap_FS_FOpenFile(va("games/%s",gameFile), &f, FS_WRITE) >= 0) { - trap_FS_Write(&size, sizeof(int), f); - trap_FS_Write(&newInfo, size, f); - trap_FS_FCloseFile(f); - } - gameFile += len + 1; - } - } +char *UI_Cvar_VariableString(const char *var_name) +{ + static char buffer[MAX_STRING_CHARS]; - UI_SetBestScores(&newInfo, qfalse); + trap_Cvar_VariableStringBuffer(var_name, buffer, sizeof(buffer)); + return buffer; } +static void UI_Cache_f(void) { Display_CacheAll(); } - -static void UI_Cache_f( void ) { - Display_CacheAll(); +static void UI_Menu_f(void) +{ + if (Menu_Count() > 0) + { + trap_Key_SetCatcher(KEYCATCH_UI); + Menus_ActivateByName(UI_Argv(1)); + } } -/* -======================= -UI_CalcPostGameStats -======================= -*/ -static void UI_CalcPostGameStats( void ) { - char map[MAX_QPATH]; - char fileName[MAX_QPATH]; - char info[MAX_INFO_STRING]; - fileHandle_t f; - int size, game, time, adjustedTime; - postGameInfo_t oldInfo; - postGameInfo_t newInfo; - qboolean newHigh = qfalse; - - trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); - Q_strncpyz( map, Info_ValueForKey( info, "mapname" ), sizeof(map) ); - game = atoi(Info_ValueForKey(info, "g_gametype")); - - // compose file name - Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game); - // see if we have one already - memset(&oldInfo, 0, sizeof(postGameInfo_t)); - if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { - // if so load it - size = 0; - trap_FS_Read(&size, sizeof(int), f); - if (size == sizeof(postGameInfo_t)) { - trap_FS_Read(&oldInfo, sizeof(postGameInfo_t), f); - } - trap_FS_FCloseFile(f); - } - - newInfo.accuracy = atoi(UI_Argv(3)); - newInfo.impressives = atoi(UI_Argv(4)); - newInfo.excellents = atoi(UI_Argv(5)); - newInfo.defends = atoi(UI_Argv(6)); - newInfo.assists = atoi(UI_Argv(7)); - newInfo.gauntlets = atoi(UI_Argv(8)); - newInfo.baseScore = atoi(UI_Argv(9)); - newInfo.perfects = atoi(UI_Argv(10)); - newInfo.redScore = atoi(UI_Argv(11)); - newInfo.blueScore = atoi(UI_Argv(12)); - time = atoi(UI_Argv(13)); - newInfo.captures = atoi(UI_Argv(14)); - - newInfo.time = (time - trap_Cvar_VariableValue("ui_matchStartTime")) / 1000; - adjustedTime = uiInfo.mapList[ui_currentMap.integer].timeToBeat[game]; - if (newInfo.time < adjustedTime) { - newInfo.timeBonus = (adjustedTime - newInfo.time) * 10; - } else { - newInfo.timeBonus = 0; - } - - if (newInfo.redScore > newInfo.blueScore && newInfo.blueScore <= 0) { - newInfo.shutoutBonus = 100; - } else { - newInfo.shutoutBonus = 0; - } - - newInfo.skillBonus = trap_Cvar_VariableValue("g_spSkill"); - if (newInfo.skillBonus <= 0) { - newInfo.skillBonus = 1; - } - newInfo.score = newInfo.baseScore + newInfo.shutoutBonus + newInfo.timeBonus; - newInfo.score *= newInfo.skillBonus; - - // see if the score is higher for this one - newHigh = (newInfo.redScore > newInfo.blueScore && newInfo.score > oldInfo.score); - - if (newHigh) { - // if so write out the new one - uiInfo.newHighScoreTime = uiInfo.uiDC.realTime + 20000; - if (trap_FS_FOpenFile(fileName, &f, FS_WRITE) >= 0) { - size = sizeof(postGameInfo_t); - trap_FS_Write(&size, sizeof(int), f); - trap_FS_Write(&newInfo, sizeof(postGameInfo_t), f); - trap_FS_FCloseFile(f); +static void UI_CloseMenus_f(void) +{ + if (Menu_Count() > 0) + { + trap_Key_SetCatcher(trap_Key_GetCatcher() & ~KEYCATCH_UI); + trap_Key_ClearStates(); + trap_Cvar_Set("cl_paused", "0"); + Menus_CloseAll(); } - } +} - if (newInfo.time < oldInfo.time) { - uiInfo.newBestTime = uiInfo.uiDC.realTime + 20000; - } +static void UI_MessageMode_f(void) +{ + char *arg = UI_Argv(0); - // put back all the ui overrides - trap_Cvar_Set("capturelimit", UI_Cvar_VariableString("ui_saveCaptureLimit")); - trap_Cvar_Set("fraglimit", UI_Cvar_VariableString("ui_saveFragLimit")); - trap_Cvar_Set("cg_drawTimer", UI_Cvar_VariableString("ui_drawTimer")); - trap_Cvar_Set("g_doWarmup", UI_Cvar_VariableString("ui_doWarmup")); - trap_Cvar_Set("g_Warmup", UI_Cvar_VariableString("ui_Warmup")); - trap_Cvar_Set("sv_pure", UI_Cvar_VariableString("ui_pure")); - trap_Cvar_Set("g_friendlyFire", UI_Cvar_VariableString("ui_friendlyFire")); + trap_Cvar_Set("ui_sayBuffer", ""); - UI_SetBestScores(&newInfo, qtrue); - UI_ShowPostGame(newHigh); + switch (arg[11]) + { + default: + case '\0': + // Global + uiInfo.chatTeam = qfalse; + break; + + case '2': + // Team + uiInfo.chatTeam = qtrue; + break; + } + trap_Key_SetCatcher(KEYCATCH_UI); + Menus_CloseByName("say"); + Menus_CloseByName("say_team"); + if (uiInfo.chatTeam) + Menus_ActivateByName("say_team"); + else + Menus_ActivateByName("say"); } -static void UI_MessageMode_f( void ) +static void UI_Me_f(void) { - char *arg = UI_Argv( 0 ); - - trap_Cvar_Set( "ui_sayBuffer", "" ); - - switch( arg[ 11 ] ) - { - default: - case '\0': - // Global - uiInfo.chatTeam = qfalse; - break; - - case '2': - // Team - uiInfo.chatTeam = qtrue; - break; - } - - trap_Key_SetCatcher( KEYCATCH_UI ); - Menus_CloseByName( "say" ); - Menus_CloseByName( "say_team" ); - - if( uiInfo.chatTeam ) - Menus_ActivateByName( "say_team" ); - else - Menus_ActivateByName( "say" ); + char buf[MAX_SAY_TEXT - 4]; + + UI_ConcatArgs(1, buf, sizeof(buf)); + + trap_Cmd_ExecuteText(EXEC_APPEND, va("say \"/me %s\"\n", buf)); } +struct uicmd { + char *cmd; + void (*function)(void); +} commands[] = +{ + {"closemenus", UI_CloseMenus_f}, + {"me", UI_Me_f}, + {"menu", UI_Menu_f}, + {"messagemode", UI_MessageMode_f}, + {"messagemode2", UI_MessageMode_f}, + {"ui_cache", UI_Cache_f}, + {"ui_load", UI_Load}, + {"ui_report", UI_Report} +}; /* ================= UI_ConsoleCommand ================= */ -qboolean UI_ConsoleCommand( int realTime ) +qboolean UI_ConsoleCommand(int realTime) { - char *cmd; - char *arg1; - - uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime; - uiInfo.uiDC.realTime = realTime; - - cmd = UI_Argv( 0 ); - - // ensure minimum menu data is available - //Menu_Cache(); - - if ( Q_stricmp (cmd, "ui_test") == 0 ) { - UI_ShowPostGame(qtrue); - } - - if ( Q_stricmp (cmd, "ui_report") == 0 ) { - UI_Report(); - return qtrue; - } - - if ( Q_stricmp (cmd, "ui_load") == 0 ) { - UI_Load(); - return qtrue; - } - - if ( Q_stricmp (cmd, "remapShader") == 0 ) { - if (trap_Argc() == 4) { - char shader1[MAX_QPATH]; - char shader2[MAX_QPATH]; - Q_strncpyz(shader1, UI_Argv(1), sizeof(shader1)); - Q_strncpyz(shader2, UI_Argv(2), sizeof(shader2)); - trap_R_RemapShader(shader1, shader2, UI_Argv(3)); - return qtrue; - } - } - - if ( Q_stricmp (cmd, "postgame") == 0 ) { - UI_CalcPostGameStats(); - return qtrue; - } + struct uicmd *cmd = bsearch(UI_Argv(0), commands, ARRAY_LEN(commands), sizeof(commands[0]), cmdcmp); - if ( Q_stricmp (cmd, "ui_cache") == 0 ) { - UI_Cache_f(); - return qtrue; - } + uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realTime; - if ( Q_stricmp (cmd, "ui_teamOrders") == 0 ) { - //UI_TeamOrdersMenu_f(); - return qtrue; - } - - if( Q_stricmp ( cmd, "menu" ) == 0 ) - { - arg1 = UI_Argv( 1 ); - - if( Menu_Count( ) > 0 ) + if (cmd) { - trap_Key_SetCatcher( KEYCATCH_UI ); - Menus_ActivateByName( arg1 ); - return qtrue; + cmd->function(); + return qtrue; } - } - - if( Q_stricmp ( cmd, "closemenus" ) == 0 ) - { - if( Menu_Count( ) > 0 ) - { - trap_Key_SetCatcher( trap_Key_GetCatcher( ) & ~KEYCATCH_UI ); - trap_Key_ClearStates( ); - trap_Cvar_Set( "cl_paused", "0" ); - Menus_CloseAll( ); - return qtrue; - } - } - - if( Q_stricmp ( cmd, "messagemode" ) == 0 || - Q_stricmp ( cmd, "messagemode2" ) == 0 ) - { - UI_MessageMode_f(); - return qtrue; - } - - return qfalse; -} -/* -================= -UI_Shutdown -================= -*/ -void UI_Shutdown( void ) { + return qfalse; } -/* -================ -UI_AdjustFrom640 - -Adjusted for resolution and screen aspect ratio -================ -*/ -void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) { - // expect valid pointers -#if 0 - *x = *x * uiInfo.uiDC.scale + uiInfo.uiDC.bias; - *y *= uiInfo.uiDC.scale; - *w *= uiInfo.uiDC.scale; - *h *= uiInfo.uiDC.scale; -#endif - - *x *= uiInfo.uiDC.xscale; - *y *= uiInfo.uiDC.yscale; - *w *= uiInfo.uiDC.xscale; - *h *= uiInfo.uiDC.yscale; +void UI_DrawNamedPic(float x, float y, float width, float height, const char *picname) +{ + qhandle_t hShader; + hShader = trap_R_RegisterShaderNoMip(picname); + UI_AdjustFrom640(&x, &y, &width, &height); + trap_R_DrawStretchPic(x, y, width, height, 0, 0, 1, 1, hShader); } -void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { - qhandle_t hShader; +void UI_DrawHandlePic(float x, float y, float w, float h, qhandle_t hShader) +{ + float s0; + float s1; + float t0; + float t1; + + if (w < 0) + { + // flip about vertical + w = -w; + s0 = 1; + s1 = 0; + } + else + { + s0 = 0; + s1 = 1; + } - hShader = trap_R_RegisterShaderNoMip( picname ); - UI_AdjustFrom640( &x, &y, &width, &height ); - trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); -} + if (h < 0) + { + // flip about horizontal + h = -h; + t0 = 1; + t1 = 0; + } + else + { + t0 = 0; + t1 = 1; + } -void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) { - float s0; - float s1; - float t0; - float t1; - - if( w < 0 ) { // flip about vertical - w = -w; - s0 = 1; - s1 = 0; - } - else { - s0 = 0; - s1 = 1; - } - - if( h < 0 ) { // flip about horizontal - h = -h; - t0 = 1; - t1 = 0; - } - else { - t0 = 0; - t1 = 1; - } - - UI_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader ); + UI_AdjustFrom640(&x, &y, &w, &h); + trap_R_DrawStretchPic(x, y, w, h, s0, t0, s1, t1, hShader); } /* @@ -493,64 +275,14 @@ UI_FillRect Coordinates are 640*480 virtual values ================= */ -void UI_FillRect( float x, float y, float width, float height, const float *color ) { - trap_R_SetColor( color ); - - UI_AdjustFrom640( &x, &y, &width, &height ); - trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); - - trap_R_SetColor( NULL ); -} - -void UI_DrawSides(float x, float y, float w, float h) { - UI_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); - trap_R_DrawStretchPic( x + w - 1, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); -} - -void UI_DrawTopBottom(float x, float y, float w, float h) { - UI_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); - trap_R_DrawStretchPic( x, y + h - 1, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); -} -/* -================ -UI_DrawRect - -Coordinates are 640*480 virtual values -================= -*/ -void UI_DrawRect( float x, float y, float width, float height, const float *color ) { - trap_R_SetColor( color ); - - UI_DrawTopBottom(x, y, width, height); - UI_DrawSides(x, y, width, height); - - trap_R_SetColor( NULL ); -} - -void UI_SetColor( const float *rgba ) { - trap_R_SetColor( rgba ); -} - -void UI_UpdateScreen( void ) { - trap_UpdateScreen(); -} - - -void UI_DrawTextBox (int x, int y, int width, int lines) +void UI_FillRect(float x, float y, float width, float height, const float *color) { - UI_FillRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorBlack ); - UI_DrawRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorWhite ); -} + trap_R_SetColor(color); -qboolean UI_CursorInRect (int x, int y, int width, int height) -{ - if (uiInfo.uiDC.cursorx < x || - uiInfo.uiDC.cursory < y || - uiInfo.uiDC.cursorx > x+width || - uiInfo.uiDC.cursory > y+height) - return qfalse; + UI_AdjustFrom640(&x, &y, &width, &height); + trap_R_DrawStretchPic(x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader); - return qtrue; + trap_R_SetColor(NULL); } + +void UI_SetColor(const float *rgba) { trap_R_SetColor(rgba); } diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c index 43639e5..92c8a7b 100644 --- a/src/ui/ui_gameinfo.c +++ b/src/ui/ui_gameinfo.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -27,72 +28,85 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "ui_local.h" - // // arena and bot info // +int ui_numBots; +static char *ui_botInfos[MAX_BOTS]; -int ui_numBots; -static char *ui_botInfos[MAX_BOTS]; - -static int ui_numArenas; -static char *ui_arenaInfos[MAX_ARENAS]; +static int ui_numArenas; +static char *ui_arenaInfos[MAX_ARENAS]; /* =============== UI_ParseInfos =============== */ -int UI_ParseInfos( char *buf, int max, char *infos[] ) { - char *token; - int count; - char key[MAX_TOKEN_CHARS]; - char info[MAX_INFO_STRING]; - - count = 0; - - while ( 1 ) { - token = COM_Parse( &buf ); - if ( !token[0] ) { - break; - } - if ( strcmp( token, "{" ) ) { - Com_Printf( "Missing { in info file\n" ); - break; - } +int UI_ParseInfos(char *buf, int max, char *infos[]) +{ + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; - if ( count == max ) { - Com_Printf( "Max infos exceeded\n" ); - break; - } + count = 0; - info[0] = '\0'; - while ( 1 ) { - token = COM_ParseExt( &buf, qtrue ); - if ( !token[0] ) { - Com_Printf( "Unexpected end of info file\n" ); - break; - } - if ( !strcmp( token, "}" ) ) { - break; - } - Q_strncpyz( key, token, sizeof( key ) ); - - token = COM_ParseExt( &buf, qfalse ); - if ( !token[0] ) { - strcpy( token, "" ); - } - Info_SetValueForKey( info, key, token ); - } - //NOTE: extra space for arena number - infos[count] = UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); - if (infos[count]) { - strcpy(infos[count], info); - count++; + while (1) + { + token = COM_Parse(&buf); + + if (!token[0]) + break; + + if (strcmp(token, "{")) + { + Com_Printf("Missing { in info file\n"); + break; + } + + if (count == max) + { + Com_Printf("Max infos exceeded\n"); + break; + } + + info[0] = '\0'; + + while (1) + { + token = COM_ParseExt(&buf, qtrue); + + if (!token[0]) + { + Com_Printf("Unexpected end of info file\n"); + break; + } + + if (!strcmp(token, "}")) + break; + + Q_strncpyz(key, token, sizeof(key)); + + token = COM_ParseExt(&buf, qfalse); + + if (!token[0]) + strcpy(token, ""); + + Info_SetValueForKey(info, key, token); + } + + // NOTE: extra space for arena number + infos[count] = UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + + if (infos[count]) + { + strcpy(infos[count], info); + count++; + } } - } - return count; + + return count; } /* @@ -100,27 +114,32 @@ int UI_ParseInfos( char *buf, int max, char *infos[] ) { UI_LoadArenasFromFile =============== */ -static void UI_LoadArenasFromFile( char *filename ) { - int len; - fileHandle_t f; - char buf[MAX_ARENAS_TEXT]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); - return; - } - if ( len >= MAX_ARENAS_TEXT ) { - trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] ); +static void UI_LoadArenasFromFile(char *filename) +{ + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile(filename, &f, FS_READ); + + if (!f) + { + trap_Print(va(S_COLOR_RED "file not found: %s\n", filename)); + return; + } + + if (len >= MAX_ARENAS_TEXT) + { + trap_Print(va(S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT)); + trap_FS_FCloseFile(f); + return; + } + + trap_FS_Read(buf, len, f); + buf[len] = 0; + trap_FS_FCloseFile(f); + + ui_numArenas += UI_ParseInfos(buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas]); } /* @@ -128,12 +147,12 @@ static void UI_LoadArenasFromFile( char *filename ) { UI_MapNameCompare ================= */ -static int UI_MapNameCompare( const void *a, const void *b ) +static int UI_MapNameCompare(const void *a, const void *b) { - mapInfo *A = (mapInfo *)a; - mapInfo *B = (mapInfo *)b; + mapInfo *A = (mapInfo *)a; + mapInfo *B = (mapInfo *)b; - return Q_stricmp( A->mapName, B->mapName ); + return Q_stricmp(A->mapName, B->mapName); } /* @@ -141,86 +160,86 @@ static int UI_MapNameCompare( const void *a, const void *b ) UI_LoadArenas =============== */ -void UI_LoadArenas( void ) { - int numdirs; - char filename[128]; - char dirlist[1024]; - char* dirptr; - int i, n; - int dirlen; - char *type; - - ui_numArenas = 0; - uiInfo.mapCount = 0; - - // get all arenas from .arena files - numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); - dirptr = dirlist; - for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { - dirlen = strlen(dirptr); - strcpy(filename, "scripts/"); - strcat(filename, dirptr); - UI_LoadArenasFromFile(filename); - } - trap_Print( va( "[skipnotify]%i arenas parsed\n", ui_numArenas ) ); - if (UI_OutOfMemory()) { - trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n"); - } - - for( n = 0; n < ui_numArenas; n++ ) - { - // determine type - type = Info_ValueForKey( ui_arenaInfos[ n ], "type" ); - // if no type specified, it will be treated as "ffa" - - if( *type && strstr( type, "tremulous" ) ) - uiInfo.mapList[ uiInfo.mapCount ].typeBits |= ( 1 << 0 ); - else - continue; //not a trem map +void UI_LoadArenas(void) +{ + int numdirs; + char filename[128]; + char dirlist[1024]; + char *dirptr; + int i, n; + int dirlen; + + ui_numArenas = 0; + uiInfo.mapCount = 0; + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024); + dirptr = dirlist; + + for (i = 0; i < numdirs; i++, dirptr += dirlen + 1) + { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + UI_LoadArenasFromFile(filename); + } - uiInfo.mapList[uiInfo.mapCount].cinematic = -1; - uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "map")); - uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "longname")); - uiInfo.mapList[uiInfo.mapCount].levelShot = -1; - uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc(va("levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName)); + trap_Print(va("[skipnotify]%i arenas parsed\n", ui_numArenas)); - uiInfo.mapCount++; - if( uiInfo.mapCount >= MAX_MAPS ) - break; - } + if (UI_OutOfMemory()) + trap_Print(S_COLOR_YELLOW "WARNING: not anough memory in pool to load all arenas\n"); - qsort( uiInfo.mapList, uiInfo.mapCount, sizeof( mapInfo ), UI_MapNameCompare ); -} + for (n = 0; n < ui_numArenas; n++) + { + uiInfo.mapList[uiInfo.mapCount].cinematic = -1; + uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "map")); + uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "longname")); + uiInfo.mapList[uiInfo.mapCount].levelShot = -1; + uiInfo.mapList[uiInfo.mapCount].imageName = + String_Alloc(va("levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName)); + + uiInfo.mapCount++; + if (uiInfo.mapCount >= MAX_MAPS) + break; + } + + qsort(uiInfo.mapList, uiInfo.mapCount, sizeof(mapInfo), UI_MapNameCompare); +} /* =============== UI_LoadBotsFromFile =============== */ -static void UI_LoadBotsFromFile( char *filename ) { - int len; - fileHandle_t f; - char buf[MAX_BOTS_TEXT]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); - return; - } - if ( len >= MAX_BOTS_TEXT ) { - trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - COM_Compress(buf); - - ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] ); +static void UI_LoadBotsFromFile(char *filename) +{ + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile(filename, &f, FS_READ); + + if (!f) + { + trap_Print(va(S_COLOR_RED "file not found: %s\n", filename)); + return; + } + + if (len >= MAX_BOTS_TEXT) + { + trap_Print(va(S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT)); + trap_FS_FCloseFile(f); + return; + } + + trap_FS_Read(buf, len, f); + buf[len] = 0; + trap_FS_FCloseFile(f); + + COM_Compress(buf); + + ui_numBots += UI_ParseInfos(buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots]); } /* @@ -228,106 +247,109 @@ static void UI_LoadBotsFromFile( char *filename ) { UI_LoadBots =============== */ -void UI_LoadBots( void ) { - vmCvar_t botsFile; - int numdirs; - char filename[128]; - char dirlist[1024]; - char* dirptr; - int i; - int dirlen; - - ui_numBots = 0; - - trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); - if( *botsFile.string ) { - UI_LoadBotsFromFile(botsFile.string); - } - else { - UI_LoadBotsFromFile("scripts/bots.txt"); - } - - // get all bots from .bot files - numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); - dirptr = dirlist; - for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { - dirlen = strlen(dirptr); - strcpy(filename, "scripts/"); - strcat(filename, dirptr); - UI_LoadBotsFromFile(filename); - } - trap_Print( va( "%i bots parsed\n", ui_numBots ) ); -} +void UI_LoadBots(void) +{ + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char *dirptr; + int i; + int dirlen; + + ui_numBots = 0; + trap_Cvar_Register(&botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM); + + if (*botsFile.string) + UI_LoadBotsFromFile(botsFile.string); + else + UI_LoadBotsFromFile("scripts/bots.txt"); + + // get all bots from .bot files + numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024); + + dirptr = dirlist; + + for (i = 0; i < numdirs; i++, dirptr += dirlen + 1) + { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + UI_LoadBotsFromFile(filename); + } + + trap_Print(va("%i bots parsed\n", ui_numBots)); +} /* =============== UI_GetBotInfoByNumber =============== */ -char *UI_GetBotInfoByNumber( int num ) { - if( num < 0 || num >= ui_numBots ) { - trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); - return NULL; - } - return ui_botInfos[num]; -} +char *UI_GetBotInfoByNumber(int num) +{ + if (num < 0 || num >= ui_numBots) + { + trap_Print(va(S_COLOR_RED "Invalid bot number: %i\n", num)); + return NULL; + } + return ui_botInfos[num]; +} /* =============== UI_GetBotInfoByName =============== */ -char *UI_GetBotInfoByName( const char *name ) { - int n; - char *value; - - for ( n = 0; n < ui_numBots ; n++ ) { - value = Info_ValueForKey( ui_botInfos[n], "name" ); - if ( !Q_stricmp( value, name ) ) { - return ui_botInfos[n]; +char *UI_GetBotInfoByName(const char *name) +{ + int n; + char *value; + + for (n = 0; n < ui_numBots; n++) + { + value = Info_ValueForKey(ui_botInfos[n], "name"); + + if (!Q_stricmp(value, name)) + return ui_botInfos[n]; } - } - return NULL; + return NULL; } -int UI_GetNumBots() { - return ui_numBots; -} +int UI_GetNumBots(void) { return ui_numBots; } + +char *UI_GetBotNameByNumber(int num) +{ + char *info = UI_GetBotInfoByNumber(num); + if (info) + return Info_ValueForKey(info, "name"); -char *UI_GetBotNameByNumber( int num ) { - char *info = UI_GetBotInfoByNumber(num); - if (info) { - return Info_ValueForKey( info, "name" ); - } - return "Sarge"; + return ""; } -void UI_ServerInfo( void ) +void UI_ServerInfo(void) { - char info[ MAX_INFO_VALUE ]; - - info[0] = '\0'; - if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) ) - { - trap_Cvar_Set( "ui_serverinfo_mapname", - Info_ValueForKey( info, "mapname" ) ); - trap_Cvar_Set( "ui_serverinfo_timelimit", - Info_ValueForKey( info, "timelimit" ) ); - trap_Cvar_Set( "ui_serverinfo_sd", - Info_ValueForKey( info, "g_suddenDeathTime" ) ); - trap_Cvar_Set( "ui_serverinfo_hostname", - Info_ValueForKey( info, "sv_hostname" ) ); - trap_Cvar_Set( "ui_serverinfo_maxclients", - Info_ValueForKey( info, "sv_maxclients" ) ); - trap_Cvar_Set( "ui_serverinfo_version", - Info_ValueForKey( info, "version" ) ); - trap_Cvar_Set( "ui_serverinfo_unlagged", - Info_ValueForKey( info, "g_unlagged" ) ); - trap_Cvar_Set( "ui_serverinfo_ff", - Info_ValueForKey( info, "ff" ) ); - } + char info[MAX_INFO_VALUE]; + char hostname[MAX_HOSTNAME_LENGTH]; + + info[0] = '\0'; + + if (trap_GetConfigString(CS_SERVERINFO, info, sizeof(info))) + { + trap_Cvar_Set("ui_serverinfo_mapname", Info_ValueForKey(info, "mapname")); + trap_Cvar_Set("ui_serverinfo_timelimit", Info_ValueForKey(info, "timelimit")); + trap_Cvar_Set("ui_serverinfo_sd", Info_ValueForKey(info, "g_suddenDeathTime")); + UI_EscapeEmoticons(hostname, Info_ValueForKey(info, "sv_hostname"), sizeof(hostname)); + trap_Cvar_Set("ui_serverinfo_hostname", hostname); + trap_Cvar_Set("ui_serverinfo_maxclients", Info_ValueForKey(info, "sv_maxclients")); + trap_Cvar_Set("ui_serverinfo_version", Info_ValueForKey(info, "version")); + trap_Cvar_Set("ui_serverinfo_unlagged", Info_ValueForKey(info, "g_unlagged")); + trap_Cvar_Set("ui_serverinfo_friendlyFire", Info_ValueForKey(info, "g_friendlyFire")); + trap_Cvar_Set("ui_serverinfo_friendlyBuildableFire", Info_ValueForKey(info, "g_friendlyBuildableFire")); + trap_Cvar_Set("ui_serverinfo_allowdl", Info_ValueForKey(info, "sv_allowdownload")); + } } diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h index 0066593..936c414 100644 --- a/src/ui/ui_local.h +++ b/src/ui/ui_local.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,1195 +17,381 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ -#ifndef __UI_LOCAL_H__ -#define __UI_LOCAL_H__ +#ifndef UI_LOCAL_H +#define UI_LOCAL_H -#include "../qcommon/q_shared.h" -#include "../renderer/tr_types.h" +#include "client/keycodes.h" +#include "game/bg_public.h" +#include "qcommon/q_shared.h" +#include "renderercommon/tr_types.h" #include "ui_public.h" -#include "../client/keycodes.h" -#include "../game/bg_public.h" #include "ui_shared.h" -// global display context - -extern vmCvar_t ui_ffa_fraglimit; -extern vmCvar_t ui_ffa_timelimit; - -extern vmCvar_t ui_tourney_fraglimit; -extern vmCvar_t ui_tourney_timelimit; - -extern vmCvar_t ui_team_fraglimit; -extern vmCvar_t ui_team_timelimit; -extern vmCvar_t ui_team_friendly; - -extern vmCvar_t ui_ctf_capturelimit; -extern vmCvar_t ui_ctf_timelimit; -extern vmCvar_t ui_ctf_friendly; - -extern vmCvar_t ui_arenasFile; -extern vmCvar_t ui_botsFile; -extern vmCvar_t ui_spScores1; -extern vmCvar_t ui_spScores2; -extern vmCvar_t ui_spScores3; -extern vmCvar_t ui_spScores4; -extern vmCvar_t ui_spScores5; -extern vmCvar_t ui_spAwards; -extern vmCvar_t ui_spVideos; -extern vmCvar_t ui_spSkill; - -extern vmCvar_t ui_spSelection; - -extern vmCvar_t ui_browserMaster; -extern vmCvar_t ui_browserGameType; -extern vmCvar_t ui_browserSortKey; -extern vmCvar_t ui_browserShowFull; -extern vmCvar_t ui_browserShowEmpty; - -extern vmCvar_t ui_brassTime; -extern vmCvar_t ui_drawCrosshair; -extern vmCvar_t ui_drawCrosshairNames; -extern vmCvar_t ui_marks; - -extern vmCvar_t ui_server1; -extern vmCvar_t ui_server2; -extern vmCvar_t ui_server3; -extern vmCvar_t ui_server4; -extern vmCvar_t ui_server5; -extern vmCvar_t ui_server6; -extern vmCvar_t ui_server7; -extern vmCvar_t ui_server8; -extern vmCvar_t ui_server9; -extern vmCvar_t ui_server10; -extern vmCvar_t ui_server11; -extern vmCvar_t ui_server12; -extern vmCvar_t ui_server13; -extern vmCvar_t ui_server14; -extern vmCvar_t ui_server15; -extern vmCvar_t ui_server16; - -extern vmCvar_t ui_captureLimit; -extern vmCvar_t ui_fragLimit; -extern vmCvar_t ui_gameType; -extern vmCvar_t ui_netGameType; -extern vmCvar_t ui_actualNetGameType; -extern vmCvar_t ui_joinGameType; -extern vmCvar_t ui_netSource; -extern vmCvar_t ui_serverFilterType; -extern vmCvar_t ui_dedicated; -extern vmCvar_t ui_opponentName; -extern vmCvar_t ui_menuFiles; -extern vmCvar_t ui_currentTier; -extern vmCvar_t ui_currentMap; -extern vmCvar_t ui_currentNetMap; -extern vmCvar_t ui_mapIndex; -extern vmCvar_t ui_currentOpponent; -extern vmCvar_t ui_selectedPlayer; -extern vmCvar_t ui_selectedPlayerName; -extern vmCvar_t ui_lastServerRefresh_0; -extern vmCvar_t ui_lastServerRefresh_1; -extern vmCvar_t ui_lastServerRefresh_2; -extern vmCvar_t ui_lastServerRefresh_3; -extern vmCvar_t ui_singlePlayerActive; -extern vmCvar_t ui_scoreAccuracy; -extern vmCvar_t ui_scoreImpressives; -extern vmCvar_t ui_scoreExcellents; -extern vmCvar_t ui_scoreDefends; -extern vmCvar_t ui_scoreAssists; -extern vmCvar_t ui_scoreGauntlets; -extern vmCvar_t ui_scoreScore; -extern vmCvar_t ui_scorePerfect; -extern vmCvar_t ui_scoreTeam; -extern vmCvar_t ui_scoreBase; -extern vmCvar_t ui_scoreTimeBonus; -extern vmCvar_t ui_scoreSkillBonus; -extern vmCvar_t ui_scoreShutoutBonus; -extern vmCvar_t ui_scoreTime; -extern vmCvar_t ui_smallFont; -extern vmCvar_t ui_bigFont; -extern vmCvar_t ui_serverStatusTimeOut; - -//TA: bank values -extern vmCvar_t ui_bank; - -extern vmCvar_t ui_chatCommands; - -// -// ui_qmenu.c -// - -#define RCOLUMN_OFFSET ( BIGCHAR_WIDTH ) -#define LCOLUMN_OFFSET (-BIGCHAR_WIDTH ) - -#define SLIDER_RANGE 10 -#define MAX_EDIT_LINE 256 - -#define MAX_MENUDEPTH 8 -#define MAX_MENUITEMS 128 - -#define MTYPE_NULL 0 -#define MTYPE_SLIDER 1 -#define MTYPE_ACTION 2 -#define MTYPE_SPINCONTROL 3 -#define MTYPE_FIELD 4 -#define MTYPE_RADIOBUTTON 5 -#define MTYPE_BITMAP 6 -#define MTYPE_TEXT 7 -#define MTYPE_SCROLLLIST 8 -#define MTYPE_PTEXT 9 -#define MTYPE_BTEXT 10 - -#define QMF_BLINK 0x00000001 -#define QMF_SMALLFONT 0x00000002 -#define QMF_LEFT_JUSTIFY 0x00000004 -#define QMF_CENTER_JUSTIFY 0x00000008 -#define QMF_RIGHT_JUSTIFY 0x00000010 -#define QMF_NUMBERSONLY 0x00000020 // edit field is only numbers -#define QMF_HIGHLIGHT 0x00000040 -#define QMF_HIGHLIGHT_IF_FOCUS 0x00000080 // steady focus -#define QMF_PULSEIFFOCUS 0x00000100 // pulse if focus -#define QMF_HASMOUSEFOCUS 0x00000200 -#define QMF_NOONOFFTEXT 0x00000400 -#define QMF_MOUSEONLY 0x00000800 // only mouse input allowed -#define QMF_HIDDEN 0x00001000 // skips drawing -#define QMF_GRAYED 0x00002000 // grays and disables -#define QMF_INACTIVE 0x00004000 // disables any input -#define QMF_NODEFAULTINIT 0x00008000 // skip default initialization -#define QMF_OWNERDRAW 0x00010000 -#define QMF_PULSE 0x00020000 -#define QMF_LOWERCASE 0x00040000 // edit field is all lower case -#define QMF_UPPERCASE 0x00080000 // edit field is all upper case -#define QMF_SILENT 0x00100000 - -// callback notifications -#define QM_GOTFOCUS 1 -#define QM_LOSTFOCUS 2 -#define QM_ACTIVATED 3 - -typedef struct _tag_menuframework -{ - int cursor; - int cursor_prev; - - int nitems; - void *items[MAX_MENUITEMS]; - - void (*draw) (void); - sfxHandle_t (*key) (int key); - - qboolean wrapAround; - qboolean fullscreen; - qboolean showlogo; -} menuframework_s; - -typedef struct -{ - int type; - const char *name; - int id; - int x, y; - int left; - int top; - int right; - int bottom; - menuframework_s *parent; - int menuPosition; - unsigned flags; - - void (*callback)( void *self, int event ); - void (*statusbar)( void *self ); - void (*ownerdraw)( void *self ); -} menucommon_s; - -typedef struct { - int cursor; - int scroll; - int widthInChars; - char buffer[MAX_EDIT_LINE]; - int maxchars; -} mfield_t; - -typedef struct -{ - menucommon_s generic; - mfield_t field; -} menufield_s; - -typedef struct -{ - menucommon_s generic; - - float minvalue; - float maxvalue; - float curvalue; - - float range; -} menuslider_s; - -typedef struct -{ - menucommon_s generic; - - int oldvalue; - int curvalue; - int numitems; - int top; - - const char **itemnames; - - int width; - int height; - int columns; - int seperation; -} menulist_s; - -typedef struct -{ - menucommon_s generic; -} menuaction_s; - -typedef struct -{ - menucommon_s generic; - int curvalue; -} menuradiobutton_s; - -typedef struct -{ - menucommon_s generic; - char* focuspic; - char* errorpic; - qhandle_t shader; - qhandle_t focusshader; - int width; - int height; - float* focuscolor; -} menubitmap_s; - -typedef struct -{ - menucommon_s generic; - char* string; - int style; - float* color; -} menutext_s; - -extern void Menu_Cache( void ); -extern void Menu_Focus( menucommon_s *m ); -extern void Menu_AddItem( menuframework_s *menu, void *item ); -extern void Menu_AdjustCursor( menuframework_s *menu, int dir ); -extern void Menu_Draw( menuframework_s *menu ); -extern void *Menu_ItemAtCursor( menuframework_s *m ); -extern sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item ); -extern void Menu_SetCursor( menuframework_s *s, int cursor ); -extern void Menu_SetCursorToItem( menuframework_s *m, void* ptr ); -extern sfxHandle_t Menu_DefaultKey( menuframework_s *s, int key ); -extern void Bitmap_Init( menubitmap_s *b ); -extern void Bitmap_Draw( menubitmap_s *b ); -extern void ScrollList_Draw( menulist_s *l ); -extern sfxHandle_t ScrollList_Key( menulist_s *l, int key ); -extern sfxHandle_t menu_in_sound; -extern sfxHandle_t menu_move_sound; -extern sfxHandle_t menu_out_sound; -extern sfxHandle_t menu_buzz_sound; -extern sfxHandle_t menu_null_sound; -extern sfxHandle_t weaponChangeSound; -extern vec4_t menu_text_color; -extern vec4_t menu_grayed_color; -extern vec4_t menu_dark_color; -extern vec4_t menu_highlight_color; -extern vec4_t menu_red_color; -extern vec4_t menu_black_color; -extern vec4_t menu_dim_color; -extern vec4_t color_black; -extern vec4_t color_white; -extern vec4_t color_yellow; -extern vec4_t color_blue; -extern vec4_t color_orange; -extern vec4_t color_red; -extern vec4_t color_dim; -extern vec4_t name_color; -extern vec4_t list_color; -extern vec4_t listbar_color; -extern vec4_t text_color_disabled; -extern vec4_t text_color_normal; -extern vec4_t text_color_highlight; - -extern char *ui_medalNames[]; -extern char *ui_medalPicNames[]; -extern char *ui_medalSounds[]; - -// -// ui_mfield.c -// -extern void MField_Clear( mfield_t *edit ); -extern void MField_KeyDownEvent( mfield_t *edit, int key ); -extern void MField_CharEvent( mfield_t *edit, int ch ); -extern void MField_Draw( mfield_t *edit, int x, int y, int style, vec4_t color ); -extern void MenuField_Init( menufield_s* m ); -extern void MenuField_Draw( menufield_s *f ); -extern sfxHandle_t MenuField_Key( menufield_s* m, int* key ); - // // ui_main.c // -void UI_Report( void ); -void UI_Load( void ); +void UI_Report(void); +void UI_Load(void); void UI_LoadMenus(const char *menuFile, qboolean reset); -void _UI_SetActiveMenu( uiMenuCommand_t menu ); int UI_AdjustTimeByGame(int time); -void UI_ShowPostGame(qboolean newHigh); -void UI_ClearScores( void ); +void UI_ClearScores(void); void UI_LoadArenas(void); void UI_ServerInfo(void); +void UI_UpdateNews(qboolean); +void UI_UpdateGithubRelease(void); -// -// ui_menu.c -// -extern void MainMenu_Cache( void ); -extern void UI_MainMenu(void); -extern void UI_RegisterCvars( void ); -extern void UI_UpdateCvars( void ); - -// -// ui_credits.c -// -extern void UI_CreditMenu( void ); - -// -// ui_ingame.c -// -extern void InGame_Cache( void ); -extern void UI_InGameMenu(void); - -// -// ui_confirm.c -// -extern void ConfirmMenu_Cache( void ); -extern void UI_ConfirmMenu( const char *question, void (*draw)( void ), void (*action)( qboolean result ) ); - -// -// ui_setup.c -// -extern void UI_SetupMenu_Cache( void ); -extern void UI_SetupMenu(void); - -// -// ui_team.c -// -extern void UI_TeamMainMenu( void ); -extern void TeamMain_Cache( void ); - -// -// ui_connect.c -// -extern void UI_DrawConnectScreen( qboolean overlay ); - -// -// ui_controls2.c -// -extern void UI_ControlsMenu( void ); -extern void Controls_Cache( void ); - -// -// ui_demo2.c -// -extern void UI_DemosMenu( void ); -extern void Demos_Cache( void ); - -// -// ui_cinematics.c -// -extern void UI_CinematicsMenu( void ); -extern void UI_CinematicsMenu_f( void ); -extern void UI_CinematicsMenu_Cache( void ); - -// -// ui_mods.c -// -extern void UI_ModsMenu( void ); -extern void UI_ModsMenu_Cache( void ); - -// -// ui_playermodel.c -// -extern void UI_PlayerModelMenu( void ); -extern void PlayerModel_Cache( void ); - -// -// ui_playersettings.c -// -extern void UI_PlayerSettingsMenu( void ); -extern void PlayerSettings_Cache( void ); - -// -// ui_preferences.c -// -extern void UI_PreferencesMenu( void ); -extern void Preferences_Cache( void ); - -// -// ui_specifyleague.c -// -extern void UI_SpecifyLeagueMenu( void ); -extern void SpecifyLeague_Cache( void ); - -// -// ui_specifyserver.c -// -extern void UI_SpecifyServerMenu( void ); -extern void SpecifyServer_Cache( void ); - -// -// ui_servers2.c -// -#define MAX_FAVORITESERVERS 16 - -extern void UI_ArenaServersMenu( void ); -extern void ArenaServers_Cache( void ); - -// -// ui_startserver.c -// -extern void UI_StartServerMenu( qboolean multiplayer ); -extern void StartServer_Cache( void ); -extern void ServerOptions_Cache( void ); -extern void UI_BotSelectMenu( char *bot ); -extern void UI_BotSelectMenu_Cache( void ); - -// -// ui_serverinfo.c -// -extern void UI_ServerInfoMenu( void ); -extern void ServerInfo_Cache( void ); - -// -// ui_video.c -// -extern void UI_GraphicsOptionsMenu( void ); -extern void GraphicsOptions_Cache( void ); -extern void DriverInfo_Cache( void ); - -// -// ui_players.c -// - -//FIXME ripped from cg_local.h -typedef struct { - int oldFrame; - int oldFrameTime; // time when ->oldFrame was exactly on - - int frame; - int frameTime; // time when ->frame will be exactly on - - float backlerp; - - float yawAngle; - qboolean yawing; - float pitchAngle; - qboolean pitching; - - int animationNumber; // may include ANIM_TOGGLEBIT - animation_t *animation; - int animationTime; // time when the first frame of the animation will be exact -} lerpFrame_t; - -typedef struct { - // model info - qhandle_t legsModel; - qhandle_t legsSkin; - lerpFrame_t legs; - - qhandle_t torsoModel; - qhandle_t torsoSkin; - lerpFrame_t torso; - - qhandle_t headModel; - qhandle_t headSkin; - - animation_t animations[MAX_PLAYER_TOTALANIMATIONS]; - - qhandle_t weaponModel; - qhandle_t barrelModel; - qhandle_t flashModel; - vec3_t flashDlightColor; - int muzzleFlashTime; - - // currently in use drawing parms - vec3_t viewAngles; - vec3_t moveAngles; - weapon_t currentWeapon; - int legsAnim; - int torsoAnim; - - // animation vars - weapon_t weapon; - weapon_t lastWeapon; - weapon_t pendingWeapon; - int weaponTimer; - int pendingLegsAnim; - int torsoAnimationTimer; - - int pendingTorsoAnim; - int legsAnimationTimer; - - qboolean chat; - qboolean newModel; - - qboolean barrelSpinning; - float barrelAngle; - int barrelTime; - - int realWeapon; -} playerInfo_t; - -void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ); -void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ); -void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNum, qboolean chat ); -qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName , const char *headName, const char *teamName); - -// -// ui_atoms.c -// -// this is only used in the old ui, the new ui has it's own version -typedef struct { - int frametime; - int realtime; - int cursorx; - int cursory; - glconfig_t glconfig; - qboolean debug; - qhandle_t whiteShader; - qhandle_t charset; - qhandle_t charsetProp; - qhandle_t charsetPropGlow; - qhandle_t charsetPropB; - qhandle_t cursor; - qhandle_t rb_on; - qhandle_t rb_off; - float scale; - float bias; - qboolean demoversion; - qboolean firstdraw; -} uiStatic_t; - +void UI_RegisterCvars(void); +void UI_UpdateCvars(void); +void UI_DrawConnectScreen(void); // new ui stuff -#define UI_NUMFX 7 -#define MAX_HEADS 64 -#define MAX_ALIASES 64 -#define MAX_HEADNAME 32 -#define MAX_TEAMS 64 -#define MAX_GAMETYPES 16 #define MAX_MAPS 128 -#define MAX_SPMAPS 16 -#define PLAYERS_PER_TEAM 5 -#define MAX_PINGREQUESTS 32 -#define MAX_ADDRESSLENGTH 64 -#define MAX_HOSTNAMELENGTH 22 -#define MAX_MAPNAMELENGTH 16 -#define MAX_STATUSLENGTH 64 -#define MAX_LISTBOXWIDTH 59 -#define UI_FONT_THRESHOLD 0.1 -#define MAX_DISPLAY_SERVERS 2048 -#define MAX_SERVERSTATUS_LINES 128 -#define MAX_SERVERSTATUS_TEXT 1024 -#define MAX_FOUNDPLAYER_SERVERS 16 -#define TEAM_MEMBERS 5 -#define GAMES_ALL 0 -#define GAMES_FFA 1 -#define GAMES_TEAMPLAY 2 -#define GAMES_TOURNEY 3 -#define GAMES_CTF 4 -#define MAPS_PER_TIER 3 -#define MAX_TIERS 16 +#define MAX_PINGREQUESTS 32 +#define MAX_ADDRESSLENGTH 64 +#define MAX_DISPLAY_SERVERS 2048 +#define MAX_SERVERSTATUS_LINES 128 +#define MAX_SERVERSTATUS_TEXT 1024 +#define MAX_NEWS_LINES 50 +#define MAX_NEWS_LINEWIDTH 85 +#define MAX_FOUNDPLAYER_SERVERS 16 #define MAX_MODS 64 #define MAX_DEMOS 256 #define MAX_MOVIES 256 -#define MAX_PLAYERMODELS 256 - - -typedef struct { - const char *name; - const char *imageName; - qhandle_t headImage; - const char *base; - qboolean active; - int reference; -} characterInfo; - -typedef struct { - const char *name; - const char *ai; - const char *action; -} aliasInfo; - -typedef struct { - const char *teamName; - const char *imageName; - const char *teamMembers[TEAM_MEMBERS]; - qhandle_t teamIcon; - qhandle_t teamIcon_Metal; - qhandle_t teamIcon_Name; - int cinematic; -} teamInfo; - -typedef struct { - const char *gameType; - int gtEnum; -} gameTypeInfo; +#define MAX_HELP_INFOPANES 32 +#define MAX_RESOLUTIONS 32 typedef struct { - const char *mapName; - const char *mapLoadName; - const char *imageName; - const char *opponentName; - int teamMembers; - int typeBits; - int cinematic; - int timeToBeat[MAX_GAMETYPES]; - qhandle_t levelShot; - qboolean active; + const char *mapName; + const char *mapLoadName; + const char *imageName; + int cinematic; + qhandle_t levelShot; } mapInfo; -typedef struct { - const char *tierName; - const char *maps[MAPS_PER_TIER]; - int gameTypes[MAPS_PER_TIER]; - qhandle_t mapHandles[MAPS_PER_TIER]; -} tierInfo; - typedef struct serverFilter_s { - const char *description; - const char *basedir; + const char *description; + const char *basedir; } serverFilter_t; typedef struct { - char adrstr[MAX_ADDRESSLENGTH]; - int start; + char adrstr[MAX_ADDRESSLENGTH]; + int start; } pinglist_t; - typedef struct serverStatus_s { - pinglist_t pingList[MAX_PINGREQUESTS]; - int numqueriedservers; - int currentping; - int nextpingtime; - int maxservers; - int refreshtime; - int numServers; - int sortKey; - int sortDir; - qboolean sorted; - int lastCount; - qboolean refreshActive; - int currentServer; - int displayServers[MAX_DISPLAY_SERVERS]; - int numDisplayServers; - int numPlayersOnServers; - int nextDisplayRefresh; - int nextSortTime; - qhandle_t currentServerPreview; - int currentServerCinematic; - int motdLen; - int motdWidth; - int motdPaintX; - int motdPaintX2; - int motdOffset; - int motdTime; - char motd[MAX_STRING_CHARS]; + pinglist_t pingList[MAX_PINGREQUESTS]; + int numqueriedservers; + int currentping; + int nextpingtime; + int maxservers; + int refreshtime; + int numServers; + int sortKey; + int sortDir; + qboolean sorted; + int lastCount; + qboolean refreshActive; + int currentServer; + int displayServers[MAX_DISPLAY_SERVERS]; + int numDisplayServers; + int numDuplicateMultiprotocolServers; + int numDuplicateMultiprotocolServerClients; + int numPlayersOnServers; + int nextDisplayRefresh; + int nextSortTime; + qhandle_t currentServerPreview; + int currentServerCinematic; + int motdLen; + int motdWidth; + int motdPaintX; + int motdPaintX2; + int motdOffset; + int motdTime; + char motd[MAX_STRING_CHARS]; } serverStatus_t; - typedef struct { - char adrstr[MAX_ADDRESSLENGTH]; - char name[MAX_ADDRESSLENGTH]; - int startTime; - int serverNum; - qboolean valid; + char adrstr[MAX_ADDRESSLENGTH]; + char name[MAX_ADDRESSLENGTH]; + int startTime; + int serverNum; + qboolean valid; } pendingServer_t; typedef struct { - int num; - pendingServer_t server[MAX_SERVERSTATUSREQUESTS]; + int num; + pendingServer_t server[MAX_SERVERSTATUSREQUESTS]; } pendingServerStatus_t; typedef struct { - char address[MAX_ADDRESSLENGTH]; - char *lines[MAX_SERVERSTATUS_LINES][4]; - char text[MAX_SERVERSTATUS_TEXT]; - char pings[MAX_CLIENTS * 3]; - int numLines; + char address[MAX_ADDRESSLENGTH]; + char *lines[MAX_SERVERSTATUS_LINES][4]; + char text[MAX_SERVERSTATUS_TEXT]; + char pings[MAX_CLIENTS * 3]; + int numLines; } serverStatusInfo_t; typedef struct { - const char *modName; - const char *modDescr; -} modInfo_t; - -//TA: tremulous menus -#define MAX_INFOPANE_TEXT 4096 -#define MAX_INFOPANE_GRAPHICS 16 -#define MAX_INFOPANES 128 - -typedef enum -{ - INFOPANE_TOP, - INFOPANE_BOTTOM, - INFOPANE_LEFT, - INFOPANE_RIGHT -} tremIPSide_t; - -typedef struct -{ - qhandle_t graphic; - - tremIPSide_t side; - int offset; - - int width, height; -} tremIPGraphic_t; - -typedef struct -{ - const char *name; - char text[ MAX_INFOPANE_TEXT ]; - int align; - - tremIPGraphic_t graphics[ MAX_INFOPANE_GRAPHICS ]; - int numGraphics; -} tremInfoPane_t; - -typedef struct -{ - const char *text; - const char *cmd; - tremInfoPane_t *infopane; -} tremMenuItem_t; -//TA: tremulous menus + char text[MAX_NEWS_LINES][MAX_NEWS_LINEWIDTH]; + int numLines; + qboolean refreshActive; + int refreshtime; +} newsInfo_t; typedef struct { - displayContextDef_t uiDC; - int newHighScoreTime; - int newBestTime; - int showPostGameTime; - qboolean newHighScore; - qboolean demoAvailable; - qboolean soundHighScore; + char text[MAX_NEWS_LINES][MAX_NEWS_LINEWIDTH]; + int numLines; + qboolean refreshActive; + int nextTime; +} githubRelease_t; - int characterCount; - int botIndex; - characterInfo characterList[MAX_HEADS]; - - int aliasCount; - aliasInfo aliasList[MAX_ALIASES]; - - int teamCount; - teamInfo teamList[MAX_TEAMS]; - - int numGameTypes; - gameTypeInfo gameTypes[MAX_GAMETYPES]; - - int numJoinGameTypes; - gameTypeInfo joinGameTypes[MAX_GAMETYPES]; - - int redBlue; - int playerCount; - int myTeamCount; - int teamIndex; - int playerRefresh; - int playerIndex; - int playerNumber; - int myPlayerIndex; - int ignoreIndex; - qboolean teamLeader; - char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - int clientNums[MAX_CLIENTS]; - int teamClientNums[MAX_CLIENTS]; - clientList_t ignoreList[MAX_CLIENTS]; - - int mapCount; - mapInfo mapList[MAX_MAPS]; - - - int tierCount; - tierInfo tierList[MAX_TIERS]; - - int skillIndex; - - modInfo_t modList[MAX_MODS]; - int modCount; - int modIndex; - - const char *demoList[MAX_DEMOS]; - int demoCount; - int demoIndex; - - const char *movieList[MAX_MOVIES]; - int movieCount; - int movieIndex; - int previewMovie; - - tremInfoPane_t tremInfoPanes[ MAX_INFOPANES ]; - int tremInfoPaneCount; - -//TA: tremulous menus - tremMenuItem_t tremTeamList[ 4 ]; - int tremTeamCount; - int tremTeamIndex; - - tremMenuItem_t tremAlienClassList[ 3 ]; - int tremAlienClassCount; - int tremAlienClassIndex; - - tremMenuItem_t tremHumanItemList[ 3 ]; - int tremHumanItemCount; - int tremHumanItemIndex; - - tremMenuItem_t tremHumanArmouryBuyList[ 32 ]; - int tremHumanArmouryBuyCount; - int tremHumanArmouryBuyIndex; - - tremMenuItem_t tremHumanArmourySellList[ 32 ]; - int tremHumanArmourySellCount; - int tremHumanArmourySellIndex; - - tremMenuItem_t tremAlienUpgradeList[ 16 ]; - int tremAlienUpgradeCount; - int tremAlienUpgradeIndex; - - tremMenuItem_t tremAlienBuildList[ 32 ]; - int tremAlienBuildCount; - int tremAlienBuildIndex; - - tremMenuItem_t tremHumanBuildList[ 32 ]; - int tremHumanBuildCount; - int tremHumanBuildIndex; -//TA: tremulous menus - - serverStatus_t serverStatus; - - // for the showing the status of a server - char serverStatusAddress[MAX_ADDRESSLENGTH]; - serverStatusInfo_t serverStatusInfo; - int nextServerStatusRefresh; - - // to retrieve the status of server to find a player - pendingServerStatus_t pendingServerStatus; - char findPlayerName[MAX_STRING_CHARS]; - char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; - char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; - int currentFoundPlayerServer; - int numFoundPlayerServers; - int nextFindPlayerRefresh; - - int currentCrosshair; - int startPostGameTime; - sfxHandle_t newHighScoreSound; +typedef struct { + const char *modName; + const char *modDescr; +} modInfo_t; - int q3HeadCount; - char q3HeadNames[MAX_PLAYERMODELS][64]; - qhandle_t q3HeadIcons[MAX_PLAYERMODELS]; - int q3SelectedHead; +typedef enum { + INFOTYPE_TEXT, + INFOTYPE_BUILDABLE, + INFOTYPE_CLASS, + INFOTYPE_WEAPON, + INFOTYPE_UPGRADE, + INFOTYPE_VOICECMD +} infoType_t; - int effectsColor; +typedef struct { + const char *text; + const char *cmd; + infoType_t type; + union { + const char *text; + buildable_t buildable; + class_t pclass; + weapon_t weapon; + upgrade_t upgrade; + } v; +} menuItem_t; - qboolean inGameLoad; +typedef struct { + int w; + int h; +} resolution_t; - qboolean chatTeam; -} uiInfo_t; +typedef struct { + displayContextDef_t uiDC; + + int playerCount; + int myTeamCount; + int teamPlayerIndex; + int playerRefresh; + int playerIndex; + int playerNumber; + int myPlayerIndex; + int ignoreIndex; + char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + int clientNums[MAX_CLIENTS]; + int teamClientNums[MAX_CLIENTS]; + clientList_t ignoreList[MAX_CLIENTS]; + + int mapCount; + mapInfo mapList[MAX_MAPS]; + + modInfo_t modList[MAX_MODS]; + int modCount; + int modIndex; + + const char *demoList[MAX_DEMOS]; + int demoCount; + int demoIndex; + + const char *movieList[MAX_MOVIES]; + int movieCount; + int movieIndex; + int previewMovie; + + menuItem_t teamList[4]; + int teamCount; + int teamIndex; + + menuItem_t alienClassList[3]; + int alienClassCount; + int alienClassIndex; + + menuItem_t humanItemList[3]; + int humanItemCount; + int humanItemIndex; + + menuItem_t humanArmouryBuyList[32]; + int humanArmouryBuyCount; + int humanArmouryBuyIndex; + + menuItem_t humanArmourySellList[32]; + int humanArmourySellCount; + int humanArmourySellIndex; + + menuItem_t alienUpgradeList[16]; + int alienUpgradeCount; + int alienUpgradeIndex; + + menuItem_t alienBuildList[32]; + int alienBuildCount; + int alienBuildIndex; + + menuItem_t humanBuildList[32]; + int humanBuildCount; + int humanBuildIndex; + + menuItem_t voiceCmdList[32]; + int voiceCmdCount; + int voiceCmdIndex; + + menuItem_t helpList[MAX_HELP_INFOPANES]; + int helpCount; + int helpIndex; + + int weapons; + int upgrades; + + serverStatus_t serverStatus; + + // for showing the game news window + newsInfo_t newsInfo; + + githubRelease_t githubRelease; + + // for the showing the status of a server + char serverStatusAddress[MAX_ADDRESSLENGTH]; + serverStatusInfo_t serverStatusInfo; + int nextServerStatusRefresh; + + // to retrieve the status of server to find a player + pendingServerStatus_t pendingServerStatus; + char findPlayerName[MAX_STRING_CHARS]; + char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + int currentFoundPlayerServer; + int numFoundPlayerServers; + int nextFindPlayerRefresh; + + resolution_t resolutions[MAX_RESOLUTIONS]; + int numResolutions; + int resolutionIndex; + + voice_t *voices; + + qboolean inGameLoad; + + qboolean chatTeam; + qboolean voiceCmd; +} uiInfo_t; extern uiInfo_t uiInfo; - -extern void UI_Init( void ); -extern void UI_Shutdown( void ); -extern void UI_KeyEvent( int key ); -extern void UI_MouseEvent( int dx, int dy ); -extern void UI_Refresh( int realtime ); -extern qboolean UI_ConsoleCommand( int realTime ); -extern float UI_ClampCvar( float min, float max, float value ); -extern void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ); -extern void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ); -extern void UI_FillRect( float x, float y, float width, float height, const float *color ); -extern void UI_DrawRect( float x, float y, float width, float height, const float *color ); -extern void UI_DrawTopBottom(float x, float y, float w, float h); -extern void UI_DrawSides(float x, float y, float w, float h); -extern void UI_UpdateScreen( void ); -extern void UI_SetColor( const float *rgba ); -extern void UI_LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); -extern void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ); -extern float UI_ProportionalSizeScale( int style ); -extern void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); -extern int UI_ProportionalStringWidth( const char* str ); -extern void UI_DrawString( int x, int y, const char* str, int style, vec4_t color ); -extern void UI_DrawChar( int x, int y, int ch, int style, vec4_t color ); -extern qboolean UI_CursorInRect (int x, int y, int width, int height); -extern void UI_AdjustFrom640( float *x, float *y, float *w, float *h ); -extern void UI_DrawTextBox (int x, int y, int width, int lines); -extern qboolean UI_IsFullscreen( void ); -extern void UI_SetActiveMenu( uiMenuCommand_t menu ); -extern void UI_PushMenu ( menuframework_s *menu ); -extern void UI_PopMenu (void); -extern void UI_ForceMenuOff (void); -extern char *UI_Argv( int arg ); -extern char *UI_Cvar_VariableString( const char *var_name ); -extern void UI_Refresh( int time ); -extern void UI_KeyEvent( int key ); -extern void UI_StartDemoLoop( void ); -extern qboolean m_entersound; -void UI_LoadBestScores(const char *map, int game); -extern uiStatic_t uis; - -// -// ui_spLevel.c -// -void UI_SPLevelMenu_Cache( void ); -void UI_SPLevelMenu( void ); -void UI_SPLevelMenu_f( void ); -void UI_SPLevelMenu_ReInit( void ); - -// -// ui_spArena.c -// -void UI_SPArena_Start( const char *arenaInfo ); - -// -// ui_spPostgame.c -// -void UI_SPPostgameMenu_Cache( void ); -void UI_SPPostgameMenu_f( void ); - -// -// ui_spSkill.c -// -void UI_SPSkillMenu( const char *arenaInfo ); -void UI_SPSkillMenu_Cache( void ); +qboolean UI_ConsoleCommand(int realTime); +char *UI_Cvar_VariableString(const char *var_name); +void UI_SetColor(const float *rgba); +void UI_AdjustFrom640(float *x, float *y, float *w, float *h); +void UI_Refresh(int time); +void UI_DrawHandlePic(float x, float y, float w, float h, qhandle_t hShader); +void UI_FillRect(float x, float y, float width, float height, const float *color); // // ui_syscalls.c // -void trap_Print( const char *string ); -void trap_Error( const char *string ); -int trap_Milliseconds( void ); -void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); -void trap_Cvar_Update( vmCvar_t *vmCvar ); -void trap_Cvar_Set( const char *var_name, const char *value ); -float trap_Cvar_VariableValue( const char *var_name ); -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); -void trap_Cvar_SetValue( const char *var_name, float value ); -void trap_Cvar_Reset( const char *name ); -void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ); -void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ); -int trap_Argc( void ); -void trap_Argv( int n, char *buffer, int bufferLength ); -void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_NOW! -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); -void trap_FS_Read( void *buffer, int len, fileHandle_t f ); -void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); -void trap_FS_FCloseFile( fileHandle_t f ); -int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); -int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t -qhandle_t trap_R_RegisterModel( const char *name ); -qhandle_t trap_R_RegisterSkin( const char *name ); -qhandle_t trap_R_RegisterShaderNoMip( const char *name ); -void trap_R_ClearScene( void ); -void trap_R_AddRefEntityToScene( const refEntity_t *re ); -void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); -void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); -void trap_R_RenderScene( const refdef_t *fd ); -void trap_R_SetColor( const float *rgba ); -void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); -void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); -void trap_UpdateScreen( void ); -int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ); -void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); -sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); -void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); -void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); -void trap_Key_SetBinding( int keynum, const char *binding ); -qboolean trap_Key_IsDown( int keynum ); -qboolean trap_Key_GetOverstrikeMode( void ); -void trap_Key_SetOverstrikeMode( qboolean state ); -void trap_Key_ClearStates( void ); -int trap_Key_GetCatcher( void ); -void trap_Key_SetCatcher( int catcher ); -void trap_GetClipboardData( char *buf, int bufsize ); -void trap_GetClientState( uiClientState_t *state ); -void trap_GetGlconfig( glconfig_t *glconfig ); -int trap_GetConfigString( int index, char* buff, int buffsize ); -int trap_LAN_GetServerCount( int source ); -void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ); -void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ); -int trap_LAN_GetServerPing( int source, int n ); -int trap_LAN_GetPingQueueCount( void ); -void trap_LAN_ClearPing( int n ); -void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ); -void trap_LAN_GetPingInfo( int n, char *buf, int buflen ); -void trap_LAN_LoadCachedServers( void ); -void trap_LAN_SaveCachedServers( void ); -void trap_LAN_MarkServerVisible(int source, int n, qboolean visible); -int trap_LAN_ServerIsVisible( int source, int n); -qboolean trap_LAN_UpdateVisiblePings( int source ); -int trap_LAN_AddServer(int source, const char *name, const char *addr); -void trap_LAN_RemoveServer(int source, const char *addr); -void trap_LAN_ResetPings(int n); -int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ); -int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ); -int trap_MemoryRemaining( void ); -void trap_R_RegisterFont(const char *pFontname, int pointSize, fontInfo_t *font); -void trap_S_StopBackgroundTrack( void ); -void trap_S_StartBackgroundTrack( const char *intro, const char *loop); -int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); -e_status trap_CIN_StopCinematic(int handle); -e_status trap_CIN_RunCinematic (int handle); -void trap_CIN_DrawCinematic (int handle); -void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); -int trap_RealTime(qtime_t *qtime); -void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); - -void trap_SetPbClStatus( int status ); - -// -// ui_addbots.c -// -void UI_AddBots_Cache( void ); -void UI_AddBotsMenu( void ); - -// -// ui_removebots.c -// -void UI_RemoveBots_Cache( void ); -void UI_RemoveBotsMenu( void ); - -// -// ui_teamorders.c -// -extern void UI_TeamOrdersMenu( void ); -extern void UI_TeamOrdersMenu_f( void ); -extern void UI_TeamOrdersMenu_Cache( void ); - -// -// ui_loadconfig.c -// -void UI_LoadConfig_Cache( void ); -void UI_LoadConfigMenu( void ); - -// -// ui_saveconfig.c -// -void UI_SaveConfigMenu_Cache( void ); -void UI_SaveConfigMenu( void ); - -// -// ui_display.c -// -void UI_DisplayOptionsMenu_Cache( void ); -void UI_DisplayOptionsMenu( void ); - -// -// ui_sound.c -// -void UI_SoundOptionsMenu_Cache( void ); -void UI_SoundOptionsMenu( void ); - -// -// ui_network.c -// -void UI_NetworkOptionsMenu_Cache( void ); -void UI_NetworkOptionsMenu( void ); - -// -// ui_gameinfo.c -// -typedef enum { - AWARD_ACCURACY, - AWARD_IMPRESSIVE, - AWARD_EXCELLENT, - AWARD_GAUNTLET, - AWARD_FRAGS, - AWARD_PERFECT -} awardType_t; - -const char *UI_GetArenaInfoByNumber( int num ); -const char *UI_GetArenaInfoByMap( const char *map ); -const char *UI_GetSpecialArenaInfo( const char *tag ); -int UI_GetNumArenas( void ); -int UI_GetNumSPArenas( void ); -int UI_GetNumSPTiers( void ); - -char *UI_GetBotInfoByNumber( int num ); -char *UI_GetBotInfoByName( const char *name ); -int UI_GetNumBots( void ); -void UI_LoadBots( void ); -char *UI_GetBotNameByNumber( int num ); - -void UI_GetBestScore( int level, int *score, int *skill ); -void UI_SetBestScore( int level, int score ); -int UI_TierCompleted( int levelWon ); -qboolean UI_ShowTierVideo( int tier ); -qboolean UI_CanShowTierVideo( int tier ); -int UI_GetCurrentGame( void ); -void UI_NewGame( void ); -void UI_LogAwardData( int award, int data ); -int UI_GetAwardLevel( int award ); - -void UI_SPUnlock_f( void ); -void UI_SPUnlockMedals_f( void ); - -void UI_InitGameinfo( void ); - -// -// ui_login.c -// -void Login_Cache( void ); -void UI_LoginMenu( void ); - -// -// ui_signup.c -// -void Signup_Cache( void ); -void UI_SignupMenu( void ); - -// -// ui_rankstatus.c -// -void RankStatus_Cache( void ); -void UI_RankStatusMenu( void ); - - -// new ui - -#define ASSET_BACKGROUND "uiBackground" - -// for tracking sp game info in Team Arena -typedef struct postGameInfo_s { - int score; - int redScore; - int blueScore; - int perfects; - int accuracy; - int impressives; - int excellents; - int defends; - int assists; - int gauntlets; - int captures; - int time; - int timeBonus; - int shutoutBonus; - int skillBonus; - int baseScore; -} postGameInfo_t; - - +void trap_Print(const char *string); +void trap_Error(const char *string) __attribute__((noreturn)); +int trap_Milliseconds(void); +void trap_Cvar_Register(vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags); +void trap_Cvar_Update(vmCvar_t *vmCvar); +void trap_Cvar_Set(const char *var_name, const char *value); +float trap_Cvar_VariableValue(const char *var_name); +void trap_Cvar_VariableStringBuffer(const char *var_name, char *buffer, int bufsize); +void trap_Cvar_SetValue(const char *var_name, float value); +void trap_Cvar_Reset(const char *name); +void trap_Cvar_Create(const char *var_name, const char *var_value, int flags); +void trap_Cvar_InfoStringBuffer(int bit, char *buffer, int bufsize); +int trap_Argc(void); +void trap_Argv(int n, char *buffer, int bufferLength); +void trap_Cmd_ExecuteText(int exec_when, const char *text); // don't use EXEC_NOW! +int trap_FS_FOpenFile(const char *qpath, fileHandle_t *f, enum FS_Mode mode); +void trap_FS_Read(void *buffer, int len, fileHandle_t f); +void trap_FS_Write(const void *buffer, int len, fileHandle_t f); +void trap_FS_FCloseFile(fileHandle_t f); +int trap_FS_GetFileList(const char *path, const char *extension, char *listbuf, int bufsize); +int trap_FS_Seek(fileHandle_t f, long offset, enum FS_Mode origin); // fsOrigin_t +qhandle_t trap_R_RegisterModel(const char *name); +qhandle_t trap_R_RegisterSkin(const char *name); +qhandle_t trap_R_RegisterShaderNoMip(const char *name); +void trap_R_ClearScene(void); +void trap_R_AddRefEntityToScene(const refEntity_t *re); +void trap_R_AddPolyToScene(qhandle_t hShader, int numVerts, const polyVert_t *verts); +void trap_R_AddLightToScene(const vec3_t org, float intensity, float r, float g, float b); +void trap_R_RenderScene(const refdef_t *fd); +void trap_R_SetColor(const float *rgba); +void trap_R_SetClipRegion(const float *region); +void trap_R_DrawStretchPic( + float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader); +void trap_R_ModelBounds(clipHandle_t model, vec3_t mins, vec3_t maxs); +void trap_UpdateScreen(void); +int trap_CM_LerpTag( + orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName); +void trap_S_StartLocalSound(sfxHandle_t sfx, int channelNum); +sfxHandle_t trap_S_RegisterSound(const char *sample, qboolean compressed); +void trap_Key_KeynumToStringBuf(int keynum, char *buf, int buflen); +void trap_Key_GetBindingBuf(int keynum, char *buf, int buflen); +void trap_Key_SetBinding(int keynum, const char *binding); +qboolean trap_Key_IsDown(int keynum); +qboolean trap_Key_GetOverstrikeMode(void); +void trap_Key_SetOverstrikeMode(qboolean state); +void trap_Key_ClearStates(void); +int trap_Key_GetCatcher(void); +void trap_Key_SetCatcher(int catcher); +void trap_GetClipboardData(char *buf, int bufsize); +void trap_GetClientState(uiClientState_t *state); +void trap_GetGlconfig(glconfig_t *glconfig); +int trap_GetConfigString(int index, char *buff, int buffsize); +int trap_LAN_GetServerCount(int source); +void trap_LAN_GetServerAddressString(int source, int n, char *buf, int buflen); +void trap_LAN_GetServerInfo(int source, int n, char *buf, int buflen); +int trap_LAN_GetServerPing(int source, int n); +int trap_LAN_GetPingQueueCount(void); +void trap_LAN_ClearPing(int n); +void trap_LAN_GetPing(int n, char *buf, int buflen, int *pingtime); +void trap_LAN_GetPingInfo(int n, char *buf, int buflen); +void trap_LAN_LoadCachedServers(void); +void trap_LAN_SaveCachedServers(void); +void trap_LAN_MarkServerVisible(int source, int n, qboolean visible); +int trap_LAN_ServerIsVisible(int source, int n); +qboolean trap_LAN_UpdateVisiblePings(int source); +int trap_LAN_AddServer(int source, const char *name, const char *addr); +void trap_LAN_RemoveServer(int source, const char *addr); +void trap_LAN_ResetPings(int n); +int trap_LAN_ServerStatus(const char *serverAddress, char *serverStatus, int maxLen); +qboolean trap_GetNews(qboolean force); +int trap_LAN_CompareServers(int source, int sortKey, int sortDir, int s1, int s2); +int trap_MemoryRemaining(void); +void trap_R_RegisterFont(const char *pFontname, int pointSize, fontInfo_t *font); +void trap_S_StopBackgroundTrack(void); +void trap_S_StartBackgroundTrack(const char *intro, const char *loop); +int trap_CIN_PlayCinematic(const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status trap_CIN_StopCinematic(int handle); +e_status trap_CIN_RunCinematic(int handle); +void trap_CIN_DrawCinematic(int handle); +void trap_CIN_SetExtents(int handle, int x, int y, int w, int h); +int trap_RealTime(qtime_t *qtime); +void trap_R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset); + +void trap_SetPbClStatus(int status); + +int trap_CheckForUpdate(int script); +int trap_InstallUpdate(int script); #endif diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c index e2396a9..ec7c0aa 100644 --- a/src/ui/ui_main.c +++ b/src/ui/ui_main.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -29,9 +30,6 @@ USER INTERFACE MAIN ======================================================================= */ -// use this to get a demo build without an explicit demo build, i.e. to get the demo ui files to build -//#define PRE_RELEASE_TADEMO - #include "ui_local.h" uiInfo_t uiInfo; @@ -39,80 +37,84 @@ uiInfo_t uiInfo; #ifdef MODULE_INTERFACE_11 #undef AS_GLOBAL #undef AS_LOCAL -#define AS_GLOBAL 2 -#define AS_LOCAL 0 +#define AS_GLOBAL 2 +#define AS_LOCAL 0 #endif -static const char *MonthAbbrev[] = { - "Jan","Feb","Mar", - "Apr","May","Jun", - "Jul","Aug","Sep", - "Oct","Nov","Dec" -}; - - -static const char *skillLevels[] = { - "I Can Win", - "Bring It On", - "Hurt Me Plenty", - "Hardcore", - "Nightmare" -}; - -static const int numSkillLevels = sizeof(skillLevels) / sizeof(const char*); - - static const char *netSources[] = { #ifdef MODULE_INTERFACE_11 - "LAN", - "Mplayer", - "Internet", + "LAN", "Mplayer", "Internet", #else - "Internet", - "Mplayer", - "LAN", + "Internet", "Mplayer", "LAN", #endif - "Favorites" -}; -static const int numNetSources = sizeof(netSources) / sizeof(const char*); - -static const serverFilter_t serverFilters[] = { - {"All", "" }, - {"Quake 3 Arena", "" }, - {"Team Arena", "missionpack" }, - {"Rocket Arena", "arena" }, - {"Alliance", "alliance20" }, - {"Weapons Factory Arena", "wfa" }, - {"OSP", "osp" }, -}; - -static const int numServerFilters = sizeof(serverFilters) / sizeof(serverFilter_t); - -static char* netnames[] = { - "???", - "UDP", - "IPX", - NULL -}; - -static int gamecodetoui[] = {4,2,3,0,5,1,6}; - - -static void UI_StartServerRefresh(qboolean full); -static void UI_StopServerRefresh( void ); -static void UI_DoServerRefresh( void ); -static void UI_FeederSelection(float feederID, int index); -static void UI_BuildServerDisplayList(int force); -static void UI_BuildServerStatus(qboolean force); -static void UI_BuildFindPlayerList(qboolean force); -static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ); -static int UI_MapCountByGameType(qboolean singlePlayer); -static int UI_HeadCountByTeam( void ); -static const char *UI_SelectedMap(int index, int *actual); -static const char *UI_SelectedHead(int index, int *actual); -static int UI_GetIndexFromSelection(int actual); - -int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 ); + "Favorites"}; + +static const size_t numNetSources = ARRAY_LEN(netSources); + +static const char *netnames[] = {"???", "UDP", "IPX", NULL}; + +/* +================ +cvars +================ +*/ + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} + +cvarTable_t; + +vmCvar_t ui_browserShowFull; +vmCvar_t ui_browserShowEmpty; + +vmCvar_t ui_dedicated; +vmCvar_t ui_netSource; +vmCvar_t ui_selectedMap; +vmCvar_t ui_lastServerRefresh_0; +vmCvar_t ui_lastServerRefresh_1; +vmCvar_t ui_lastServerRefresh_2; +vmCvar_t ui_lastServerRefresh_3; +vmCvar_t ui_lastServerRefresh_0_time; +vmCvar_t ui_lastServerRefresh_1_time; +vmCvar_t ui_lastServerRefresh_2_time; +vmCvar_t ui_lastServerRefresh_3_time; +vmCvar_t ui_smallFont; +vmCvar_t ui_bigFont; +vmCvar_t ui_findPlayer; +vmCvar_t ui_serverStatusTimeOut; +vmCvar_t ui_textWrapCache; +vmCvar_t ui_developer; + +vmCvar_t ui_emoticons; +vmCvar_t ui_winner; +vmCvar_t ui_chatCommands; + +static cvarTable_t cvarTable[] = {{&ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE}, + {&ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE}, + + {&ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE}, {&ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE}, + {&ui_selectedMap, "ui_selectedMap", "0", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE}, + {&ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE}, + {&ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE | CVAR_LATCH}, + {&ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE | CVAR_LATCH}, {&ui_findPlayer, "ui_findPlayer", "", CVAR_ARCHIVE}, + {&ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE}, + {&ui_textWrapCache, "ui_textWrapCache", "1", CVAR_ARCHIVE}, + {&ui_developer, "ui_developer", "0", CVAR_ARCHIVE | CVAR_CHEAT}, + {&ui_emoticons, "cg_emoticons", "1", CVAR_LATCH | CVAR_ARCHIVE}, {&ui_winner, "ui_winner", "", CVAR_ROM}, + {&ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE}}; + +static size_t cvarTableSize = ARRAY_LEN(cvarTable); /* ================ @@ -122,100 +124,118 @@ This is the only way control passes into the module. This must be the very first function compiled into the .qvm file ================ */ -vmCvar_t ui_new; -vmCvar_t ui_debug; -vmCvar_t ui_initialized; -vmCvar_t ui_teamArenaFirstRun; - -void _UI_Init( qboolean ); -void _UI_Shutdown( void ); -void _UI_KeyEvent( int key, qboolean down ); -void _UI_MouseEvent( int dx, int dy ); -int _UI_MousePosition( void ); -void _UI_SetMousePosition( int x, int y ); -void _UI_Refresh( int realtime ); -qboolean _UI_IsFullscreen( void ); -Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, - int arg4, int arg5, int arg6, int arg7, - int arg8, int arg9, int arg10, int arg11 ) { - switch ( command ) { - case UI_GETAPIVERSION: - return UI_API_VERSION; - - case UI_INIT: - _UI_Init(arg0); - return 0; - - case UI_SHUTDOWN: - _UI_Shutdown(); - return 0; - - case UI_KEY_EVENT: - _UI_KeyEvent( arg0, arg1 ); - return 0; - - case UI_MOUSE_EVENT: - _UI_MouseEvent( arg0, arg1 ); - return 0; +void UI_Init(qboolean); +void UI_Shutdown(void); +void UI_KeyEvent(int key, qboolean down); +void UI_MouseEvent(int dx, int dy); +int UI_MousePosition(void); +void UI_SetMousePosition(int x, int y); +void UI_Refresh(int realtime); +qboolean UI_IsFullscreen(void); +void UI_SetActiveMenu(uiMenuCommand_t menu); + +Q_EXPORT intptr_t vmMain(int command, int arg0, int arg1, int arg2) +{ + switch (command) + { + case UI_GETAPIVERSION: + return UI_API_VERSION; + + case UI_INIT: + UI_Init(arg0); + return 0; + + case UI_SHUTDOWN: + UI_Shutdown(); + return 0; + + case UI_KEY_EVENT: + UI_KeyEvent(arg0, arg1); + return 0; + + case UI_MOUSE_EVENT: + UI_MouseEvent(arg0, arg1); + return 0; #ifndef MODULE_INTERFACE_11 - case UI_MOUSE_POSITION: - return _UI_MousePosition( ); + case UI_MOUSE_POSITION: + return UI_MousePosition(); - case UI_SET_MOUSE_POSITION: - _UI_SetMousePosition( arg0, arg1 ); - return 0; + case UI_SET_MOUSE_POSITION: + UI_SetMousePosition(arg0, arg1); + return 0; #endif - case UI_REFRESH: - _UI_Refresh( arg0 ); - return 0; + case UI_REFRESH: + UI_Refresh(arg0); + return 0; - case UI_IS_FULLSCREEN: - return _UI_IsFullscreen(); + case UI_IS_FULLSCREEN: + return UI_IsFullscreen(); - case UI_SET_ACTIVE_MENU: - _UI_SetActiveMenu( arg0 ); - return 0; + case UI_SET_ACTIVE_MENU: + UI_SetActiveMenu(arg0); + return 0; - case UI_CONSOLE_COMMAND: - return UI_ConsoleCommand(arg0); + case UI_CONSOLE_COMMAND: + return UI_ConsoleCommand(arg0); - case UI_DRAW_CONNECT_SCREEN: - UI_DrawConnectScreen( arg0 ); - return 0; - } + case UI_DRAW_CONNECT_SCREEN: + UI_DrawConnectScreen(); + return 0; + } - return -1; + return -1; } +void AssetCache(void) +{ + int i; + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(ASSET_GRADIENTBAR); + uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR); + uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWDOWN); + uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWUP); + uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWLEFT); + uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWRIGHT); + uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip(ASSET_SCROLL_THUMB); + uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip(ASSET_SLIDER_BAR); + uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip(ASSET_SLIDER_THUMB); + + if (ui_emoticons.integer) + { + uiInfo.uiDC.Assets.emoticonCount = BG_LoadEmoticons(uiInfo.uiDC.Assets.emoticons, MAX_EMOTICONS); + } + else + uiInfo.uiDC.Assets.emoticonCount = 0; -void AssetCache( void ) { - uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); - uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); - uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); - uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); - uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); - uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); - uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); - uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); - uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + for (i = 0; i < uiInfo.uiDC.Assets.emoticonCount; i++) + { + uiInfo.uiDC.Assets.emoticons[i].shader = trap_R_RegisterShaderNoMip( + va("emoticons/%s_%dx1.tga", uiInfo.uiDC.Assets.emoticons[i].name, uiInfo.uiDC.Assets.emoticons[i].width)); + } } -void _UI_DrawSides(float x, float y, float w, float h, float size) { - UI_AdjustFrom640( &x, &y, &w, &h ); - size *= uiInfo.uiDC.xscale; - trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); - trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +void UI_DrawSides(float x, float y, float w, float h, float size) +{ + float sizeY; + + UI_AdjustFrom640(&x, &y, &w, &h); + sizeY = size * uiInfo.uiDC.yscale; + size *= uiInfo.uiDC.xscale; + + trap_R_DrawStretchPic(x, y + sizeY, size, h - (sizeY * 2.0f), 0, 0, 0, 0, uiInfo.uiDC.whiteShader); + trap_R_DrawStretchPic(x + w - size, y + sizeY, size, h - (sizeY * 2.0f), 0, 0, 0, 0, uiInfo.uiDC.whiteShader); } -void _UI_DrawTopBottom(float x, float y, float w, float h, float size) { - UI_AdjustFrom640( &x, &y, &w, &h ); - size *= uiInfo.uiDC.yscale; - trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); - trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +void UI_DrawTopBottom(float x, float y, float w, float h, float size) +{ + UI_AdjustFrom640(&x, &y, &w, &h); + size *= uiInfo.uiDC.yscale; + trap_R_DrawStretchPic(x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader); + trap_R_DrawStretchPic(x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader); } + /* ================ UI_DrawRect @@ -223,2884 +243,2077 @@ UI_DrawRect Coordinates are 640*480 virtual values ================= */ -void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) { - trap_R_SetColor( color ); +void UI_DrawRect(float x, float y, float width, float height, float size, const float *color) +{ + trap_R_SetColor(color); - _UI_DrawTopBottom(x, y, width, height, size); - _UI_DrawSides(x, y, width, height, size); + UI_DrawTopBottom(x, y, width, height, size); + UI_DrawSides(x, y, width, height, size); - trap_R_SetColor( NULL ); + trap_R_SetColor(NULL); } +/* +================== +UI_ServerInfoIsValid +Return false if the infostring contains nonprinting characters, + or if the hostname is blank/undefined +================== +*/ +static qboolean UI_ServerInfoIsValid(char *info) +{ + char *c; + int len = 0; - -int Text_Width(const char *text, float scale, int limit) { - int count,len; - float out; - glyphInfo_t *glyph; - float useScale; - const char *s = text; - fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; - if (scale <= ui_smallFont.value) { - font = &uiInfo.uiDC.Assets.smallFont; - } else if (scale >= ui_bigFont.value) { - font = &uiInfo.uiDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - out = 0; - if (text) { - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - if ( Q_IsColorString(s) ) { - s += 2; - continue; - } else { - glyph = &font->glyphs[(int)*s]; - out += glyph->xSkip; - s++; - count++; - } + for (c = info; *c; c++) + { + if (!isprint(*c)) + return qfalse; } - } - return out * useScale; -} -int Text_Height(const char *text, float scale, int limit) { - int len, count; - float max; - glyphInfo_t *glyph; - float useScale; - const char *s = text; // bk001206 - unsigned - fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; - if (scale <= ui_smallFont.value) { - font = &uiInfo.uiDC.Assets.smallFont; - } else if (scale >= ui_bigFont.value) { - font = &uiInfo.uiDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - max = 0; - if (text) { - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - if ( Q_IsColorString(s) ) { - s += 2; - continue; - } else { - glyph = &font->glyphs[(int)*s]; - if (max < glyph->height) { - max = glyph->height; - } - s++; - count++; - } + for (c = Info_ValueForKey(info, "hostname"); *c; c++) + { + if (isgraph(*c)) + len++; } - } - return max * useScale; -} -void Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { - float w, h; - w = width * scale; - h = height * scale; - UI_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); + if (len) + return qtrue; + else + return qfalse; } -void Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - float useScale; - fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; - if (scale <= ui_smallFont.value) { - font = &uiInfo.uiDC.Assets.smallFont; - } else if (scale >= ui_bigFont.value) { - font = &uiInfo.uiDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - if (text) { - const char *s = text; // bk001206 - unsigned - trap_R_SetColor( color ); - memcpy(&newColor[0], &color[0], sizeof(vec4_t)); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; - //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; - //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - float yadj = useScale * glyph->top; - if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { - int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; - colorBlack[3] = newColor[3]; - trap_R_SetColor( colorBlack ); - Text_PaintChar(x + ofs, y - yadj + ofs, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - trap_R_SetColor( newColor ); - colorBlack[3] = 1.0; - } - else if( style == ITEM_TEXTSTYLE_NEON ) - { - vec4_t glow, outer, inner, white; - - glow[ 0 ] = newColor[ 0 ] * 0.5; - glow[ 1 ] = newColor[ 1 ] * 0.5; - glow[ 2 ] = newColor[ 2 ] * 0.5; - glow[ 3 ] = newColor[ 3 ] * 0.2; - - outer[ 0 ] = newColor[ 0 ]; - outer[ 1 ] = newColor[ 1 ]; - outer[ 2 ] = newColor[ 2 ]; - outer[ 3 ] = newColor[ 3 ]; - - inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5; - inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5; - inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5; - inner[ 3 ] = newColor[ 3 ]; - - white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f; - - trap_R_SetColor( glow ); - Text_PaintChar( x - 1.5, y - yadj - 1.5, - glyph->imageWidth + 3, - glyph->imageHeight + 3, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( outer ); - Text_PaintChar( x - 1, y - yadj - 1, - glyph->imageWidth + 2, - glyph->imageHeight + 2, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( inner ); - Text_PaintChar( x - 0.5, y - yadj - 0.5, - glyph->imageWidth + 1, - glyph->imageHeight + 1, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( white ); - } - - Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - - x += (glyph->xSkip * useScale) + adjust; - s++; - count++; - } +/* +================== +UI_SanitiseString + +Remove color codes and non-alphanumeric characters from a string +================== +*/ +void UI_SanitiseString( char *in, char *out, int len ) +{ + len--; + + while( *in && len > 0 ) + { + if( Q_IsColorString( in ) ) + { + in += 2; // skip color code + continue; + } + + if( isalnum( *in ) ) + { + *out++ = tolower( *in ); + len--; + } + in++; } - trap_R_SetColor( NULL ); - } + *out = 0; } -void Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph, *glyph2; - float yadj; - float useScale; - fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; - if (scale <= ui_smallFont.value) { - font = &uiInfo.uiDC.Assets.smallFont; - } else if (scale >= ui_bigFont.value) { - font = &uiInfo.uiDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - if (text) { - const char *s = text; // bk001206 - unsigned - trap_R_SetColor( color ); - memcpy(&newColor[0], &color[0], sizeof(vec4_t)); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - glyph2 = &font->glyphs[ (int) cursor]; // bk001206 - possible signed char - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - yadj = useScale * glyph->top; - if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { - int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; - colorBlack[3] = newColor[3]; - trap_R_SetColor( colorBlack ); - Text_PaintChar(x + ofs, y - yadj + ofs, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - colorBlack[3] = 1.0; - trap_R_SetColor( newColor ); - } - else if( style == ITEM_TEXTSTYLE_NEON ) - { - vec4_t glow, outer, inner, white; - - glow[ 0 ] = newColor[ 0 ] * 0.5; - glow[ 1 ] = newColor[ 1 ] * 0.5; - glow[ 2 ] = newColor[ 2 ] * 0.5; - glow[ 3 ] = newColor[ 3 ] * 0.2; - - outer[ 0 ] = newColor[ 0 ]; - outer[ 1 ] = newColor[ 1 ]; - outer[ 2 ] = newColor[ 2 ]; - outer[ 3 ] = newColor[ 3 ]; - - inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5; - inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5; - inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5; - inner[ 3 ] = newColor[ 3 ]; - - white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f; - - trap_R_SetColor( glow ); - Text_PaintChar( x - 1.5, y - yadj - 1.5, - glyph->imageWidth + 3, - glyph->imageHeight + 3, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( outer ); - Text_PaintChar( x - 1, y - yadj - 1, - glyph->imageWidth + 2, - glyph->imageHeight + 2, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( inner ); - Text_PaintChar( x - 0.5, y - yadj - 0.5, - glyph->imageWidth + 1, - glyph->imageHeight + 1, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph ); - - trap_R_SetColor( white ); - } - - Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - - // CG_DrawPic(x, y - yadj, scale * uiDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * uiDC.Assets.textFont.glyphs[text[i]].imageHeight, uiDC.Assets.textFont.glyphs[text[i]].glyph); - yadj = useScale * glyph2->top; - if (count == cursorPos && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) { - Text_PaintChar(x, y - yadj, - glyph2->imageWidth, - glyph2->imageHeight, - useScale, - glyph2->s, - glyph2->t, - glyph2->s2, - glyph2->t2, - glyph2->glyph); - } - - x += (glyph->xSkip * useScale); - s++; - count++; - } - } - // need to paint cursor at end of text - if (cursorPos == len && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) { - yadj = useScale * glyph2->top; - Text_PaintChar(x, y - yadj, - glyph2->imageWidth, - glyph2->imageHeight, - useScale, - glyph2->s, - glyph2->t, - glyph2->s2, - glyph2->t2, - glyph2->glyph); +/* +================== +UI_PortFromAddress +================== +*/ +static int UI_PortFromAddress(const char *adrStr) { + int i; + int portLength = 0; + char portStr[MAX_ADDRESSLENGTH] = ""; + qboolean foundPort = qfalse; + if (!adrStr || !adrStr[0]) { + return -1; } + for (i = 0; adrStr[i] && (adrStr[i] != ' '); i++) { + if (!foundPort) { + if (adrStr[i] == ':') { + foundPort = qtrue; + } + + continue; + } + + portStr[portLength] = adrStr[i]; + portLength++; + } - trap_R_SetColor( NULL ); - } + if (portLength) { + return atoi(portStr); + } else { + return -1; + } } +/* +================== +UI_ProtocolFromAddress -static void Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - if (text) { - const char *s = text; // bk001206 - unsigned - float max = *maxX; - float useScale; - fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; - if (scale <= ui_smallFont.value) { - font = &uiInfo.uiDC.Assets.smallFont; - } else if (scale > ui_bigFont.value) { - font = &uiInfo.uiDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - trap_R_SetColor( color ); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - float yadj = useScale * glyph->top; - if (Text_Width(s, useScale, 1) + x > max) { - *maxX = 0; - break; - } - Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - x += (glyph->xSkip * useScale) + adjust; - *maxX = x; - count++; - s++; - } +returns 2 if 1.1 is detected, returns 1 if gpp is detected, otherwise returns 0 +================== +*/ +static int UI_ProtocolFromAddress(const char *adrStr) { + int i; + + if (!adrStr || !adrStr[0]) { + return 0; } - trap_R_SetColor( NULL ); - } -} + for (i = 0; adrStr[i]; i++) { + if (adrStr[i] == '-') { + if (adrStr[i+1]) { + switch (adrStr[i+1]) { + case '1': + return 2; + case 'g': + return 1; + + default: + return 0; + } + } + } + } -void UI_ShowPostGame(qboolean newHigh) { - trap_Cvar_Set ("cg_cameraOrbit", "0"); - trap_Cvar_Set("cg_thirdPerson", "0"); - trap_Cvar_Set( "sv_killserver", "1" ); - uiInfo.soundHighScore = newHigh; - _UI_SetActiveMenu(UIMENU_POSTGAME); + return 0; } + /* -================= -_UI_Refresh -================= +================== +UI_RemoveServerFromDisplayList +================== */ +static void UI_RemoveServerFromDisplayList(int num) +{ + int i, j; + static char info[MAX_STRING_CHARS]; -void UI_DrawCenteredPic(qhandle_t image, int w, int h) { - int x, y; - x = (SCREEN_WIDTH - w) / 2; - y = (SCREEN_HEIGHT - h) / 2; - UI_DrawHandlePic(x, y, w, h, image); -} + for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) + { + if (uiInfo.serverStatus.displayServers[i] == num) + { + uiInfo.serverStatus.numDisplayServers--; -int frameCount = 0; -int startTime; + trap_LAN_GetServerInfo(ui_netSource.integer, num, info, MAX_STRING_CHARS); -#define UI_FPS_FRAMES 4 -void _UI_Refresh( int realtime ) -{ - static int index; - static int previousTimes[UI_FPS_FRAMES]; - - //if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) { - // return; - //} - - uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime; - uiInfo.uiDC.realTime = realtime; - - previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime; - index++; - if ( index > UI_FPS_FRAMES ) { - int i, total; - // average multiple frames together to smooth changes out a bit - total = 0; - for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) { - total += previousTimes[i]; - } - if ( !total ) { - total = 1; - } - uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total; - } - - - - UI_UpdateCvars(); - - if (Menu_Count() > 0) { - // paint all the menus - Menu_PaintAll(); - // refresh server browser list - UI_DoServerRefresh(); - // refresh server status - UI_BuildServerStatus(qfalse); - // refresh find player list - UI_BuildFindPlayerList(qfalse); - } - - // draw cursor - UI_SetColor( NULL ); - - //TA: don't draw the cursor whilst loading - if( Menu_Count( ) > 0 && !trap_Cvar_VariableValue( "ui_loading" ) && !trap_Cvar_VariableValue( "ui_hideCursor" ) ) - UI_DrawHandlePic( uiInfo.uiDC.cursorx-16, uiInfo.uiDC.cursory-16, 32, 32, uiInfo.uiDC.Assets.cursor); - -#ifndef NDEBUG - if (uiInfo.uiDC.debug) - { - // cursor coordinates - //FIXME - //UI_DrawString( 0, 0, va("(%d,%d)",uis.cursorx,uis.cursory), UI_LEFT|UI_SMALLFONT, colorRed ); - } -#endif + for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) + uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1]; + return; + } + } } /* -================= -_UI_Shutdown -================= +================== +UI_InsertServerIntoDisplayList +================== */ -void _UI_Shutdown( void ) { - trap_LAN_SaveCachedServers(); -} +static qboolean UI_InsertServerIntoDisplayList(int num, int position) +{ + int i; + int hostnameLen; + int protocol; + int port; + char adrstr[MAX_ADDRESSLENGTH]; + char hostname[MAX_HOSTNAME_LENGTH]; + char basehostname[MAX_HOSTNAME_LENGTH]; + static char info[MAX_STRING_CHARS]; + + if (position < 0 || position > uiInfo.serverStatus.numDisplayServers) + return qfalse; -char *defaultMenu = NULL; - -char *GetMenuBuffer(const char *filename) { - int len; - fileHandle_t f; - static char buf[MAX_MENUFILE]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); - return defaultMenu; - } - if ( len >= MAX_MENUFILE ) { - trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); - trap_FS_FCloseFile( f ); - return defaultMenu; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - //COM_Compress(buf); - return buf; + trap_LAN_GetServerInfo(ui_netSource.integer, num, info, MAX_STRING_CHARS); -} + if (!UI_ServerInfoIsValid(info)) // don't list servers with invalid info + return qfalse; -qboolean Asset_Parse(int handle) { - pc_token_t token; - const char *tempStr; + Q_strncpyz(hostname, Info_ValueForKey(info, "hostname"), MAX_HOSTNAME_LENGTH); - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (Q_stricmp(token.string, "{") != 0) { - return qfalse; - } + hostnameLen = strlen(hostname); - while ( 1 ) { + trap_LAN_GetServerAddressString( + ui_netSource.integer, num, adrstr, MAX_ADDRESSLENGTH); - memset(&token, 0, sizeof(pc_token_t)); + protocol = UI_ProtocolFromAddress(adrstr); - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; + port = UI_PortFromAddress(adrstr); - if (Q_stricmp(token.string, "}") == 0) { - return qtrue; + if (protocol && hostnameLen > 6) { + // strip the protocol tags from the hostname + hostname[hostnameLen - 6] = '\0'; } - // font - if (Q_stricmp(token.string, "font") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) { - return qfalse; - } - trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.textFont); - uiInfo.uiDC.Assets.fontRegistered = qtrue; - continue; - } + UI_SanitiseString(hostname, basehostname, sizeof(basehostname)); - if (Q_stricmp(token.string, "smallFont") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) { - return qfalse; - } - trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont); - continue; - } + // check if this is a duplicate listing of a multiprotocol server + for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) { + int j; + int clients; + int protocol2; + int port2; + char info2[MAX_STRING_CHARS]; + char adrstr2[MAX_ADDRESSLENGTH]; - if (Q_stricmp(token.string, "bigFont") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) { - return qfalse; - } - trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont); - continue; - } + trap_LAN_GetServerAddressString( + ui_netSource.integer, + uiInfo.serverStatus.displayServers[i], adrstr2, MAX_ADDRESSLENGTH); + protocol2 = UI_ProtocolFromAddress(adrstr2); - // gradientbar - if (Q_stricmp(token.string, "gradientbar") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); - continue; - } + port2 = UI_PortFromAddress(adrstr2); - // enterMenuSound - if (Q_stricmp(token.string, "menuEnterSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } + //compare the addresses + if (adrstr[0] != adrstr2[0]) { + continue; + } else { + qboolean skip = qfalse; + + for (j = 1; adrstr[j] && adrstr2[j]; j++) { + if(adrstr[j] != adrstr2[j]) { + skip = qtrue; + break; + } + + //don't compare ports + if (adrstr[j] == ':') { + break; + } + } - // exitMenuSound - if (Q_stricmp(token.string, "menuExitSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } + if (skip) { + continue; + } + } - // itemFocusSound - if (Q_stricmp(token.string, "itemFocusSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } + trap_LAN_GetServerInfo( + ui_netSource.integer, + uiInfo.serverStatus.displayServers[i], info2, MAX_STRING_CHARS); - // menuBuzzSound - if (Q_stricmp(token.string, "menuBuzzSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } + // if the ports are not the same, check to see if the host names are the + // same for older multiprotocol servers + if(port != port2) { + int hostnameLen2; + char hostname2[MAX_HOSTNAME_LENGTH]; + char basehostname2[MAX_HOSTNAME_LENGTH]; - if (Q_stricmp(token.string, "cursor") == 0) { - if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr)) { - return qfalse; - } - uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr); - continue; - } + Q_strncpyz(hostname2, Info_ValueForKey(info2, "hostname"), MAX_HOSTNAME_LENGTH); - if (Q_stricmp(token.string, "fadeClamp") == 0) { - if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) { - return qfalse; - } - continue; - } + hostnameLen2 = strlen(hostname2); - if (Q_stricmp(token.string, "fadeCycle") == 0) { - if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) { - return qfalse; - } - continue; - } + if (protocol2 && hostnameLen2 > 6) { + // strip the protocol tags from the hostname + hostname2[hostnameLen2 - 7] = '\0'; + } - if (Q_stricmp(token.string, "fadeAmount") == 0) { - if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) { - return qfalse; - } - continue; - } + UI_SanitiseString(hostname2, basehostname2, sizeof(basehostname2)); - if (Q_stricmp(token.string, "shadowX") == 0) { - if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) { - return qfalse; - } - continue; - } + //compare the hostnames + if (Q_stricmp(basehostname, basehostname2)) { + continue; + } + } - if (Q_stricmp(token.string, "shadowY") == 0) { - if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) { - return qfalse; - } - continue; - } + uiInfo.serverStatus.numDuplicateMultiprotocolServers++; - if (Q_stricmp(token.string, "shadowColor") == 0) { - if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) { - return qfalse; - } - uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; - continue; + //show only the most recent protocol for a given server + if (protocol >= protocol2) { + clients = atoi(Info_ValueForKey(info, "clients")); + uiInfo.serverStatus.numDuplicateMultiprotocolServerClients += clients; + return qfalse; + } else { + clients = atoi(Info_ValueForKey(info2, "clients")); + uiInfo.serverStatus.numDuplicateMultiprotocolServerClients += clients; + UI_RemoveServerFromDisplayList(uiInfo.serverStatus.displayServers[i]); + i--; + continue; + } } - } - return qfalse; -} + //insert the server + uiInfo.serverStatus.numDisplayServers++; -void Font_Report( void ) { - int i; - Com_Printf("Font Info\n"); - Com_Printf("=========\n"); - for ( i = 32; i < 96; i++) { - Com_Printf("Glyph handle %i: %i\n", i, uiInfo.uiDC.Assets.textFont.glyphs[i].glyph); - } -} + for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) + uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i - 1]; -void UI_Report( void ) { - String_Report(); - //Font_Report(); + uiInfo.serverStatus.displayServers[position] = num; + return qtrue; } -void UI_ParseMenu(const char *menuFile) { - int handle; - pc_token_t token; - - /*Com_Printf("Parsing menu file:%s\n", menuFile);*/ +/* +================== +UI_BinaryServerInsertion +================== +*/ +static qboolean UI_BinaryServerInsertion(int num) +{ + int mid, offset, res, len; - handle = trap_Parse_LoadSource(menuFile); - if (!handle) { - return; - } + // use binary search to insert server + len = uiInfo.serverStatus.numDisplayServers; + mid = len; + offset = 0; + res = 0; - while ( 1 ) { - memset(&token, 0, sizeof(pc_token_t)); - if (!trap_Parse_ReadToken( handle, &token )) { - break; - } + while (mid > 0) + { + mid = len >> 1; + // + res = trap_LAN_CompareServers(ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, + num, uiInfo.serverStatus.displayServers[offset + mid]); + // if equal - //if ( Q_stricmp( token, "{" ) ) { - // Com_Printf( "Missing { in menu file\n" ); - // break; - //} + if (res == 0) + { + return UI_InsertServerIntoDisplayList(num, offset + mid); + } - //if ( menuCount == MAX_MENUS ) { - // Com_Printf( "Too many menus!\n" ); - // break; - //} + // if larger + else if (res == 1) + { + offset += mid; + len -= mid; + } - if ( token.string[0] == '}' ) { - break; + // if smaller + else + len -= mid; } - if (Q_stricmp(token.string, "assetGlobalDef") == 0) { - if (Asset_Parse(handle)) { - continue; - } else { - break; - } - } + if (res == 1) + offset++; - if (Q_stricmp(token.string, "menudef") == 0) { - // start a new menu - Menu_New(handle); - } - } - trap_Parse_FreeSource(handle); + return UI_InsertServerIntoDisplayList(num, offset); } +typedef struct { + char *name, *altName; +} + +serverStatusCvar_t; + +serverStatusCvar_t serverStatusCvars[] = {{"sv_hostname", "Name"}, {"Address", ""}, {"gamename", "Game name"}, + {"mapname", "Map"}, {"version", ""}, {"protocol", ""}, {"timelimit", ""}, {NULL, NULL}}; + /* -=============== -UI_FindInfoPaneByName -=============== +================== +UI_SortServerStatusInfo +================== */ -tremInfoPane_t *UI_FindInfoPaneByName( const char *name ) + +static int UI_SortServerStatusCompare(const void *a, const void *b) +{ + const char **la = (const char **)a; + const char **lb = (const char **)b; + + return strcmp(la[0], lb[0]); +} + +static void UI_SortServerStatusInfo(serverStatusInfo_t *info) { - int i; + int i, j, index; + char *tmp1, *tmp2; - for( i = 0; i < uiInfo.tremInfoPaneCount; i++ ) - { - if( !Q_stricmp( uiInfo.tremInfoPanes[ i ].name, name ) ) - return &uiInfo.tremInfoPanes[ i ]; - } + index = 0; - //create a dummy infopane demanding the user write the infopane - uiInfo.tremInfoPanes[ i ].name = String_Alloc( name ); - strncpy( uiInfo.tremInfoPanes[ i ].text, "Not implemented.\n\nui/infopanes.def\n", MAX_INFOPANE_TEXT ); - Q_strcat( uiInfo.tremInfoPanes[ i ].text, MAX_INFOPANE_TEXT, String_Alloc( name ) ); + for (i = 0; serverStatusCvars[i].name; i++) + { + for (j = 0; j < info->numLines; j++) + { + if (!info->lines[j][1] || info->lines[j][1][0]) + continue; - uiInfo.tremInfoPaneCount++; + if (!Q_stricmp(serverStatusCvars[i].name, info->lines[j][0])) + { + // swap lines + tmp1 = info->lines[index][0]; + tmp2 = info->lines[index][3]; + info->lines[index][0] = info->lines[j][0]; + info->lines[index][3] = info->lines[j][3]; + info->lines[j][0] = tmp1; + info->lines[j][3] = tmp2; + // + + if (strlen(serverStatusCvars[i].altName)) + info->lines[index][0] = serverStatusCvars[i].altName; + + index++; + } + } + } - return &uiInfo.tremInfoPanes[ i ]; + // sort remaining cvars + qsort(info->lines + index, info->numLines - index, sizeof(info->lines[0]), UI_SortServerStatusCompare); } /* -=============== -UI_LoadInfoPane -=============== +================== +UI_GetServerStatusInfo +================== */ -qboolean UI_LoadInfoPane( int handle ) +static int UI_GetServerStatusInfo(const char *serverAddress, serverStatusInfo_t *info) { - pc_token_t token; - qboolean valid = qfalse; - - while( 1 ) - { - memset( &token, 0, sizeof( pc_token_t ) ); - - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + char *p, *score, *ping, *name; + int i, len; - if( !Q_stricmp( token.string, "name" ) ) + if (!info) { - memset( &token, 0, sizeof( pc_token_t ) ); + trap_LAN_ServerStatus(serverAddress, NULL, 0); + return qfalse; + } - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + memset(info, 0, sizeof(*info)); - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].name = String_Alloc( token.string ); - valid = qtrue; - } - else if( !Q_stricmp( token.string, "graphic" ) ) + if (trap_LAN_ServerStatus(serverAddress, info->text, sizeof(info->text))) { - int *graphic; + Q_strncpyz(info->address, serverAddress, sizeof(info->address)); + p = info->text; + info->numLines = 0; + info->lines[info->numLines][0] = "Address"; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = info->address; + info->numLines++; + // get the cvars - memset( &token, 0, sizeof( pc_token_t ) ); + while (p && *p) + { + p = strchr(p, '\\'); - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + if (!p) + break; - graphic = &uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].numGraphics; + *p++ = '\0'; - if( !Q_stricmp( token.string, "top" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_TOP; - else if( !Q_stricmp( token.string, "bottom" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_BOTTOM; - else if( !Q_stricmp( token.string, "left" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_LEFT; - else if( !Q_stricmp( token.string, "right" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_RIGHT; - else - break; + if (*p == '\\') + break; - memset( &token, 0, sizeof( pc_token_t ) ); + info->lines[info->numLines][0] = p; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + p = strchr(p, '\\'); - if( !Q_stricmp( token.string, "center" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = -1; - else - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = token.intvalue; + if (!p) + break; - memset( &token, 0, sizeof( pc_token_t ) ); + *p++ = '\0'; - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + info->lines[info->numLines][3] = p; + info->numLines++; - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].graphic = - trap_R_RegisterShaderNoMip( token.string ); + if (info->numLines >= MAX_SERVERSTATUS_LINES) + break; + } - memset( &token, 0, sizeof( pc_token_t ) ); + UI_SortServerStatusInfo(info); - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + // get the player list + if (info->numLines < MAX_SERVERSTATUS_LINES - 3) + { + // empty line + info->lines[info->numLines][0] = ""; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = ""; + info->numLines++; + // header + info->lines[info->numLines][0] = "num"; + info->lines[info->numLines][1] = "score"; + info->lines[info->numLines][2] = "ping"; + info->lines[info->numLines][3] = "name"; + info->numLines++; + // parse players + i = 0; + len = 0; + + while (p && *p) + { + if (*p == '\\') + *p++ = '\0'; - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].width = token.intvalue; + if (!p) + break; - memset( &token, 0, sizeof( pc_token_t ) ); + score = p; - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + p = strchr(p, ' '); - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].height = token.intvalue; + if (!p) + break; - //increment graphics - (*graphic)++; + *p++ = '\0'; - if( *graphic == MAX_INFOPANE_GRAPHICS ) - break; - } - else if( !Q_stricmp( token.string, "text" ) ) - { - memset( &token, 0, sizeof( pc_token_t ) ); + ping = p; - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + p = strchr(p, ' '); - Q_strcat( uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].text, MAX_INFOPANE_TEXT, token.string ); - } - else if( !Q_stricmp( token.string, "align" ) ) - { - memset( &token, 0, sizeof( pc_token_t ) ); + if (!p) + break; - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + *p++ = '\0'; - if( !Q_stricmp( token.string, "left" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_LEFT; - else if( !Q_stricmp( token.string, "right" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_RIGHT; - else if( !Q_stricmp( token.string, "center" ) ) - uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_CENTER; - } - else if( token.string[ 0 ] == '}' ) - { - //reached the end, break - break; - } - else - break; - } + name = p; - if( valid ) - { - uiInfo.tremInfoPaneCount++; - return qtrue; - } - else - { - return qfalse; - } -} + Com_sprintf(&info->pings[len], sizeof(info->pings) - len, "%d", i); -/* -=============== -UI_LoadInfoPanes -=============== -*/ -void UI_LoadInfoPanes( const char *file ) -{ - pc_token_t token; - int handle; - int count; + info->lines[info->numLines][0] = &info->pings[len]; - uiInfo.tremInfoPaneCount = count = 0; + len += strlen(&info->pings[len]) + 1; - handle = trap_Parse_LoadSource( file ); + info->lines[info->numLines][1] = score; + info->lines[info->numLines][2] = ping; + info->lines[info->numLines][3] = name; + info->numLines++; - if( !handle ) - { - trap_Error( va( S_COLOR_YELLOW "infopane file not found: %s\n", file ) ); - return; - } + if (info->numLines >= MAX_SERVERSTATUS_LINES) + break; - while( 1 ) - { - if( !trap_Parse_ReadToken( handle, &token ) ) - break; + p = strchr(p, '\\'); - if( token.string[ 0 ] == 0 ) - break; + if (!p) + break; - if( token.string[ 0 ] == '{' ) - { - if( UI_LoadInfoPane( handle ) ) - count++; + *p++ = '\0'; + + // + i++; + } + } - if( count == MAX_INFOPANES ) - break; + return qtrue; } - } - trap_Parse_FreeSource( handle ); + return qfalse; } -qboolean Load_Menu(int handle) { - pc_token_t token; +/* +================== +stristr +================== +*/ +static char *stristr(char *str, char *charset) +{ + int i; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (token.string[0] != '{') { - return qfalse; - } + while (*str) + { + for (i = 0; charset[i] && str[i]; i++) + if (toupper(charset[i]) != toupper(str[i])) + break; - while ( 1 ) { + if (!charset[i]) + return str; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; + str++; + } + + return NULL; +} + +/* +================== +UI_BuildFindPlayerList +================== +*/ +static void UI_FeederSelection(int feederID, int index); - if ( token.string[0] == 0 ) { - return qfalse; +static void UI_BuildFindPlayerList(qboolean force) +{ + static int numFound, numTimeOuts; + int i, j, k, resend; + serverStatusInfo_t info; + char name[MAX_NAME_LENGTH + 2]; + char infoString[MAX_STRING_CHARS]; + qboolean duplicate; + + if (!force) + { + if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) + return; } + else + { + memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus)); + uiInfo.numFoundPlayerServers = 0; + uiInfo.currentFoundPlayerServer = 0; + trap_Cvar_VariableStringBuffer("ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName)); + Q_CleanStr(uiInfo.findPlayerName); + // should have a string of some length + + if (!strlen(uiInfo.findPlayerName)) + { + uiInfo.nextFindPlayerRefresh = 0; + return; + } + + // set resend time + resend = ui_serverStatusTimeOut.integer / 2 - 10; - if ( token.string[0] == '}' ) { - return qtrue; + if (resend < 50) + resend = 50; + + trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend)); + // reset all server status requests + trap_LAN_ServerStatus(NULL, NULL, 0); + // + uiInfo.numFoundPlayerServers = 1; + Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1]), "searching %d...", + uiInfo.pendingServerStatus.num); + numFound = 0; + numTimeOuts++; } - UI_ParseMenu(token.string); - } - return qfalse; -} + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + // if this pending server is valid -void UI_LoadMenus(const char *menuFile, qboolean reset) { - pc_token_t token; - int handle; - int start; + if (uiInfo.pendingServerStatus.server[i].valid) + { + // try to get the server status for this server - start = trap_Milliseconds(); + if (UI_GetServerStatusInfo(uiInfo.pendingServerStatus.server[i].adrstr, &info)) + { + // + numFound++; + // parse through the server status lines + + for (j = 0; j < info.numLines; j++) + { + // should have ping info + + if (!info.lines[j][2] || !info.lines[j][2][0]) + continue; + + // clean string first + Q_strncpyz(name, info.lines[j][3], sizeof(name)); + + Q_CleanStr(name); + + duplicate = qfalse; + + for (k = 0; k < uiInfo.numFoundPlayerServers - 1; k++) + { + if (Q_strncmp(uiInfo.foundPlayerServerAddresses[k], uiInfo.pendingServerStatus.server[i].adrstr, + MAX_ADDRESSLENGTH) == 0) + duplicate = qtrue; + } + + // if the player name is a substring + if (stristr(name, uiInfo.findPlayerName) && !duplicate) + { + // add to found server list if we have space (always leave space for a line with the number + // found) + + if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS - 1) + { + // + Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers - 1], + uiInfo.pendingServerStatus.server[i].adrstr, + sizeof(uiInfo.foundPlayerServerAddresses[0])); + Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + uiInfo.pendingServerStatus.server[i].name, sizeof(uiInfo.foundPlayerServerNames[0])); + uiInfo.numFoundPlayerServers++; + } + else + { + // can't add any more so we're done + uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers; + } + } + } + + Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1]), "searching %d/%d...", + numFound, uiInfo.pendingServerStatus.num); + // retrieved the server status so reuse this spot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + } + } - handle = trap_Parse_LoadSource( menuFile ); - if (!handle) { - trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); - handle = trap_Parse_LoadSource( "ui/menus.txt" ); - if (!handle) { - trap_Error( va( S_COLOR_RED "default menu file not found: ui/menus.txt, unable to continue!\n" ) ); - } - } + // if empty pending slot or timed out + if (!uiInfo.pendingServerStatus.server[i].valid || + uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) + { + if (uiInfo.pendingServerStatus.server[i].valid) + numTimeOuts++; - ui_new.integer = 1; + // reset server status request for this address + UI_GetServerStatusInfo(uiInfo.pendingServerStatus.server[i].adrstr, NULL); - if (reset) { - Menu_Reset(); - } + // reuse pending slot + uiInfo.pendingServerStatus.server[i].valid = qfalse; - while ( 1 ) { - if (!trap_Parse_ReadToken(handle, &token)) - break; - if( token.string[0] == 0 || token.string[0] == '}') { - break; + // if we didn't try to get the status of all servers in the main browser yet + if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) + { + uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime; + trap_LAN_GetServerAddressString(ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], + uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr)); + + trap_LAN_GetServerInfo(ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString)); + + Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), + sizeof(uiInfo.pendingServerStatus.server[0].name)); + + uiInfo.pendingServerStatus.server[i].valid = qtrue; + uiInfo.pendingServerStatus.num++; + Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1]), "searching %d/%d...", + numFound, uiInfo.pendingServerStatus.num); + } + } + } + + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) + { + if (uiInfo.pendingServerStatus.server[i].valid) + break; } - if ( token.string[0] == '}' ) { - break; + // if still trying to retrieve server status info + if (i < MAX_SERVERSTATUSREQUESTS) + uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25; + else + { + // add a line that shows the number of servers found + + if (!uiInfo.numFoundPlayerServers) + { + Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found"); + } + else + { + Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof(uiInfo.foundPlayerServerAddresses[0]), "%d server%s found with player %s", + uiInfo.numFoundPlayerServers - 1, uiInfo.numFoundPlayerServers == 2 ? "" : "s", uiInfo.findPlayerName); + } + + uiInfo.nextFindPlayerRefresh = 0; + // show the server status info for the selected server + UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer); } +} - if (Q_stricmp(token.string, "loadmenu") == 0) { - if (Load_Menu(handle)) { - continue; - } else { - break; - } +/* +================== +UI_BuildServerStatus +================== +*/ +static void UI_BuildServerStatus(qboolean force) +{ + if (uiInfo.nextFindPlayerRefresh) + return; + + if (!force) + { + if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) + return; + } + else + { + Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); + uiInfo.serverStatusInfo.numLines = 0; + // reset all server status requests + trap_LAN_ServerStatus(NULL, NULL, 0); } - } - Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); + if (uiInfo.serverStatus.currentServer < 0 || + uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || + uiInfo.serverStatus.numDisplayServers == 0) + return; - trap_Parse_FreeSource( handle ); + if (UI_GetServerStatusInfo(uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo)) + { + uiInfo.nextServerStatusRefresh = 0; + UI_GetServerStatusInfo(uiInfo.serverStatusAddress, NULL); + } + else + uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500; } -void UI_Load( void ) { - char lastName[1024]; - menuDef_t *menu = Menu_GetFocused(); - char *menuSet = UI_Cvar_VariableString("ui_menuFiles"); - if (menu && menu->window.name) { - strcpy(lastName, menu->window.name); - } - if (menuSet == NULL || menuSet[0] == '\0') { - menuSet = "ui/menus.txt"; - } +/* +================== +UI_BuildServerDisplayList +================== +*/ +static void UI_BuildServerDisplayList(int force) +{ + int i, count, clients, maxClients, ping, len, visible; + char info[MAX_STRING_CHARS]; + static int numinvisible; - String_Init(); + if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) + return; -/* UI_ParseGameInfo("gameinfo.txt"); - UI_LoadArenas();*/ + // if we shouldn't reset + if (force == 2) + force = 0; - UI_LoadMenus(menuSet, qtrue); - Menus_CloseAll(); - Menus_ActivateByName(lastName); + // do motd updates here too + trap_Cvar_VariableStringBuffer("cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd)); -} + len = strlen(uiInfo.serverStatus.motd); -static const char *handicapValues[] = {"None","95","90","85","80","75","70","65","60","55","50","45","40","35","30","25","20","15","10","5",NULL}; + if (len != uiInfo.serverStatus.motdLen) + { + uiInfo.serverStatus.motdLen = len; + uiInfo.serverStatus.motdWidth = -1; + } -static void UI_DrawHandicap(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int i, h; + if (force) + { + numinvisible = 0; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + uiInfo.serverStatus.numDuplicateMultiprotocolServers = 0; + uiInfo.serverStatus.numDuplicateMultiprotocolServerClients = 0; + // set list box index to zero + Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL); + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); + } + + // get the server count (comes from the master) + count = trap_LAN_GetServerCount(ui_netSource.integer); + + if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0)) + { + // still waiting on a response from the master + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + uiInfo.serverStatus.numDuplicateMultiprotocolServers = 0; + uiInfo.serverStatus.numDuplicateMultiprotocolServerClients = 0; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500; + return; + } - h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); - i = 20 - h / 5; + visible = qfalse; - Text_Paint(rect->x, rect->y, scale, color, handicapValues[i], 0, 0, textStyle); -} + for (i = 0; i < count; i++) + { + // if we already got info for this server -static void UI_DrawClanName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_teamName"), 0, 0, textStyle); -} + if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) + continue; + visible = qtrue; + // get the ping for this server + ping = trap_LAN_GetServerPing(ui_netSource.integer, i); -static void UI_SetCapFragLimits(qboolean uiVars) { - int cap = 5; - int frag = 10; - if (uiVars) { - trap_Cvar_Set("ui_captureLimit", va("%d", cap)); - trap_Cvar_Set("ui_fragLimit", va("%d", frag)); - } else { - trap_Cvar_Set("capturelimit", va("%d", cap)); - trap_Cvar_Set("fraglimit", va("%d", frag)); - } -} -// ui_gameType assumes gametype 0 is -1 ALL and will not show -static void UI_DrawGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_gameType.integer].gameType, 0, 0, textStyle); -} + if (ping > 0 || ui_netSource.integer == AS_FAVORITES) + { + trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS); -static void UI_DrawNetGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - if (ui_netGameType.integer < 0 || ui_netGameType.integer > uiInfo.numGameTypes) { - trap_Cvar_Set("ui_netGameType", "0"); - trap_Cvar_Set("ui_actualNetGameType", "0"); - } - Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_netGameType.integer].gameType , 0, 0, textStyle); -} + clients = atoi(Info_ValueForKey(info, "clients")); + uiInfo.serverStatus.numPlayersOnServers += clients; -static void UI_DrawJoinGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - if (ui_joinGameType.integer < 0 || ui_joinGameType.integer > uiInfo.numJoinGameTypes) { - trap_Cvar_Set("ui_joinGameType", "0"); - } - Text_Paint(rect->x, rect->y, scale, color, uiInfo.joinGameTypes[ui_joinGameType.integer].gameType , 0, 0, textStyle); -} + if (ui_browserShowEmpty.integer == 0) + { + if (clients == 0) + { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + continue; + } + } + if (ui_browserShowFull.integer == 0) + { + maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + + if (clients == maxClients) + { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + continue; + } + } + // make sure we never add a favorite server twice + if (ui_netSource.integer == AS_FAVORITES) + UI_RemoveServerFromDisplayList(i); -static int UI_TeamIndexFromName(const char *name) { - int i; + // insert the server into the list + UI_BinaryServerInsertion(i); - if (name && *name) { - for (i = 0; i < uiInfo.teamCount; i++) { - if (Q_stricmp(name, uiInfo.teamList[i].teamName) == 0) { - return i; - } + // done with this server + if (ping > 0) + { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + numinvisible++; + } + } } - } - return 0; + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime; + + // if there were no servers visible for ping updates + if (!visible) + { + // UI_StopServerRefresh(); + // uiInfo.serverStatus.nextDisplayRefresh = 0; + } } -static void UI_DrawClanLogo(rectDef_t *rect, float scale, vec4_t color) { - int i; - i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - if (i >= 0 && i < uiInfo.teamCount) { - trap_R_SetColor( color ); +/* +================= +UI_StopServerRefresh +================= +*/ +static void UI_StopServerRefresh(void) +{ + int count; - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + if (!uiInfo.serverStatus.refreshActive) + { + // not currently refreshing + return; } - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon); - trap_R_SetColor(NULL); - } + uiInfo.serverStatus.refreshActive = qfalse; + Com_Printf("%d servers listed in browser with %d players.\n", uiInfo.serverStatus.numDisplayServers, + uiInfo.serverStatus.numPlayersOnServers - + uiInfo.serverStatus.numDuplicateMultiprotocolServerClients); + count = trap_LAN_GetServerCount(ui_netSource.integer); + + if (count - uiInfo.serverStatus.numDisplayServers - uiInfo.serverStatus.numDuplicateMultiprotocolServers > 0) + { + Com_Printf( + "%d servers not listed due to packet loss, invalid info," + " or pings higher than %d\n", + count - uiInfo.serverStatus.numDisplayServers - uiInfo.serverStatus.numDuplicateMultiprotocolServers, + (int)trap_Cvar_VariableValue("cl_maxPing")); + } } -static void UI_DrawClanCinematic(rectDef_t *rect, float scale, vec4_t color) { - int i; - i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - if (i >= 0 && i < uiInfo.teamCount) { +/* +================= +UI_DoServerRefresh +================= +*/ +static void UI_DoServerRefresh(void) +{ + qboolean wait = qfalse; + + if (!uiInfo.serverStatus.refreshActive) + return; - if (uiInfo.teamList[i].cinematic >= -2) { - if (uiInfo.teamList[i].cinematic == -1) { - uiInfo.teamList[i].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.teamList[i].imageName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); - } - if (uiInfo.teamList[i].cinematic >= 0) { - trap_CIN_RunCinematic(uiInfo.teamList[i].cinematic); - trap_CIN_SetExtents(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.teamList[i].cinematic); - } else { - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal); - trap_R_SetColor(NULL); - uiInfo.teamList[i].cinematic = -2; - } - } else { - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon); - trap_R_SetColor(NULL); + if (ui_netSource.integer != AS_FAVORITES) + { + if (ui_netSource.integer == AS_LOCAL) + { + if (!trap_LAN_GetServerCount(ui_netSource.integer)) + wait = qtrue; + } + else + { + if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) + wait = qtrue; + } } - } -} + if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) + { + if (wait) + return; + } -static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) { - if (uiInfo.previewMovie > -2) { - uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); - if (uiInfo.previewMovie >= 0) { - trap_CIN_RunCinematic(uiInfo.previewMovie); - trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.previewMovie); - } else { - uiInfo.previewMovie = -2; + // if still trying to retrieve pings + if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + else if (!wait) + { + // get the last servers in the list + UI_BuildServerDisplayList(2); + // stop the refresh + UI_StopServerRefresh(); } - } + // + UI_BuildServerDisplayList(qfalse); } +/* +================= +UI_UpdatePendingPings +================= +*/ +static void UI_UpdatePendingPings(void) +{ + trap_LAN_ResetPings(ui_netSource.integer); + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; +} -#define GRAPHIC_BWIDTH 8.0f /* -=============== -UI_DrawInfoPane -=============== +================= +UI_StartServerRefresh +================= */ -static void UI_DrawInfoPane( tremInfoPane_t *pane, rectDef_t *rect, float text_x, float text_y, - float scale, vec4_t color, int textStyle ) +static void UI_StartServerRefresh(qboolean full) { - int i; - float maxLeft = 0, maxTop = 0; - float maxRight = 0, maxBottom = 0; - float x = rect->x - text_x, y = rect->y - text_y, w, h; - float xoffset = 0, yoffset = 0; - menuDef_t dummyParent; - itemDef_t textItem; + int time; + qtime_t q; - //iterate through graphics - for( i = 0; i < pane->numGraphics; i++ ) - { - float width = pane->graphics[ i ].width; - float height = pane->graphics[ i ].height; - qhandle_t graphic = pane->graphics[ i ].graphic; + time = trap_RealTime(&q); + trap_Cvar_Set(va("ui_lastServerRefresh_%i_time", ui_netSource.integer), va("%i", time)); + trap_Cvar_Set(va("ui_lastServerRefresh_%i", ui_netSource.integer), + va("%04i-%02i-%02i %02i:%02i:%02i", q.tm_year + 1900, q.tm_mon + 1, q.tm_mday, q.tm_hour, q.tm_min, q.tm_sec)); - if( pane->graphics[ i ].side == INFOPANE_TOP || pane->graphics[ i ].side == INFOPANE_BOTTOM ) - { - //set horizontal offset of graphic - if( pane->graphics[ i ].offset < 0 ) - xoffset = ( rect->w / 2 ) - ( pane->graphics[ i ].width / 2 ); - else - xoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH; - } - else if( pane->graphics[ i ].side == INFOPANE_LEFT || pane->graphics[ i ].side == INFOPANE_RIGHT ) + if (!full) { - //set vertical offset of graphic - if( pane->graphics[ i ].offset < 0 ) - yoffset = ( rect->h / 2 ) - ( pane->graphics[ i ].height / 2 ); - else - yoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH; + UI_UpdatePendingPings(); + return; } - if( pane->graphics[ i ].side == INFOPANE_LEFT ) - { - //set the horizontal offset of the text - if( pane->graphics[ i ].width > maxLeft ) - maxLeft = pane->graphics[ i ].width + GRAPHIC_BWIDTH; + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + uiInfo.serverStatus.numDuplicateMultiprotocolServers = 0; + uiInfo.serverStatus.numDuplicateMultiprotocolServerClients = 0; + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); + // reset all the pings + trap_LAN_ResetPings(ui_netSource.integer); + // - xoffset = GRAPHIC_BWIDTH; - } - else if( pane->graphics[ i ].side == INFOPANE_RIGHT ) + if (ui_netSource.integer == AS_LOCAL) { - if( pane->graphics[ i ].width > maxRight ) - maxRight = pane->graphics[ i ].width + GRAPHIC_BWIDTH; - - xoffset = rect->w - width - GRAPHIC_BWIDTH; + trap_Cmd_ExecuteText(EXEC_APPEND, "localservers\n"); + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + return; } - else if( pane->graphics[ i ].side == INFOPANE_TOP ) - { - //set the vertical offset of the text - if( pane->graphics[ i ].height > maxTop ) - maxTop = pane->graphics[ i ].height + GRAPHIC_BWIDTH; - yoffset = GRAPHIC_BWIDTH; - } - else if( pane->graphics[ i ].side == INFOPANE_BOTTOM ) + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; + + if (ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER) { - if( pane->graphics[ i ].height > maxBottom ) - maxBottom = pane->graphics[ i ].height + GRAPHIC_BWIDTH; + qboolean global = ui_netSource.integer == AS_GLOBAL; - yoffset = rect->h - height - GRAPHIC_BWIDTH; +#ifdef MODULE_INTERFACE_11 + trap_Cmd_ExecuteText(EXEC_APPEND, va("globalservers %d 69 full empty\n", +#else + trap_Cmd_ExecuteText(EXEC_APPEND, va("globalservers %d 70 full empty\n", +#endif + global ? 0 : 1)); } +} - //draw the graphic - UI_DrawHandlePic( x + xoffset, y + yoffset, width, height, graphic ); - } +int frameCount = 0; +int startTime; - //offset the text - x = rect->x + maxLeft; - y = rect->y + maxTop; - w = rect->w - ( maxLeft + maxRight + 16 + ( 2 * text_x ) ); //16 to ensure text within frame - h = rect->h - ( maxTop + maxBottom ); +#define UI_FPS_FRAMES 4 +void UI_Refresh(int realtime) +{ + static int index; + static int previousTimes[UI_FPS_FRAMES]; - textItem.text = pane->text; + // if( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) { + // return; + //} - textItem.parent = &dummyParent; - memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) ); - textItem.window.flags = 0; + uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realtime; - switch( pane->align ) - { - case ITEM_ALIGN_LEFT: - textItem.window.rect.x = x; - break; + previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime; + index++; - case ITEM_ALIGN_RIGHT: - textItem.window.rect.x = x + w; - break; + if (index > UI_FPS_FRAMES) + { + int i, total; + // average multiple frames together to smooth changes out a bit + total = 0; - case ITEM_ALIGN_CENTER: - textItem.window.rect.x = x + ( w / 2 ); - break; + for (i = 0; i < UI_FPS_FRAMES; i++) + total += previousTimes[i]; - default: - textItem.window.rect.x = x; - break; - } + if (!total) + total = 1; - textItem.window.rect.y = y; - textItem.window.rect.w = w; - textItem.window.rect.h = h; - textItem.window.borderSize = 0; - textItem.textRect.x = 0; - textItem.textRect.y = 0; - textItem.textRect.w = 0; - textItem.textRect.h = 0; - textItem.textalignment = pane->align; - textItem.textalignx = text_x; - textItem.textaligny = text_y; - textItem.textscale = scale; - textItem.textStyle = textStyle; + uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total; + } - textItem.enableCvar = NULL; - textItem.cvarTest = NULL; + UI_UpdateCvars(); - //hack to utilise existing autowrap code - Item_Text_AutoWrapped_Paint( &textItem ); -} + if (Menu_Count() > 0) + { + Menu_UpdateAll(); + Menu_PaintAll(); + UI_DoServerRefresh(); + UI_BuildServerStatus(qfalse); + UI_BuildFindPlayerList(qfalse); + UI_UpdateNews(qfalse); + // FIXME: CHECK FOR "AUTOMATICALLLY CHECK FOR UPDATES == true" + // UI_UpdateGithubRelease( ); + } + // draw cursor + UI_SetColor(NULL); -static void UI_DrawSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int i; - i = trap_Cvar_VariableValue( "g_spSkill" ); - if (i < 1 || i > numSkillLevels) { - i = 1; - } - Text_Paint(rect->x, rect->y, scale, color, skillLevels[i-1],0, 0, textStyle); + if (trap_Key_GetCatcher() == KEYCATCH_UI && !trap_Cvar_VariableValue("ui_hideCursor")) + { + UI_DrawHandlePic(uiInfo.uiDC.cursorx - (16.0f * uiInfo.uiDC.aspectScale), uiInfo.uiDC.cursory - 16.0f, + 32.0f * uiInfo.uiDC.aspectScale, 32.0f, uiInfo.uiDC.Assets.cursor); + } } +/* +================= +UI_Shutdown +================= +*/ +void UI_Shutdown(void) { trap_LAN_SaveCachedServers(); } -static void UI_DrawTeamName(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int textStyle) { - int i; - i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam")); - if (i >= 0 && i < uiInfo.teamCount) { - Text_Paint(rect->x, rect->y, scale, color, va("%s: %s", (blue) ? "Blue" : "Red", uiInfo.teamList[i].teamName),0, 0, textStyle); - } -} +qboolean Asset_Parse(int handle) +{ + pc_token_t token; + const char *tempStr; -static void UI_DrawTeamMember(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int num, int textStyle) { - // 0 - None - // 1 - Human - // 2..NumCharacters - Bot - int value = trap_Cvar_VariableValue(va(blue ? "ui_blueteam%i" : "ui_redteam%i", num)); - const char *text; - if (value <= 0) { - text = "Closed"; - } else if (value == 1) { - text = "Human"; - } else { - value -= 2; - - if( value >= UI_GetNumBots( ) ) - value = 0; - - text = UI_GetBotNameByNumber(value); - } - Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle); -} + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; -static void UI_DrawMapPreview(rectDef_t *rect, float scale, vec4_t color, qboolean net) { - int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; - if (map < 0 || map > uiInfo.mapCount) { - if (net) { - ui_currentNetMap.integer = 0; - trap_Cvar_Set("ui_currentNetMap", "0"); - } else { - ui_currentMap.integer = 0; - trap_Cvar_Set("ui_currentMap", "0"); - } - map = 0; - } + if (Q_stricmp(token.string, "{") != 0) + return qfalse; - if (uiInfo.mapList[map].levelShot == -1) { - uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName); - } + while (1) + { + memset(&token, 0, sizeof(pc_token_t)); - if (uiInfo.mapList[map].levelShot > 0) { - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot); - } else { - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen")); - } -} + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + if (Q_stricmp(token.string, "}") == 0) + return qtrue; -static void UI_DrawMapTimeToBeat(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int minutes, seconds, time; - if (ui_currentMap.integer < 0 || ui_currentMap.integer > uiInfo.mapCount) { - ui_currentMap.integer = 0; - trap_Cvar_Set("ui_currentMap", "0"); - } + // font + if (Q_stricmp(token.string, "font") == 0) + { + int pointSize; - time = uiInfo.mapList[ui_currentMap.integer].timeToBeat[uiInfo.gameTypes[ui_gameType.integer].gtEnum]; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) + return qfalse; - minutes = time / 60; - seconds = time % 60; + trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.textFont); + uiInfo.uiDC.Assets.fontRegistered = qtrue; + continue; + } - Text_Paint(rect->x, rect->y, scale, color, va("%02i:%02i", minutes, seconds), 0, 0, textStyle); -} + if (Q_stricmp(token.string, "smallFont") == 0) + { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) + return qfalse; + trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont); + continue; + } -static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboolean net) { + if (Q_stricmp(token.string, "bigFont") == 0) + { + int pointSize; - int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; - if (map < 0 || map > uiInfo.mapCount) { - if (net) { - ui_currentNetMap.integer = 0; - trap_Cvar_Set("ui_currentNetMap", "0"); - } else { - ui_currentMap.integer = 0; - trap_Cvar_Set("ui_currentMap", "0"); - } - map = 0; - } + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) + return qfalse; - if (uiInfo.mapList[map].cinematic >= -1) { - if (uiInfo.mapList[map].cinematic == -1) { - uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); - } - if (uiInfo.mapList[map].cinematic >= 0) { - trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic); - trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic); - } else { - uiInfo.mapList[map].cinematic = -2; - } - } else { - UI_DrawMapPreview(rect, scale, color, net); - } -} + trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont); + continue; + } + // gradientbar + if (Q_stricmp(token.string, "gradientbar") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + return qfalse; + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); + continue; + } -static qboolean updateModel = qtrue; -static qboolean q3Model = qfalse; - -static void UI_DrawPlayerModel(rectDef_t *rect) { - static playerInfo_t info; - char model[MAX_QPATH]; - char team[256]; - char head[256]; - vec3_t viewangles; - vec3_t moveangles; - - if (trap_Cvar_VariableValue("ui_Q3Model")) { - strcpy(model, UI_Cvar_VariableString("model")); - strcpy(head, UI_Cvar_VariableString("headmodel")); - if (!q3Model) { - q3Model = qtrue; - updateModel = qtrue; - } - team[0] = '\0'; - } else { - - strcpy(team, UI_Cvar_VariableString("ui_teamName")); - strcpy(model, UI_Cvar_VariableString("team_model")); - strcpy(head, UI_Cvar_VariableString("team_headmodel")); - if (q3Model) { - q3Model = qfalse; - updateModel = qtrue; - } - } - if (updateModel) { - memset( &info, 0, sizeof(playerInfo_t) ); - viewangles[YAW] = 180 - 10; - viewangles[PITCH] = 0; - viewangles[ROLL] = 0; - VectorClear( moveangles ); - UI_PlayerInfo_SetModel( &info, model, head, team); - UI_PlayerInfo_SetInfo( &info, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse ); -// UI_RegisterClientModelname( &info, model, head, team); - updateModel = qfalse; - } - - UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info, uiInfo.uiDC.realTime / 2); + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + return qfalse; -} + uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound(tempStr, qfalse); + continue; + } -static void UI_DrawNetSource(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - if (ui_netSource.integer < 0 || ui_netSource.integer > numNetSources) { - ui_netSource.integer = 0; - } - Text_Paint(rect->x, rect->y, scale, color, va("Source: %s", netSources[ui_netSource.integer]), 0, 0, textStyle); -} + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + return qfalse; -static void UI_DrawNetMapPreview(rectDef_t *rect, float scale, vec4_t color) { + uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound(tempStr, qfalse); + continue; + } - if (uiInfo.serverStatus.currentServerPreview > 0) { - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview); - } else { - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen")); - } -} + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + return qfalse; -static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) { - if (ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount) { - ui_currentNetMap.integer = 0; - trap_Cvar_Set("ui_currentNetMap", "0"); - } - - if (uiInfo.serverStatus.currentServerCinematic >= 0) { - trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic); - trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic); - } else { - UI_DrawNetMapPreview(rect, scale, color); - } -} + uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound(tempStr, qfalse); + continue; + } + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + return qfalse; + uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound(tempStr, qfalse); + continue; + } -static void UI_DrawNetFilter(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) { - ui_serverFilterType.integer = 0; - } - Text_Paint(rect->x, rect->y, scale, color, va("Filter: %s", serverFilters[ui_serverFilterType.integer].description), 0, 0, textStyle); -} + if (Q_stricmp(token.string, "cursor") == 0) + { + if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr)) + return qfalse; + uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip(uiInfo.uiDC.Assets.cursorStr); + continue; + } -static void UI_DrawTier(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int i; - i = trap_Cvar_VariableValue( "ui_currentTier" ); - if (i < 0 || i >= uiInfo.tierCount) { - i = 0; - } - Text_Paint(rect->x, rect->y, scale, color, va("Tier: %s", uiInfo.tierList[i].tierName),0, 0, textStyle); -} + if (Q_stricmp(token.string, "fadeClamp") == 0) + { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) + return qfalse; -static void UI_DrawTierMap(rectDef_t *rect, int index) { - int i; - i = trap_Cvar_VariableValue( "ui_currentTier" ); - if (i < 0 || i >= uiInfo.tierCount) { - i = 0; - } + continue; + } - if (uiInfo.tierList[i].mapHandles[index] == -1) { - uiInfo.tierList[i].mapHandles[index] = trap_R_RegisterShaderNoMip(va("levelshots/%s", uiInfo.tierList[i].maps[index])); - } + if (Q_stricmp(token.string, "fadeCycle") == 0) + { + if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) + return qfalse; - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.tierList[i].mapHandles[index]); -} + continue; + } -static const char *UI_EnglishMapName(const char *map) { - int i; - for (i = 0; i < uiInfo.mapCount; i++) { - if (Q_stricmp(map, uiInfo.mapList[i].mapLoadName) == 0) { - return uiInfo.mapList[i].mapName; - } - } - return ""; -} + if (Q_stricmp(token.string, "fadeAmount") == 0) + { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) + return qfalse; -static void UI_DrawTierMapName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int i, j; - i = trap_Cvar_VariableValue( "ui_currentTier" ); - if (i < 0 || i >= uiInfo.tierCount) { - i = 0; - } - j = trap_Cvar_VariableValue("ui_currentMap"); - if (j < 0 || j > MAPS_PER_TIER) { - j = 0; - } - - Text_Paint(rect->x, rect->y, scale, color, UI_EnglishMapName(uiInfo.tierList[i].maps[j]), 0, 0, textStyle); -} + continue; + } -static void UI_DrawTierGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int i, j; - i = trap_Cvar_VariableValue( "ui_currentTier" ); - if (i < 0 || i >= uiInfo.tierCount) { - i = 0; - } - j = trap_Cvar_VariableValue("ui_currentMap"); - if (j < 0 || j > MAPS_PER_TIER) { - j = 0; - } - - Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[uiInfo.tierList[i].gameTypes[j]].gameType , 0, 0, textStyle); -} + if (Q_stricmp(token.string, "shadowX") == 0) + { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) + return qfalse; + continue; + } -static const char *UI_AIFromName(const char *name) { - int j; - for (j = 0; j < uiInfo.aliasCount; j++) { - if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) { - return uiInfo.aliasList[j].ai; - } - } - return "James"; -} + if (Q_stricmp(token.string, "shadowY") == 0) + { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) + return qfalse; -static qboolean updateOpponentModel = qtrue; -static void UI_DrawOpponent(rectDef_t *rect) { - static playerInfo_t info2; - char model[MAX_QPATH]; - char headmodel[MAX_QPATH]; - char team[256]; - vec3_t viewangles; - vec3_t moveangles; - - if (updateOpponentModel) { - - strcpy(model, UI_Cvar_VariableString("ui_opponentModel")); - strcpy(headmodel, UI_Cvar_VariableString("ui_opponentModel")); - team[0] = '\0'; - - memset( &info2, 0, sizeof(playerInfo_t) ); - viewangles[YAW] = 180 - 10; - viewangles[PITCH] = 0; - viewangles[ROLL] = 0; - VectorClear( moveangles ); - UI_PlayerInfo_SetModel( &info2, model, headmodel, ""); - UI_PlayerInfo_SetInfo( &info2, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse ); - UI_RegisterClientModelname( &info2, model, headmodel, team); - updateOpponentModel = qfalse; - } - - UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info2, uiInfo.uiDC.realTime / 2); + continue; + } -} + if (Q_stricmp(token.string, "shadowColor") == 0) + { + if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) + return qfalse; -static void UI_NextOpponent( void ) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); - int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - i++; - if (i >= uiInfo.teamCount) { - i = 0; - } - if (i == j) { - i++; - if ( i >= uiInfo.teamCount) { - i = 0; - } - } - trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); -} + uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; + continue; + } + } -static void UI_PriorOpponent( void ) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); - int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - i--; - if (i < 0) { - i = uiInfo.teamCount - 1; - } - if (i == j) { - i--; - if ( i < 0) { - i = uiInfo.teamCount - 1; - } - } - trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); + return qfalse; } -static void UI_DrawPlayerLogo(rectDef_t *rect, vec3_t color) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +void UI_Report(void) { String_Report(); } - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); - } +void UI_ParseMenu(const char *menuFile) +{ + int handle; + pc_token_t token; - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); - trap_R_SetColor( NULL ); -} + handle = trap_Parse_LoadSource(menuFile); -static void UI_DrawPlayerLogoMetal(rectDef_t *rect, vec3_t color) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); - } - - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); - trap_R_SetColor( NULL ); -} + if (!handle) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Menu file %s not found\n", menuFile); + return; + } -static void UI_DrawPlayerLogoName(rectDef_t *rect, vec3_t color) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); - } - - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); - trap_R_SetColor( NULL ); -} + while (1) + { + memset(&token, 0, sizeof(pc_token_t)); -static void UI_DrawOpponentLogo(rectDef_t *rect, vec3_t color) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); - } - - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); - trap_R_SetColor( NULL ); -} + if (!trap_Parse_ReadToken(handle, &token)) + break; -static void UI_DrawOpponentLogoMetal(rectDef_t *rect, vec3_t color) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); - } - - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); - trap_R_SetColor( NULL ); -} + // if( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} -static void UI_DrawOpponentLogoName(rectDef_t *rect, vec3_t color) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); - if (uiInfo.teamList[i].teamIcon == -1) { - uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); - uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); - uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); - } - - trap_R_SetColor( color ); - UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); - trap_R_SetColor( NULL ); -} + // if( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} -static void UI_DrawAllMapsSelection(rectDef_t *rect, float scale, vec4_t color, int textStyle, qboolean net) { - int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; - if (map >= 0 && map < uiInfo.mapCount) { - Text_Paint(rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle); - } -} + if (token.string[0] == '}') + break; -static void UI_DrawPlayerListSelection( rectDef_t *rect, float scale, - vec4_t color, int textStyle ) -{ - if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) - { - Text_Paint(rect->x, rect->y, scale, color, - uiInfo.rawPlayerNames[ uiInfo.playerIndex ], - 0, 0, textStyle); - } -} + if (Q_stricmp(token.string, "assetGlobalDef") == 0) + { + if (Asset_Parse(handle)) + continue; + else + break; + } -static void UI_DrawTeamListSelection( rectDef_t *rect, float scale, - vec4_t color, int textStyle ) -{ - if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) - { - Text_Paint(rect->x, rect->y, scale, color, - uiInfo.rawTeamNames[ uiInfo.teamIndex ], - 0, 0, textStyle); - } -} + if (Q_stricmp(token.string, "menudef") == 0) + { + // start a new menu + Menu_New(handle); + } + } -static void UI_DrawOpponentName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_opponentName"), 0, 0, textStyle); + trap_Parse_FreeSource(handle); } +qboolean Load_Menu(int handle) +{ + pc_token_t token; -static int UI_OwnerDrawWidth(int ownerDraw, float scale) { - int i, h, value; - const char *text; - const char *s = NULL; - - switch( ownerDraw ) - { - case UI_HANDICAP: - h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); - i = 20 - h / 5; - s = handicapValues[i]; - break; - case UI_CLANNAME: - s = UI_Cvar_VariableString("ui_teamName"); - break; - case UI_GAMETYPE: - s = uiInfo.gameTypes[ui_gameType.integer].gameType; - break; - case UI_SKILL: - i = trap_Cvar_VariableValue( "g_spSkill" ); - if (i < 1 || i > numSkillLevels) { - i = 1; - } - s = skillLevels[i-1]; - break; - case UI_BLUETEAMNAME: - i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_blueTeam")); - if (i >= 0 && i < uiInfo.teamCount) { - s = va("%s: %s", "Blue", uiInfo.teamList[i].teamName); - } - break; - case UI_REDTEAMNAME: - i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_redTeam")); - if (i >= 0 && i < uiInfo.teamCount) { - s = va("%s: %s", "Red", uiInfo.teamList[i].teamName); - } - break; - case UI_BLUETEAM1: - case UI_BLUETEAM2: - case UI_BLUETEAM3: - case UI_BLUETEAM4: - case UI_BLUETEAM5: - value = trap_Cvar_VariableValue(va("ui_blueteam%i", ownerDraw-UI_BLUETEAM1 + 1)); - if (value <= 0) { - text = "Closed"; - } else if (value == 1) { - text = "Human"; - } else { - value -= 2; - if (value >= uiInfo.aliasCount) { - value = 0; - } - text = uiInfo.aliasList[value].name; - } - s = va("%i. %s", ownerDraw-UI_BLUETEAM1 + 1, text); - break; - case UI_REDTEAM1: - case UI_REDTEAM2: - case UI_REDTEAM3: - case UI_REDTEAM4: - case UI_REDTEAM5: - value = trap_Cvar_VariableValue(va("ui_redteam%i", ownerDraw-UI_REDTEAM1 + 1)); - if (value <= 0) { - text = "Closed"; - } else if (value == 1) { - text = "Human"; - } else { - value -= 2; - if (value >= uiInfo.aliasCount) { - value = 0; - } - text = uiInfo.aliasList[value].name; - } - s = va("%i. %s", ownerDraw-UI_REDTEAM1 + 1, text); - break; - case UI_NETSOURCE: - if (ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numJoinGameTypes) { - ui_netSource.integer = 0; - } - s = va("Source: %s", netSources[ui_netSource.integer]); - break; - case UI_NETFILTER: - if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) { - ui_serverFilterType.integer = 0; - } - s = va("Filter: %s", serverFilters[ui_serverFilterType.integer].description ); - break; - case UI_TIER: - break; - case UI_TIER_MAPNAME: - break; - case UI_TIER_GAMETYPE: - break; - case UI_ALLMAPS_SELECTION: - break; - case UI_PLAYERLIST_SELECTION: - break; - case UI_TEAMLIST_SELECTION: - break; - case UI_OPPONENT_NAME: - break; - case UI_KEYBINDSTATUS: - if (Display_KeyBindPending()) { - s = "Waiting for new key... Press ESCAPE to cancel"; - } else { - s = "Press ENTER or CLICK to change, Press BACKSPACE to clear"; - } - break; - case UI_SERVERREFRESHDATE: - s = UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)); - break; - default: - break; - } - - if (s) { - return Text_Width(s, scale, 0); - } - return 0; -} + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; -static void UI_DrawBotName(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - int value = uiInfo.botIndex; - const char *text = ""; + if (token.string[0] != '{') + return qfalse; - if( value >= UI_GetNumBots( ) ) - value = 0; + while (1) + { + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; - text = UI_GetBotNameByNumber( value ); + if (token.string[0] == 0) + return qfalse; - Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle); -} + if (token.string[0] == '}') + return qtrue; -static void UI_DrawBotSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - if (uiInfo.skillIndex >= 0 && uiInfo.skillIndex < numSkillLevels) { - Text_Paint(rect->x, rect->y, scale, color, skillLevels[uiInfo.skillIndex], 0, 0, textStyle); - } -} + UI_ParseMenu(token.string); + } -static void UI_DrawRedBlue(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - Text_Paint(rect->x, rect->y, scale, color, (uiInfo.redBlue == 0) ? "Red" : "Blue", 0, 0, textStyle); + return qfalse; } -/* -=============== -UI_BuildPlayerList -=============== -*/ -static void UI_BuildPlayerList( void ) { - uiClientState_t cs; - int n, count, team, team2, playerTeamNumber; - char info[MAX_INFO_STRING]; - - trap_GetClientState( &cs ); - trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); - uiInfo.playerNumber = cs.clientNum; - uiInfo.teamLeader = atoi(Info_ValueForKey(info, "tl")); - team = atoi(Info_ValueForKey(info, "t")); - trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); - count = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); - uiInfo.playerCount = 0; - uiInfo.myTeamCount = 0; - uiInfo.myPlayerIndex = 0; - playerTeamNumber = 0; - for( n = 0; n < count; n++ ) { - trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING ); - - if (info[0]) { - BG_ClientListParse( &uiInfo.ignoreList[ uiInfo.playerCount ], - Info_ValueForKey( info, "ig" ) ); - Q_strncpyz( uiInfo.rawPlayerNames[uiInfo.playerCount], - Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); - Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount], - Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); - Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] ); - uiInfo.clientNums[uiInfo.playerCount] = n; - if( n == uiInfo.playerNumber ) - uiInfo.myPlayerIndex = uiInfo.playerCount; - uiInfo.playerCount++; - team2 = atoi(Info_ValueForKey(info, "t")); - if (team2 == team) { - Q_strncpyz( uiInfo.rawTeamNames[uiInfo.myTeamCount], - Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); - Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount], - Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); - Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] ); - uiInfo.teamClientNums[uiInfo.myTeamCount] = n; - if (uiInfo.playerNumber == n) { - playerTeamNumber = uiInfo.myTeamCount; - } - uiInfo.myTeamCount++; - } - } - } - - if (!uiInfo.teamLeader) { - trap_Cvar_Set("cg_selectedPlayer", va("%d", playerTeamNumber)); - } +void UI_LoadMenus(const char *menuFile, qboolean reset) +{ + pc_token_t token; + int handle; + int start; - n = trap_Cvar_VariableValue("cg_selectedPlayer"); - if (n < 0 || n > uiInfo.myTeamCount) { - n = 0; - } - if (n < uiInfo.myTeamCount) { - trap_Cvar_Set("cg_selectedPlayerName", uiInfo.teamNames[n]); - } -} + start = trap_Milliseconds(); + handle = trap_Parse_LoadSource(menuFile); -static void UI_DrawSelectedPlayer(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - char name[ MAX_NAME_LENGTH ]; - char *s; - - if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { - uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; - UI_BuildPlayerList(); - } - if( uiInfo.teamLeader ) - s = UI_Cvar_VariableString("cg_selectedPlayerName"); - else - s = UI_Cvar_VariableString("name"); - Q_strncpyz( name, s, sizeof( name ) ); - Text_Paint(rect->x, rect->y, scale, color, name, 0, 0, textStyle); -} + if (!handle) + trap_Error(va(S_COLOR_RED "menu list '%s' not found, unable to continue!", menuFile)); -static void UI_DrawServerRefreshDate(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - if (uiInfo.serverStatus.refreshActive) { - vec4_t lowLight, newColor; - lowLight[0] = 0.8 * color[0]; - lowLight[1] = 0.8 * color[1]; - lowLight[2] = 0.8 * color[2]; - lowLight[3] = 0.8 * color[3]; - LerpColor(color,lowLight,newColor,0.5+0.5*sin(uiInfo.uiDC.realTime / PULSE_DIVISOR)); - Text_Paint(rect->x, rect->y, scale, newColor, va("Getting info for %d servers (ESC to cancel)", trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0, textStyle); - } else { - char buff[64]; - Q_strncpyz(buff, UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)), 64); - Text_Paint(rect->x, rect->y, scale, color, va("Refresh Time: %s", buff), 0, 0, textStyle); - } -} + if (reset) + Menu_Reset(); -static void UI_DrawServerMOTD(rectDef_t *rect, float scale, vec4_t color) { - if (uiInfo.serverStatus.motdLen) { - float maxX; + while (1) + { + if (!trap_Parse_ReadToken(handle, &token)) + break; - if (uiInfo.serverStatus.motdWidth == -1) { - uiInfo.serverStatus.motdWidth = 0; - uiInfo.serverStatus.motdPaintX = rect->x + 1; - uiInfo.serverStatus.motdPaintX2 = -1; - } + if (token.string[0] == 0 || token.string[0] == '}') + break; - if (uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen) { - uiInfo.serverStatus.motdOffset = 0; - uiInfo.serverStatus.motdPaintX = rect->x + 1; - uiInfo.serverStatus.motdPaintX2 = -1; - } + if (token.string[0] == '}') + break; - if (uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime) { - uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10; - if (uiInfo.serverStatus.motdPaintX <= rect->x + 2) { - if (uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen) { - uiInfo.serverStatus.motdPaintX += Text_Width(&uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], scale, 1) - 1; - uiInfo.serverStatus.motdOffset++; - } else { - uiInfo.serverStatus.motdOffset = 0; - if (uiInfo.serverStatus.motdPaintX2 >= 0) { - uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2; - } else { - uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2; - } - uiInfo.serverStatus.motdPaintX2 = -1; - } - } else { - //serverStatus.motdPaintX--; - uiInfo.serverStatus.motdPaintX -= 2; - if (uiInfo.serverStatus.motdPaintX2 >= 0) { - //serverStatus.motdPaintX2--; - uiInfo.serverStatus.motdPaintX2 -= 2; + if (Q_stricmp(token.string, "loadmenu") == 0) + { + if (Load_Menu(handle)) + continue; + else + break; } - } } - maxX = rect->x + rect->w - 2; - Text_Paint_Limit(&maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0); - if (uiInfo.serverStatus.motdPaintX2 >= 0) { - float maxX2 = rect->x + rect->w - 2; - Text_Paint_Limit(&maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset); - } - if (uiInfo.serverStatus.motdOffset && maxX > 0) { - // if we have an offset ( we are skipping the first part of the string ) and we fit the string - if (uiInfo.serverStatus.motdPaintX2 == -1) { - uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2; - } - } else { - uiInfo.serverStatus.motdPaintX2 = -1; - } + Com_Printf("UI menu file '%s' loaded in %d msec\n", menuFile, trap_Milliseconds() - start); - } + trap_Parse_FreeSource(handle); } -static void UI_DrawKeyBindStatus(rectDef_t *rect, float scale, vec4_t color, int textStyle) { -// int ofs = 0; TTimo: unused - if (Display_KeyBindPending()) { - Text_Paint(rect->x, rect->y, scale, color, "Waiting for new key... Press ESCAPE to cancel", 0, 0, textStyle); - } else { - Text_Paint(rect->x, rect->y, scale, color, "Press ENTER or CLICK to change, Press BACKSPACE to clear", 0, 0, textStyle); - } -} - -static void UI_DrawGLInfo(rectDef_t *rect, float scale, vec4_t color, int textStyle) { - char * eptr; - char buff[1024]; - const char *lines[64]; - int y, numLines, i; +void UI_LoadHelp(const char *helpFile) +{ + pc_token_t token; + int handle, start; + char title[32], buffer[1024]; - Text_Paint(rect->x + 2, rect->y, scale, color, va("VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string), 0, 30, textStyle); - Text_Paint(rect->x + 2, rect->y + 15, scale, color, va("VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), 0, 30, textStyle); - Text_Paint(rect->x + 2, rect->y + 30, scale, color, va ("PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), 0, 30, textStyle); + start = trap_Milliseconds(); - // build null terminated extension strings - // TTimo: https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=399 - // in TA this was not directly crashing, but displaying a nasty broken shader right in the middle - // brought down the string size to 1024, there's not much that can be shown on the screen anyway - Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, 1024); - eptr = buff; - y = rect->y + 45; - numLines = 0; - while ( y < rect->y + rect->h && *eptr ) - { - while ( *eptr && *eptr == ' ' ) - *eptr++ = '\0'; + handle = trap_Parse_LoadSource(helpFile); + if (!handle) + { + Com_Printf(S_COLOR_YELLOW "WARNING: help file '%s' not found!\n", helpFile); + return; + } - // track start of valid string - if (*eptr && *eptr != ' ') { - lines[numLines++] = eptr; + if (!trap_Parse_ReadToken(handle, &token) || token.string[0] == 0 || token.string[0] != '{') + { + Com_Printf(S_COLOR_YELLOW + "WARNING: help file '%s' does not start with " + "'{'\n", + helpFile); + return; } - while ( *eptr && *eptr != ' ' ) - eptr++; - } + uiInfo.helpCount = 0; + title[0] = 0; + while (1) + { + if (!trap_Parse_ReadToken(handle, &token) || token.string[0] == 0 || token.string[0] == '}') + break; - i = 0; - while (i < numLines) { - Text_Paint(rect->x + 2, y, scale, color, lines[i++], 0, 20, textStyle); - if (i < numLines) { - Text_Paint(rect->x + rect->w / 2, y, scale, color, lines[i++], 0, 20, textStyle); - } - y += 10; - if (y > rect->y + rect->h - 11) { - break; + if (token.string[0] == '{') + { + buffer[0] = 0; + Q_strcat(buffer, sizeof(buffer), title); + Q_strcat(buffer, sizeof(buffer), "\n\n"); + while (trap_Parse_ReadToken(handle, &token) && token.string[0] != 0 && token.string[0] != '}') + { + Q_strcat(buffer, sizeof(buffer), token.string); + } + + uiInfo.helpList[uiInfo.helpCount].text = String_Alloc(title); + uiInfo.helpList[uiInfo.helpCount].v.text = String_Alloc(buffer); + uiInfo.helpList[uiInfo.helpCount].type = INFOTYPE_TEXT; + uiInfo.helpCount++; + title[0] = 0; + } + else + Q_strcat(title, sizeof(title), token.string); } - } + trap_Parse_FreeSource(handle); + Com_Printf("UI help file '%s' loaded in %d msec (%d infopanes)\n", helpFile, trap_Milliseconds() - start, + uiInfo.helpCount); } -// FIXME: table drive -// -static void UI_OwnerDraw( float x, float y, float w, float h, - float text_x, float text_y, int ownerDraw, - int ownerDrawFlags, int align, float special, - float scale, vec4_t color, qhandle_t shader, int textStyle ) +void UI_Load(void) { - rectDef_t rect; - tremInfoPane_t *pane = NULL; - - rect.x = x + text_x; - rect.y = y + text_y; - rect.w = w; - rect.h = h; - - switch( ownerDraw ) - { - case UI_TEAMINFOPANE: - if( ( pane = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_ACLASSINFOPANE: - if( ( pane = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_AUPGRADEINFOPANE: - if( ( pane = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_HITEMINFOPANE: - if( ( pane = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_HBUYINFOPANE: - if( ( pane = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_HSELLINFOPANE: - if( ( pane = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_ABUILDINFOPANE: - if( ( pane = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_HBUILDINFOPANE: - if( ( pane = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].infopane ) ) - UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle ); - break; - - case UI_HANDICAP: - UI_DrawHandicap(&rect, scale, color, textStyle); - break; - case UI_PLAYERMODEL: - UI_DrawPlayerModel(&rect); - break; - case UI_CLANNAME: - UI_DrawClanName(&rect, scale, color, textStyle); - break; - case UI_CLANLOGO: - UI_DrawClanLogo(&rect, scale, color); - break; - case UI_CLANCINEMATIC: - UI_DrawClanCinematic(&rect, scale, color); - break; - case UI_PREVIEWCINEMATIC: - UI_DrawPreviewCinematic(&rect, scale, color); - break; - case UI_GAMETYPE: - UI_DrawGameType(&rect, scale, color, textStyle); - break; - case UI_NETGAMETYPE: - UI_DrawNetGameType(&rect, scale, color, textStyle); - break; - case UI_JOINGAMETYPE: - UI_DrawJoinGameType(&rect, scale, color, textStyle); - break; - case UI_MAPPREVIEW: - UI_DrawMapPreview(&rect, scale, color, qtrue); - break; - case UI_MAP_TIMETOBEAT: - UI_DrawMapTimeToBeat(&rect, scale, color, textStyle); - break; - case UI_MAPCINEMATIC: - UI_DrawMapCinematic(&rect, scale, color, qfalse); - break; - case UI_STARTMAPCINEMATIC: - UI_DrawMapCinematic(&rect, scale, color, qtrue); - break; - case UI_SKILL: - UI_DrawSkill(&rect, scale, color, textStyle); - break; - case UI_BLUETEAMNAME: - UI_DrawTeamName(&rect, scale, color, qtrue, textStyle); - break; - case UI_REDTEAMNAME: - UI_DrawTeamName(&rect, scale, color, qfalse, textStyle); - break; - case UI_BLUETEAM1: - case UI_BLUETEAM2: - case UI_BLUETEAM3: - case UI_BLUETEAM4: - case UI_BLUETEAM5: - UI_DrawTeamMember(&rect, scale, color, qtrue, ownerDraw - UI_BLUETEAM1 + 1, textStyle); - break; - case UI_REDTEAM1: - case UI_REDTEAM2: - case UI_REDTEAM3: - case UI_REDTEAM4: - case UI_REDTEAM5: - UI_DrawTeamMember(&rect, scale, color, qfalse, ownerDraw - UI_REDTEAM1 + 1, textStyle); - break; - case UI_NETSOURCE: - UI_DrawNetSource(&rect, scale, color, textStyle); - break; - case UI_NETMAPPREVIEW: - UI_DrawNetMapPreview(&rect, scale, color); - break; - case UI_NETMAPCINEMATIC: - UI_DrawNetMapCinematic(&rect, scale, color); - break; - case UI_NETFILTER: - UI_DrawNetFilter(&rect, scale, color, textStyle); - break; - case UI_TIER: - UI_DrawTier(&rect, scale, color, textStyle); - break; - case UI_OPPONENTMODEL: - UI_DrawOpponent(&rect); - break; - case UI_TIERMAP1: - UI_DrawTierMap(&rect, 0); - break; - case UI_TIERMAP2: - UI_DrawTierMap(&rect, 1); - break; - case UI_TIERMAP3: - UI_DrawTierMap(&rect, 2); - break; - case UI_PLAYERLOGO: - UI_DrawPlayerLogo(&rect, color); - break; - case UI_PLAYERLOGO_METAL: - UI_DrawPlayerLogoMetal(&rect, color); - break; - case UI_PLAYERLOGO_NAME: - UI_DrawPlayerLogoName(&rect, color); - break; - case UI_OPPONENTLOGO: - UI_DrawOpponentLogo(&rect, color); - break; - case UI_OPPONENTLOGO_METAL: - UI_DrawOpponentLogoMetal(&rect, color); - break; - case UI_OPPONENTLOGO_NAME: - UI_DrawOpponentLogoName(&rect, color); - break; - case UI_TIER_MAPNAME: - UI_DrawTierMapName(&rect, scale, color, textStyle); - break; - case UI_TIER_GAMETYPE: - UI_DrawTierGameType(&rect, scale, color, textStyle); - break; - case UI_ALLMAPS_SELECTION: - UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qtrue); - break; - case UI_MAPS_SELECTION: - UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qfalse); - break; - case UI_PLAYERLIST_SELECTION: - UI_DrawPlayerListSelection(&rect, scale, color, textStyle); - break; - case UI_TEAMLIST_SELECTION: - UI_DrawTeamListSelection(&rect, scale, color, textStyle); - break; - case UI_OPPONENT_NAME: - UI_DrawOpponentName(&rect, scale, color, textStyle); - break; - case UI_BOTNAME: - UI_DrawBotName(&rect, scale, color, textStyle); - break; - case UI_BOTSKILL: - UI_DrawBotSkill(&rect, scale, color, textStyle); - break; - case UI_REDBLUE: - UI_DrawRedBlue(&rect, scale, color, textStyle); - break; - case UI_SELECTEDPLAYER: - UI_DrawSelectedPlayer(&rect, scale, color, textStyle); - break; - case UI_SERVERREFRESHDATE: - UI_DrawServerRefreshDate(&rect, scale, color, textStyle); - break; - case UI_SERVERMOTD: - UI_DrawServerMOTD(&rect, scale, color); - break; - case UI_GLINFO: - UI_DrawGLInfo(&rect,scale, color, textStyle); - break; - case UI_KEYBINDSTATUS: - UI_DrawKeyBindStatus(&rect,scale, color, textStyle); - break; - default: - break; - } + char lastName[1024]; + menuDef_t *menu = Menu_GetFocused(); + + if (menu && menu->window.name) + strcpy(lastName, menu->window.name); + String_Init(); + + UI_LoadMenus("ui/menus.txt", qtrue); + UI_LoadMenus("ui/ingame.txt", qfalse); + UI_LoadMenus("ui/tremulous.txt", qfalse); + UI_LoadHelp("ui/help.txt"); + Menus_CloseAll(); + Menus_ActivateByName(lastName); } -static qboolean UI_OwnerDrawVisible(int flags) { - qboolean vis = qtrue; - uiClientState_t cs; - pTeam_t team; - char info[ MAX_INFO_STRING ]; +/* +=============== +UI_GetCurrentAlienStage +=============== +*/ +static stage_t UI_GetCurrentAlienStage(void) +{ + char buffer[MAX_TOKEN_CHARS]; + stage_t stage, dummy; - trap_GetClientState( &cs ); - trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); - team = atoi( Info_ValueForKey( info, "t" ) ); + trap_Cvar_VariableStringBuffer("ui_stages", buffer, sizeof(buffer)); + sscanf(buffer, "%d %d", (int *)&stage, (int *)&dummy); + return stage; +} - while (flags) { +/* +=============== +UI_GetCurrentHumanStage +=============== +*/ +static stage_t UI_GetCurrentHumanStage(void) +{ + char buffer[MAX_TOKEN_CHARS]; + stage_t stage, dummy; - if( flags & UI_SHOW_NOTSPECTATING ) - { - if( team == PTE_NONE ) - vis = qfalse; + trap_Cvar_VariableStringBuffer("ui_stages", buffer, sizeof(buffer)); + sscanf(buffer, "%d %d", (int *)&dummy, (int *)&stage); - flags &= ~UI_SHOW_NOTSPECTATING; - } + return stage; +} - if( flags & UI_SHOW_VOTEACTIVE ) - { - if( !trap_Cvar_VariableValue( "ui_voteActive" ) ) - vis = qfalse; +/* +=============== +UI_DrawInfoPane +=============== +*/ +static void UI_DrawInfoPane(menuItem_t *item, rectDef_t *rect, float text_x, float text_y, float scale, int textalign, + int textvalign, vec4_t color, int textStyle) +{ + int value = 0; + const char *s = ""; + char *string = ""; - flags &= ~UI_SHOW_VOTEACTIVE; - } + int class, credits; + char ui_currentClass[MAX_STRING_CHARS]; - if( flags & UI_SHOW_CANVOTE ) - { - if( trap_Cvar_VariableValue( "ui_voteActive" ) ) - vis = qfalse; + trap_Cvar_VariableStringBuffer("ui_currentClass", ui_currentClass, MAX_STRING_CHARS); - flags &= ~UI_SHOW_CANVOTE; - } + sscanf(ui_currentClass, "%d %d", &class, &credits); - if( flags & UI_SHOW_TEAMVOTEACTIVE ) + switch (item->type) { - if( team == PTE_ALIENS ) - { - if( !trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) ) - vis = qfalse; - } - else if( team == PTE_HUMANS ) - { - if( !trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) ) - vis = qfalse; - } + case INFOTYPE_TEXT: + case INFOTYPE_VOICECMD: + s = item->v.text; + break; - flags &= ~UI_SHOW_TEAMVOTEACTIVE; - } + case INFOTYPE_CLASS: + value = (BG_ClassCanEvolveFromTo(class, item->v.pclass, credits, UI_GetCurrentAlienStage(), 0) + + ALIEN_CREDITS_PER_KILL - 1) / + ALIEN_CREDITS_PER_KILL; - if( flags & UI_SHOW_CANTEAMVOTE ) - { - if( team == PTE_ALIENS ) - { - if( trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) ) - vis = qfalse; - } - else if( team == PTE_HUMANS ) - { - if( trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) ) - vis = qfalse; - } + if (value < 1) + { + s = va("%s\n\n%s", BG_ClassConfig(item->v.pclass)->humanName, BG_Class(item->v.pclass)->info); + } + else + { + s = va("%s\n\n%s\n\nFrags: %d", BG_ClassConfig(item->v.pclass)->humanName, + BG_Class(item->v.pclass)->info, value); + } - flags &= ~UI_SHOW_CANTEAMVOTE; - } + break; - if (flags & UI_SHOW_LEADER) { - // these need to show when this client can give orders to a player or a group - if (!uiInfo.teamLeader) { - vis = qfalse; - } else { - // if showing yourself - if (ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber) { - vis = qfalse; - } - } - flags &= ~UI_SHOW_LEADER; - } - if (flags & UI_SHOW_NOTLEADER) { - // these need to show when this client is assigning their own status or they are NOT the leader - if (uiInfo.teamLeader) { - // if not showing yourself - if (!(ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber)) { - vis = qfalse; - } - // these need to show when this client can give orders to a player or a group - } - flags &= ~UI_SHOW_NOTLEADER; - } - if (flags & UI_SHOW_FAVORITESERVERS) { - // this assumes you only put this type of display flag on something showing in the proper context - if (ui_netSource.integer != AS_FAVORITES) { - vis = qfalse; - } - flags &= ~UI_SHOW_FAVORITESERVERS; - } - if (flags & UI_SHOW_NOTFAVORITESERVERS) { - // this assumes you only put this type of display flag on something showing in the proper context - if (ui_netSource.integer == AS_FAVORITES) { - vis = qfalse; - } - flags &= ~UI_SHOW_NOTFAVORITESERVERS; - } - if (flags & UI_SHOW_NEWHIGHSCORE) { - if (uiInfo.newHighScoreTime < uiInfo.uiDC.realTime) { - vis = qfalse; - } else { - if (uiInfo.soundHighScore) { - if (trap_Cvar_VariableValue("sv_killserver") == 0) { - // wait on server to go down before playing sound - trap_S_StartLocalSound(uiInfo.newHighScoreSound, CHAN_ANNOUNCER); - uiInfo.soundHighScore = qfalse; - } - } - } - flags &= ~UI_SHOW_NEWHIGHSCORE; - } - if (flags & UI_SHOW_NEWBESTTIME) { - if (uiInfo.newBestTime < uiInfo.uiDC.realTime) { - vis = qfalse; - } - flags &= ~UI_SHOW_NEWBESTTIME; - } - if (flags & UI_SHOW_DEMOAVAILABLE) { - if (!uiInfo.demoAvailable) { - vis = qfalse; - } - flags &= ~UI_SHOW_DEMOAVAILABLE; - } else { - flags = 0; - } - } - return vis; -} + case INFOTYPE_WEAPON: + value = BG_Weapon(item->v.weapon)->price; -static qboolean UI_Handicap_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int h; - h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); - if (key == K_MOUSE2) { - h -= 5; - } else { - h += 5; - } - if (h > 100) { - h = 5; - } else if (h < 0) { - h = 100; - } - trap_Cvar_Set( "handicap", va( "%i", h) ); - return qtrue; - } - return qfalse; -} + if (value == 0) + { + s = va( + "%s\n\n%s\n\nCredits: Free", BG_Weapon(item->v.weapon)->humanName, BG_Weapon(item->v.weapon)->info); + } + else + { + s = va("%s\n\n%s\n\nCredits: %d", BG_Weapon(item->v.weapon)->humanName, BG_Weapon(item->v.weapon)->info, + value); + } -static qboolean UI_ClanName_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int i; - i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - if (uiInfo.teamList[i].cinematic >= 0) { - trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic); - uiInfo.teamList[i].cinematic = -1; - } - if (key == K_MOUSE2) { - i--; - } else { - i++; - } - if (i >= uiInfo.teamCount) { - i = 0; - } else if (i < 0) { - i = uiInfo.teamCount - 1; - } - trap_Cvar_Set( "ui_teamName", uiInfo.teamList[i].teamName); - UI_HeadCountByTeam(); - UI_FeederSelection(FEEDER_HEADS, 0); - updateModel = qtrue; - return qtrue; - } - return qfalse; -} + break; -static qboolean UI_GameType_HandleKey(int flags, float *special, int key, qboolean resetMap) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int oldCount = UI_MapCountByGameType(qtrue); - - // hard coded mess here - if (key == K_MOUSE2) { - ui_gameType.integer--; - if (ui_gameType.integer == 2) { - ui_gameType.integer = 1; - } else if (ui_gameType.integer < 2) { - ui_gameType.integer = uiInfo.numGameTypes - 1; - } - } else { - ui_gameType.integer++; - if (ui_gameType.integer >= uiInfo.numGameTypes) { - ui_gameType.integer = 1; - } else if (ui_gameType.integer == 2) { - ui_gameType.integer = 3; - } - } + case INFOTYPE_UPGRADE: + value = BG_Upgrade(item->v.upgrade)->price; - trap_Cvar_Set("ui_Q3Model", "0"); + if (value == 0) + { + s = va("%s\n\n%s\n\nCredits: Free", BG_Upgrade(item->v.upgrade)->humanName, + BG_Upgrade(item->v.upgrade)->info); + } + else + { + s = va("%s\n\n%s\n\nCredits: %d", BG_Upgrade(item->v.upgrade)->humanName, + BG_Upgrade(item->v.upgrade)->info, value); + } - trap_Cvar_Set("ui_gameType", va("%d", ui_gameType.integer)); - UI_SetCapFragLimits(qtrue); - UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); - if (resetMap && oldCount != UI_MapCountByGameType(qtrue)) { - trap_Cvar_Set( "ui_currentMap", "0"); - Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, NULL); - } - return qtrue; - } - return qfalse; -} + break; -static qboolean UI_NetGameType_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + case INFOTYPE_BUILDABLE: + value = BG_Buildable(item->v.buildable)->buildPoints; - if (key == K_MOUSE2) { - ui_netGameType.integer--; - } else { - ui_netGameType.integer++; - } + switch (BG_Buildable(item->v.buildable)->team) + { + case TEAM_ALIENS: + string = "Sentience"; + break; - if (ui_netGameType.integer < 0) { - ui_netGameType.integer = uiInfo.numGameTypes - 1; - } else if (ui_netGameType.integer >= uiInfo.numGameTypes) { - ui_netGameType.integer = 0; - } + case TEAM_HUMANS: + string = "Power"; + break; - trap_Cvar_Set( "ui_netGameType", va("%d", ui_netGameType.integer)); - trap_Cvar_Set( "ui_actualnetGameType", va("%d", uiInfo.gameTypes[ui_netGameType.integer].gtEnum)); - trap_Cvar_Set( "ui_currentNetMap", "0"); - UI_MapCountByGameType(qfalse); - Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, NULL); - return qtrue; - } - return qfalse; -} + default: + break; + } -static qboolean UI_JoinGameType_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + if (value == 0) + { + s = va("%s\n\n%s", BG_Buildable(item->v.buildable)->humanName, BG_Buildable(item->v.buildable)->info); + } + else + { + s = va("%s\n\n%s\n\n%s: %d", BG_Buildable(item->v.buildable)->humanName, + BG_Buildable(item->v.buildable)->info, string, value); + } - if (key == K_MOUSE2) { - ui_joinGameType.integer--; - } else { - ui_joinGameType.integer++; + break; } - if (ui_joinGameType.integer < 0) { - ui_joinGameType.integer = uiInfo.numJoinGameTypes - 1; - } else if (ui_joinGameType.integer >= uiInfo.numJoinGameTypes) { - ui_joinGameType.integer = 0; - } + UI_DrawTextBlock(rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, s); +} - trap_Cvar_Set( "ui_joinGameType", va("%d", ui_joinGameType.integer)); - UI_BuildServerDisplayList(qtrue); - return qtrue; - } - return qfalse; +static void UI_DrawServerMapPreview(rectDef_t *rect, float scale, vec4_t color) +{ + if (uiInfo.serverStatus.currentServerCinematic >= 0) + { + trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic); + trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h); + trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic); + } + else if (uiInfo.serverStatus.currentServerPreview > 0) + UI_DrawHandlePic(rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview); + else + UI_DrawHandlePic(rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen")); } +static void UI_DrawSelectedMapPreview(rectDef_t *rect, float scale, vec4_t color) +{ + int map = ui_selectedMap.integer; + if (map < 0 || map > uiInfo.mapCount) + { + ui_selectedMap.integer = 0; + trap_Cvar_Set("ui_selectedMap", "0"); + map = 0; + } -static qboolean UI_Skill_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int i = trap_Cvar_VariableValue( "g_spSkill" ); + if (uiInfo.mapList[map].cinematic >= -1) + { + if (uiInfo.mapList[map].cinematic == -1) + uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic( + va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent)); - if (key == K_MOUSE2) { - i--; - } else { - i++; + if (uiInfo.mapList[map].cinematic >= 0) + { + trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic); + trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h); + trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic); + } + else + uiInfo.mapList[map].cinematic = -2; } + else + { + if (uiInfo.mapList[map].levelShot == -1) + uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName); - if (i < 1) { - i = numSkillLevels; - } else if (i > numSkillLevels) { - i = 1; + if (uiInfo.mapList[map].levelShot > 0) + UI_DrawHandlePic(rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot); + else + UI_DrawHandlePic(rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen")); } +} - trap_Cvar_Set("g_spSkill", va("%i", i)); - return qtrue; - } - return qfalse; +static void UI_DrawSelectedMapName(rectDef_t *rect, float scale, vec4_t color, int textStyle) +{ + int map = ui_selectedMap.integer; + + if (map >= 0 && map < uiInfo.mapCount) + UI_Text_Paint(rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle); } -static qboolean UI_TeamName_HandleKey(int flags, float *special, int key, qboolean blue) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int i; - i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam")); +static const char *UI_OwnerDrawText(int ownerDraw) +{ + const char *s = NULL; - if (key == K_MOUSE2) { - i--; - } else { - i++; - } + switch (ownerDraw) + { + case UI_NETSOURCE: + if (ui_netSource.integer < 0 || ui_netSource.integer >= numNetSources) + ui_netSource.integer = 0; - if (i >= uiInfo.teamCount) { - i = 0; - } else if (i < 0) { - i = uiInfo.teamCount - 1; - } + s = netSources[ui_netSource.integer]; + break; - trap_Cvar_Set( (blue) ? "ui_blueTeam" : "ui_redTeam", uiInfo.teamList[i].teamName); + case UI_KEYBINDSTATUS: + if (Display_KeyBindPending()) + s = "Waiting for new key... Press ESCAPE to cancel"; + else + s = "Press ENTER or CLICK to change, Press BACKSPACE to clear"; - return qtrue; - } - return qfalse; -} + break; -static qboolean UI_TeamMember_HandleKey(int flags, float *special, int key, qboolean blue, int num) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - // 0 - None - // 1 - Human - // 2..NumCharacters - Bot - char *cvar = va(blue ? "ui_blueteam%i" : "ui_redteam%i", num); - int value = trap_Cvar_VariableValue(cvar); + case UI_SERVERREFRESHDATE: + if (uiInfo.serverStatus.refreshActive) + { +#define MAX_DOTS 5 + int numServers = trap_LAN_GetServerCount(ui_netSource.integer); + int numDots = (uiInfo.uiDC.realTime / 500) % (MAX_DOTS + 1); + char dots[MAX_DOTS + 1]; + int i; - if (key == K_MOUSE2) { - value--; - } else { - value++; - } + for (i = 0; i < numDots; i++) + dots[i] = '.'; - if( value >= UI_GetNumBots( ) + 2 ) - value = 0; - else if( value < 0 ) - value = UI_GetNumBots( ) + 2 - 1; + dots[i] = '\0'; - trap_Cvar_Set(cvar, va("%i", value)); - return qtrue; - } - return qfalse; -} + s = numServers < 0 ? va("Waiting for response%s", dots) + : va("Getting info for %d servers (ESC to cancel)%s", numServers, dots); + } + else + s = va("Refresh Time: %s", UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer))); -static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { + break; - if (key == K_MOUSE2) { - ui_netSource.integer--; - if (ui_netSource.integer == AS_MPLAYER) - ui_netSource.integer--; - } else { - ui_netSource.integer++; - if (ui_netSource.integer == AS_MPLAYER) - ui_netSource.integer++; - } + case UI_SERVERMOTD: + s = uiInfo.serverStatus.motd; + break; - if (ui_netSource.integer >= numNetSources) { - ui_netSource.integer = 0; - } else if (ui_netSource.integer < 0) { - ui_netSource.integer = numNetSources - 1; + default: + break; } - UI_BuildServerDisplayList(qtrue); - if (ui_netSource.integer != AS_GLOBAL) { - UI_StartServerRefresh(qtrue); - } - trap_Cvar_Set( "ui_netSource", va("%d", ui_netSource.integer)); - return qtrue; - } - return qfalse; + return s; } -static qboolean UI_NetFilter_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { +static int UI_OwnerDrawWidth(int ownerDraw, float scale) +{ + const char *s = NULL; - if (key == K_MOUSE2) { - ui_serverFilterType.integer--; - } else { - ui_serverFilterType.integer++; - } + switch (ownerDraw) + { + case UI_NETSOURCE: + case UI_KEYBINDSTATUS: + case UI_SERVERREFRESHDATE: + case UI_SERVERMOTD: + s = UI_OwnerDrawText(ownerDraw); + break; - if (ui_serverFilterType.integer >= numServerFilters) { - ui_serverFilterType.integer = 0; - } else if (ui_serverFilterType.integer < 0) { - ui_serverFilterType.integer = numServerFilters - 1; + default: + break; } - UI_BuildServerDisplayList(qtrue); - return qtrue; - } - return qfalse; -} -static qboolean UI_OpponentName_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - if (key == K_MOUSE2) { - UI_PriorOpponent(); - } else { - UI_NextOpponent(); - } - return qtrue; - } - return qfalse; + if (s) + return UI_Text_Width(s, scale); + + return 0; } -static qboolean UI_BotName_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int value = uiInfo.botIndex; +/* +=============== +UI_BuildPlayerList +=============== +*/ +static void UI_BuildPlayerList(void) +{ + uiClientState_t cs; + int n, count, team, team2; + char info[MAX_INFO_STRING]; + + trap_GetClientState(&cs); + trap_GetConfigString(CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING); + uiInfo.playerNumber = cs.clientNum; + team = atoi(Info_ValueForKey(info, "t")); + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + count = atoi(Info_ValueForKey(info, "sv_maxclients")); + uiInfo.playerCount = 0; + uiInfo.myTeamCount = 0; + uiInfo.myPlayerIndex = 0; + + for (n = 0; n < count; n++) + { + trap_GetConfigString(CS_PLAYERS + n, info, MAX_INFO_STRING); - if (key == K_MOUSE2) { - value--; - } else { - value++; - } + if (info[0]) + { + Com_ClientListParse(&uiInfo.ignoreList[uiInfo.playerCount], Info_ValueForKey(info, "ig")); + Q_strncpyz(uiInfo.rawPlayerNames[uiInfo.playerCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); + Q_strncpyz(uiInfo.playerNames[uiInfo.playerCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); + Q_CleanStr(uiInfo.playerNames[uiInfo.playerCount]); + uiInfo.clientNums[uiInfo.playerCount] = n; + if (n == uiInfo.playerNumber) + uiInfo.myPlayerIndex = uiInfo.playerCount; - if( value >= UI_GetNumBots( ) + 2 ) - value = 0; - else if( value < 0 ) - value = UI_GetNumBots( ) + 2 - 1; + uiInfo.playerCount++; - uiInfo.botIndex = value; - return qtrue; - } - return qfalse; -} + team2 = atoi(Info_ValueForKey(info, "t")); -static qboolean UI_BotSkill_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - if (key == K_MOUSE2) { - uiInfo.skillIndex--; - } else { - uiInfo.skillIndex++; - } - if (uiInfo.skillIndex >= numSkillLevels) { - uiInfo.skillIndex = 0; - } else if (uiInfo.skillIndex < 0) { - uiInfo.skillIndex = numSkillLevels-1; + if (team2 == team) + { + Q_strncpyz(uiInfo.rawTeamNames[uiInfo.myTeamCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); + Q_strncpyz(uiInfo.teamNames[uiInfo.myTeamCount], Info_ValueForKey(info, "n"), MAX_NAME_LENGTH); + Q_CleanStr(uiInfo.teamNames[uiInfo.myTeamCount]); + uiInfo.teamClientNums[uiInfo.myTeamCount] = n; + + uiInfo.myTeamCount++; + } + } } - return qtrue; - } - return qfalse; } -static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - uiInfo.redBlue ^= 1; - return qtrue; - } - return qfalse; +static void UI_DrawGLInfo(rectDef_t *rect, float scale, int textalign, int textvalign, vec4_t color, int textStyle, + float text_x, float text_y) +{ + char buffer[4096]; + + Com_sprintf(buffer, sizeof(buffer), + "VENDOR: %s\nVERSION: %s\n" + "PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)\n%s", + uiInfo.uiDC.glconfig.vendor_string, uiInfo.uiDC.glconfig.renderer_string, uiInfo.uiDC.glconfig.colorBits, + uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits, uiInfo.uiDC.glconfig.extensions_string); + + UI_DrawTextBlock(rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, buffer); } +// FIXME: table drive +// +static void UI_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, + int ownerDrawFlags, int align, int textalign, int textvalign, float borderSize, float scale, vec4_t foreColor, + vec4_t backColor, qhandle_t shader, int textStyle) +{ + rectDef_t rect; + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; -static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - int selected; + switch (ownerDraw) + { + case UI_TEAMINFOPANE: + UI_DrawInfoPane(&uiInfo.teamList[uiInfo.teamIndex], &rect, text_x, text_y, scale, textalign, textvalign, + foreColor, textStyle); + break; + + case UI_VOICECMDINFOPANE: + UI_DrawInfoPane(&uiInfo.voiceCmdList[uiInfo.voiceCmdIndex], &rect, text_x, text_y, scale, textalign, + textvalign, foreColor, textStyle); + break; + + case UI_ACLASSINFOPANE: + UI_DrawInfoPane(&uiInfo.alienClassList[uiInfo.alienClassIndex], &rect, text_x, text_y, scale, textalign, + textvalign, foreColor, textStyle); + break; + + case UI_AUPGRADEINFOPANE: + UI_DrawInfoPane(&uiInfo.alienUpgradeList[uiInfo.alienUpgradeIndex], &rect, text_x, text_y, scale, textalign, + textvalign, foreColor, textStyle); + break; + + case UI_HITEMINFOPANE: + UI_DrawInfoPane(&uiInfo.humanItemList[uiInfo.humanItemIndex], &rect, text_x, text_y, scale, textalign, + textvalign, foreColor, textStyle); + break; + + case UI_HBUYINFOPANE: + UI_DrawInfoPane(&uiInfo.humanArmouryBuyList[uiInfo.humanArmouryBuyIndex], &rect, text_x, text_y, scale, + textalign, textvalign, foreColor, textStyle); + break; + + case UI_HSELLINFOPANE: + UI_DrawInfoPane(&uiInfo.humanArmourySellList[uiInfo.humanArmourySellIndex], &rect, text_x, text_y, scale, + textalign, textvalign, foreColor, textStyle); + break; + + case UI_ABUILDINFOPANE: + UI_DrawInfoPane(&uiInfo.alienBuildList[uiInfo.alienBuildIndex], &rect, text_x, text_y, scale, textalign, + textvalign, foreColor, textStyle); + break; + + case UI_HBUILDINFOPANE: + UI_DrawInfoPane(&uiInfo.humanBuildList[uiInfo.humanBuildIndex], &rect, text_x, text_y, scale, textalign, + textvalign, foreColor, textStyle); + break; + + case UI_HELPINFOPANE: + UI_DrawInfoPane(&uiInfo.helpList[uiInfo.helpIndex], &rect, text_x, text_y, scale, textalign, textvalign, + foreColor, textStyle); + break; + + case UI_NETMAPPREVIEW: + UI_DrawServerMapPreview(&rect, scale, foreColor); + break; + + case UI_SELECTEDMAPPREVIEW: + UI_DrawSelectedMapPreview(&rect, scale, foreColor); + break; + + case UI_SELECTEDMAPNAME: + UI_DrawSelectedMapName(&rect, scale, foreColor, textStyle); + break; + + case UI_GLINFO: + UI_DrawGLInfo(&rect, scale, textalign, textvalign, foreColor, textStyle, text_x, text_y); + break; - UI_BuildPlayerList(); - if (!uiInfo.teamLeader) { - return qfalse; + default: + break; } - selected = trap_Cvar_VariableValue("cg_selectedPlayer"); +} - if (key == K_MOUSE2) { - selected--; - } else { - selected++; - } +static qboolean UI_OwnerDrawVisible(int flags) +{ + qboolean vis = qtrue; + uiClientState_t cs; + team_t team; + char info[MAX_INFO_STRING]; - if (selected > uiInfo.myTeamCount) { - selected = 0; - } else if (selected < 0) { - selected = uiInfo.myTeamCount; - } + trap_GetClientState(&cs); + trap_GetConfigString(CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING); + team = atoi(Info_ValueForKey(info, "t")); - if (selected == uiInfo.myTeamCount) { - trap_Cvar_Set( "cg_selectedPlayerName", "Everyone"); - } else { - trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected]); + while (flags) + { + if (flags & UI_SHOW_NOTSPECTATING) + { + if (team == TEAM_NONE) + vis = qfalse; + + flags &= ~UI_SHOW_NOTSPECTATING; + } + + if (flags & UI_SHOW_VOTEACTIVE) + { + if (!trap_Cvar_VariableValue("ui_voteActive")) + vis = qfalse; + + flags &= ~UI_SHOW_VOTEACTIVE; + } + + if (flags & UI_SHOW_CANVOTE) + { + if (trap_Cvar_VariableValue("ui_voteActive")) + vis = qfalse; + + flags &= ~UI_SHOW_CANVOTE; + } + + if (flags & UI_SHOW_TEAMVOTEACTIVE) + { + if (team == TEAM_ALIENS) + { + if (!trap_Cvar_VariableValue("ui_alienTeamVoteActive")) + vis = qfalse; + } + else if (team == TEAM_HUMANS) + { + if (!trap_Cvar_VariableValue("ui_humanTeamVoteActive")) + vis = qfalse; + } + + flags &= ~UI_SHOW_TEAMVOTEACTIVE; + } + + if (flags & UI_SHOW_CANTEAMVOTE) + { + if (team == TEAM_ALIENS) + { + if (trap_Cvar_VariableValue("ui_alienTeamVoteActive")) + vis = qfalse; + } + else if (team == TEAM_HUMANS) + { + if (trap_Cvar_VariableValue("ui_humanTeamVoteActive")) + vis = qfalse; + } + + flags &= ~UI_SHOW_CANTEAMVOTE; + } + + if (flags & UI_SHOW_FAVORITESERVERS) + { + // this assumes you only put this type of display flag on something showing in the proper context + + if (ui_netSource.integer != AS_FAVORITES) + vis = qfalse; + + flags &= ~UI_SHOW_FAVORITESERVERS; + } + + if (flags & UI_SHOW_NOTFAVORITESERVERS) + { + // this assumes you only put this type of display flag on something showing in the proper context + + if (ui_netSource.integer == AS_FAVORITES) + vis = qfalse; + + flags &= ~UI_SHOW_NOTFAVORITESERVERS; + } + else + flags = 0; } - trap_Cvar_Set( "cg_selectedPlayer", va("%d", selected)); - } - return qfalse; + + return vis; } +static qboolean UI_NetSource_HandleKey(int key) +{ + if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) + { + if (key == K_MOUSE2) + { + ui_netSource.integer--; + + if (ui_netSource.integer == AS_MPLAYER) + ui_netSource.integer--; + } + else + { + ui_netSource.integer++; + + if (ui_netSource.integer == AS_MPLAYER) + ui_netSource.integer++; + } + + if (ui_netSource.integer < 0) + ui_netSource.integer = numNetSources - 1; + else if (ui_netSource.integer >= numNetSources) + ui_netSource.integer = 0; + + UI_BuildServerDisplayList(qtrue); -static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { - switch (ownerDraw) { - case UI_HANDICAP: - return UI_Handicap_HandleKey(flags, special, key); - break; - case UI_CLANNAME: - return UI_ClanName_HandleKey(flags, special, key); - break; - case UI_GAMETYPE: - return UI_GameType_HandleKey(flags, special, key, qtrue); - break; - case UI_NETGAMETYPE: - return UI_NetGameType_HandleKey(flags, special, key); - break; - case UI_JOINGAMETYPE: - return UI_JoinGameType_HandleKey(flags, special, key); - break; - case UI_SKILL: - return UI_Skill_HandleKey(flags, special, key); - break; - case UI_BLUETEAMNAME: - return UI_TeamName_HandleKey(flags, special, key, qtrue); - break; - case UI_REDTEAMNAME: - return UI_TeamName_HandleKey(flags, special, key, qfalse); - break; - case UI_BLUETEAM1: - case UI_BLUETEAM2: - case UI_BLUETEAM3: - case UI_BLUETEAM4: - case UI_BLUETEAM5: - UI_TeamMember_HandleKey(flags, special, key, qtrue, ownerDraw - UI_BLUETEAM1 + 1); - break; - case UI_REDTEAM1: - case UI_REDTEAM2: - case UI_REDTEAM3: - case UI_REDTEAM4: - case UI_REDTEAM5: - UI_TeamMember_HandleKey(flags, special, key, qfalse, ownerDraw - UI_REDTEAM1 + 1); - break; - case UI_NETSOURCE: - UI_NetSource_HandleKey(flags, special, key); - break; - case UI_NETFILTER: - UI_NetFilter_HandleKey(flags, special, key); - break; - case UI_OPPONENT_NAME: - UI_OpponentName_HandleKey(flags, special, key); - break; - case UI_BOTNAME: - return UI_BotName_HandleKey(flags, special, key); - break; - case UI_BOTSKILL: - return UI_BotSkill_HandleKey(flags, special, key); - break; - case UI_REDBLUE: - UI_RedBlue_HandleKey(flags, special, key); - break; - case UI_SELECTEDPLAYER: - UI_SelectedPlayer_HandleKey(flags, special, key); - break; - default: - break; - } - - return qfalse; + if (ui_netSource.integer != AS_GLOBAL) + UI_StartServerRefresh(qtrue); + + trap_Cvar_Set("ui_netSource", va("%d", ui_netSource.integer)); + return qtrue; + } + + return qfalse; } +static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int key) +{ + switch (ownerDraw) + { + case UI_NETSOURCE: + UI_NetSource_HandleKey(key); + break; + + default: + break; + } -static float UI_GetValue(int ownerDraw) { - return 0; + return qfalse; } /* @@ -3108,446 +2321,462 @@ static float UI_GetValue(int ownerDraw) { UI_ServersQsortCompare ================= */ -static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) { - return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2); +static int QDECL UI_ServersQsortCompare(const void *arg1, const void *arg2) +{ + return trap_LAN_CompareServers( + ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int *)arg1, *(int *)arg2); } - /* ================= UI_ServersSort ================= */ -void UI_ServersSort(int column, qboolean force) { - - if ( !force ) { - if ( uiInfo.serverStatus.sortKey == column ) { - return; +void UI_ServersSort(int column, qboolean force) +{ + if (!force) + { + if (uiInfo.serverStatus.sortKey == column) + return; } - } - uiInfo.serverStatus.sortKey = column; - qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare); + uiInfo.serverStatus.sortKey = column; + qsort(&uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), + UI_ServersQsortCompare); } - /* =============== -UI_GetCurrentAlienStage +UI_LoadTeams =============== */ -static stage_t UI_GetCurrentAlienStage( void ) +static void UI_LoadTeams(void) { - char buffer[ MAX_TOKEN_CHARS ]; - stage_t stage, dummy; - - trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) ); - sscanf( buffer, "%d %d", (int *)&stage , (int *)&dummy ); - - return stage; + uiInfo.teamCount = 4; + + uiInfo.teamList[0].text = "Aliens"; + uiInfo.teamList[0].cmd = "cmd team aliens\n"; + uiInfo.teamList[0].type = INFOTYPE_TEXT; + uiInfo.teamList[0].v.text = + "The Alien Team\n\n" + "The Aliens' strengths are in movement and the ability to " + "quickly construct new bases quickly. They possess a range " + "of abilities including basic melee attacks, movement-" + "crippling poisons and more."; + + uiInfo.teamList[1].text = "Humans"; + uiInfo.teamList[1].cmd = "cmd team humans\n"; + uiInfo.teamList[1].type = INFOTYPE_TEXT; + uiInfo.teamList[1].v.text = + "The Human Team\n\n" + "The humans are the masters of technology. Although their " + "bases take long to construct, their automated defense " + "ensures they stay built. A wide range of upgrades and " + "weapons are available to the humans, each contributing " + "to eradicate the alien threat."; + + uiInfo.teamList[2].text = "Spectate"; + uiInfo.teamList[2].cmd = "cmd team spectate\n"; + uiInfo.teamList[2].type = INFOTYPE_TEXT; + uiInfo.teamList[2].v.text = "Watch the game without playing."; + + uiInfo.teamList[3].text = "Auto select"; + uiInfo.teamList[3].cmd = "cmd team auto\n"; + uiInfo.teamList[3].type = INFOTYPE_TEXT; + uiInfo.teamList[3].v.text = "Join the team with the least players."; } /* =============== -UI_GetCurrentHumanStage +UI_AddClass =============== */ -static stage_t UI_GetCurrentHumanStage( void ) + +static void UI_AddClass(class_t class) { - char buffer[ MAX_TOKEN_CHARS ]; - stage_t stage, dummy; + uiInfo.alienClassList[uiInfo.alienClassCount].text = BG_ClassConfig(class)->humanName; + uiInfo.alienClassList[uiInfo.alienClassCount].cmd = String_Alloc(va("cmd class %s\n", BG_Class(class)->name)); + uiInfo.alienClassList[uiInfo.alienClassCount].type = INFOTYPE_CLASS; - trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) ); - sscanf( buffer, "%d %d", (int *)&dummy, (int *)&stage ); + uiInfo.alienClassList[uiInfo.alienClassCount].v.pclass = class; - return stage; + uiInfo.alienClassCount++; } /* =============== -UI_LoadTremTeams +UI_LoadAlienClasses =============== */ -static void UI_LoadTremTeams( void ) +static void UI_LoadAlienClasses(void) { - uiInfo.tremTeamCount = 4; - - uiInfo.tremTeamList[ 0 ].text = String_Alloc( "Aliens" ); - uiInfo.tremTeamList[ 0 ].cmd = String_Alloc( "cmd team aliens\n" ); - uiInfo.tremTeamList[ 0 ].infopane = UI_FindInfoPaneByName( "alienteam" ); - - uiInfo.tremTeamList[ 1 ].text = String_Alloc( "Humans" ); - uiInfo.tremTeamList[ 1 ].cmd = String_Alloc( "cmd team humans\n" ); - uiInfo.tremTeamList[ 1 ].infopane = UI_FindInfoPaneByName( "humanteam" ); + uiInfo.alienClassCount = 0; - uiInfo.tremTeamList[ 2 ].text = String_Alloc( "Spectate" ); - uiInfo.tremTeamList[ 2 ].cmd = String_Alloc( "cmd team spectate\n" ); - uiInfo.tremTeamList[ 2 ].infopane = UI_FindInfoPaneByName( "spectateteam" ); + if (BG_ClassIsAllowed(PCL_ALIEN_LEVEL0)) + UI_AddClass(PCL_ALIEN_LEVEL0); - uiInfo.tremTeamList[ 3 ].text = String_Alloc( "Auto select" ); - uiInfo.tremTeamList[ 3 ].cmd = String_Alloc( "cmd team auto\n" ); - uiInfo.tremTeamList[ 3 ].infopane = UI_FindInfoPaneByName( "autoteam" ); + if (BG_ClassIsAllowed(PCL_ALIEN_BUILDER0_UPG) && + BG_ClassAllowedInStage(PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage())) + UI_AddClass(PCL_ALIEN_BUILDER0_UPG); + else if (BG_ClassIsAllowed(PCL_ALIEN_BUILDER0)) + UI_AddClass(PCL_ALIEN_BUILDER0); } /* =============== -UI_AddClass +UI_AddItem =============== */ -static void UI_AddClass( pClass_t class ) +static void UI_AddItem(weapon_t weapon) { - uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].text = - String_Alloc( BG_FindHumanNameForClassNum( class ) ); - uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].cmd = - String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( class ) ) ); - uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].infopane = - UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( class ) ) ); - - uiInfo.tremAlienClassCount++; + uiInfo.humanItemList[uiInfo.humanItemCount].text = BG_Weapon(weapon)->humanName; + uiInfo.humanItemList[uiInfo.humanItemCount].cmd = String_Alloc(va("cmd class %s\n", BG_Weapon(weapon)->name)); + uiInfo.humanItemList[uiInfo.humanItemCount].type = INFOTYPE_WEAPON; + uiInfo.humanItemList[uiInfo.humanItemCount].v.weapon = weapon; + + uiInfo.humanItemCount++; } /* =============== -UI_LoadTremAlienClasses +UI_LoadHumanItems =============== */ -static void UI_LoadTremAlienClasses( void ) +static void UI_LoadHumanItems(void) { - uiInfo.tremAlienClassCount = 0; + uiInfo.humanItemCount = 0; - if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) ) - UI_AddClass( PCL_ALIEN_LEVEL0 ); + if (BG_WeaponIsAllowed(WP_MACHINEGUN)) + UI_AddItem(WP_MACHINEGUN); - if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) && - BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage( ) ) ) - UI_AddClass( PCL_ALIEN_BUILDER0_UPG ); - else if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) ) - UI_AddClass( PCL_ALIEN_BUILDER0 ); + if (BG_WeaponIsAllowed(WP_HBUILD)) + UI_AddItem(WP_HBUILD); } /* =============== -UI_AddItem +UI_ParseCarriageList =============== */ -static void UI_AddItem( weapon_t weapon ) +static void UI_ParseCarriageList(void) { - uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].text = - String_Alloc( BG_FindHumanNameForWeapon( weapon ) ); - uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].cmd = - String_Alloc( va( "cmd class %s\n", BG_FindNameForWeapon( weapon ) ) ); - uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].infopane = - UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( weapon ) ) ); - - uiInfo.tremHumanItemCount++; -} + int i; + char carriageCvar[MAX_TOKEN_CHARS]; + char *iterator; + char buffer[MAX_TOKEN_CHARS]; + char *bufPointer; -/* -=============== -UI_LoadTremHumanItems -=============== -*/ -static void UI_LoadTremHumanItems( void ) -{ - uiInfo.tremHumanItemCount = 0; + trap_Cvar_VariableStringBuffer("ui_carriage", carriageCvar, sizeof(carriageCvar)); + iterator = carriageCvar; - if( BG_WeaponIsAllowed( WP_MACHINEGUN ) ) - UI_AddItem( WP_MACHINEGUN ); + uiInfo.weapons = 0; + uiInfo.upgrades = 0; - if( BG_WeaponIsAllowed( WP_HBUILD2 ) && - BG_FindStagesForWeapon( WP_HBUILD2, UI_GetCurrentHumanStage( ) ) ) - UI_AddItem( WP_HBUILD2 ); - else if( BG_WeaponIsAllowed( WP_HBUILD ) ) - UI_AddItem( WP_HBUILD ); + // simple parser to give rise to weapon/upgrade list + + while (iterator && iterator[0] != '$') + { + bufPointer = buffer; + + if (iterator[0] == 'W') + { + iterator++; + + while (iterator[0] != ' ') + *bufPointer++ = *iterator++; + + *bufPointer++ = '\n'; + + i = atoi(buffer); + + uiInfo.weapons |= (1 << i); + } + else if (iterator[0] == 'U') + { + iterator++; + + while (iterator[0] != ' ') + *bufPointer++ = *iterator++; + + *bufPointer++ = '\n'; + + i = atoi(buffer); + + uiInfo.upgrades |= (1 << i); + } + + iterator++; + } } /* =============== -UI_ParseCarriageList +UI_LoadHumanArmouryBuys =============== */ -static void UI_ParseCarriageList( int *weapons, int *upgrades ) +static void UI_LoadHumanArmouryBuys(void) { - int i; - char carriageCvar[ MAX_TOKEN_CHARS ]; - char *iterator; - char buffer[ MAX_TOKEN_CHARS ]; - char *bufPointer; + int i, j = 0; + stage_t stage = UI_GetCurrentHumanStage(); + int slots = 0; - trap_Cvar_VariableStringBuffer( "ui_carriage", carriageCvar, sizeof( carriageCvar ) ); - iterator = carriageCvar; + UI_ParseCarriageList(); - if( weapons ) - *weapons = 0; + for (i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++) + { + if (uiInfo.weapons & (1 << i)) + slots |= BG_Weapon(i)->slots; + } - if( upgrades ) - *upgrades = 0; + for (i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++) + { + if (uiInfo.upgrades & (1 << i)) + slots |= BG_Upgrade(i)->slots; + } - //simple parser to give rise to weapon/upgrade list - while( iterator && iterator[ 0 ] != '$' ) - { - bufPointer = buffer; + uiInfo.humanArmouryBuyCount = 0; - if( iterator[ 0 ] == 'W' ) + for (i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++) { - iterator++; + if (BG_Weapon(i)->team == TEAM_HUMANS && BG_Weapon(i)->purchasable && BG_WeaponAllowedInStage(i, stage) && + BG_WeaponIsAllowed(i) && !(BG_Weapon(i)->slots & slots) && !(uiInfo.weapons & (1 << i))) + { + uiInfo.humanArmouryBuyList[j].text = BG_Weapon(i)->humanName; + uiInfo.humanArmouryBuyList[j].cmd = String_Alloc(va("cmd buy %s\n", BG_Weapon(i)->name)); + uiInfo.humanArmouryBuyList[j].type = INFOTYPE_WEAPON; + uiInfo.humanArmouryBuyList[j].v.weapon = i; + + j++; - while( iterator[ 0 ] != ' ' ) - *bufPointer++ = *iterator++; + uiInfo.humanArmouryBuyCount++; + } + } - *bufPointer++ = '\n'; + for (i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++) + { + if (BG_Upgrade(i)->team == TEAM_HUMANS && BG_Upgrade(i)->purchasable && BG_UpgradeAllowedInStage(i, stage) && + BG_UpgradeIsAllowed(i) && !(BG_Upgrade(i)->slots & slots) && !(uiInfo.upgrades & (1 << i))) + { + uiInfo.humanArmouryBuyList[j].text = BG_Upgrade(i)->humanName; + uiInfo.humanArmouryBuyList[j].cmd = String_Alloc(va("cmd buy %s\n", BG_Upgrade(i)->name)); + uiInfo.humanArmouryBuyList[j].type = INFOTYPE_UPGRADE; + uiInfo.humanArmouryBuyList[j].v.upgrade = i; - i = atoi( buffer ); + j++; - if( weapons ) - *weapons |= ( 1 << i ); + uiInfo.humanArmouryBuyCount++; + } } - else if( iterator[ 0 ] == 'U' ) - { - iterator++; +} + +/* +=============== +UI_LoadHumanArmourySells +=============== +*/ +static void UI_LoadHumanArmourySells(void) +{ + int i, j = 0; - while( iterator[ 0 ] != ' ' ) - *bufPointer++ = *iterator++; + uiInfo.humanArmourySellCount = 0; + UI_ParseCarriageList(); - *bufPointer++ = '\n'; + for (i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++) + { + if (uiInfo.weapons & (1 << i)) + { + uiInfo.humanArmourySellList[j].text = BG_Weapon(i)->humanName; + uiInfo.humanArmourySellList[j].cmd = String_Alloc(va("cmd sell %s\n", BG_Weapon(i)->name)); + uiInfo.humanArmourySellList[j].type = INFOTYPE_WEAPON; + uiInfo.humanArmourySellList[j].v.weapon = i; - i = atoi( buffer ); + j++; - if( upgrades ) - *upgrades |= ( 1 << i ); + uiInfo.humanArmourySellCount++; + } } - iterator++; - } + for (i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++) + { + if (uiInfo.upgrades & (1 << i)) + { + uiInfo.humanArmourySellList[j].text = BG_Upgrade(i)->humanName; + uiInfo.humanArmourySellList[j].cmd = String_Alloc(va("cmd sell %s\n", BG_Upgrade(i)->name)); + uiInfo.humanArmourySellList[j].type = INFOTYPE_UPGRADE; + uiInfo.humanArmourySellList[j].v.upgrade = i; + + j++; + + uiInfo.humanArmourySellCount++; + } + } } /* =============== -UI_LoadTremHumanArmouryBuys +UI_ArmouryRefreshCb =============== */ -static void UI_LoadTremHumanArmouryBuys( void ) +static void UI_ArmouryRefreshCb(void *data) { - int i, j = 0; - stage_t stage = UI_GetCurrentHumanStage( ); - int weapons, upgrades; - int slots = 0; - - UI_ParseCarriageList( &weapons, &upgrades ); - - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( weapons & ( 1 << i ) ) - slots |= BG_FindSlotsForWeapon( i ); - } - - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( upgrades & ( 1 << i ) ) - slots |= BG_FindSlotsForUpgrade( i ); - } - - uiInfo.tremHumanArmouryBuyCount = 0; - - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( BG_FindTeamForWeapon( i ) == WUT_HUMANS && - BG_FindPurchasableForWeapon( i ) && - BG_FindStagesForWeapon( i, stage ) && - BG_WeaponIsAllowed( i ) && - !( BG_FindSlotsForWeapon( i ) & slots ) && - !( weapons & ( 1 << i ) ) ) - { - uiInfo.tremHumanArmouryBuyList[ j ].text = - String_Alloc( BG_FindHumanNameForWeapon( i ) ); - uiInfo.tremHumanArmouryBuyList[ j ].cmd = - String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForWeapon( i ) ) ); - uiInfo.tremHumanArmouryBuyList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) ); - - j++; - - uiInfo.tremHumanArmouryBuyCount++; - } - } - - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_FindTeamForUpgrade( i ) == WUT_HUMANS && - BG_FindPurchasableForUpgrade( i ) && - BG_FindStagesForUpgrade( i, stage ) && - BG_UpgradeIsAllowed( i ) && - !( BG_FindSlotsForUpgrade( i ) & slots ) && - !( upgrades & ( 1 << i ) ) ) - { - uiInfo.tremHumanArmouryBuyList[ j ].text = - String_Alloc( BG_FindHumanNameForUpgrade( i ) ); - uiInfo.tremHumanArmouryBuyList[ j ].cmd = - String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForUpgrade( i ) ) ); - uiInfo.tremHumanArmouryBuyList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) ); - - j++; - - uiInfo.tremHumanArmouryBuyCount++; - } - } + int oldWeapons = uiInfo.weapons; + int oldUpgrades = uiInfo.upgrades; + + UI_ParseCarriageList(); + + if (uiInfo.weapons != oldWeapons || uiInfo.upgrades != oldUpgrades) + { + UI_LoadHumanArmouryBuys(); + UI_LoadHumanArmourySells(); + UI_RemoveCaptureFunc(); + } } /* =============== -UI_LoadTremHumanArmourySells +UI_LoadAlienUpgrades =============== */ -static void UI_LoadTremHumanArmourySells( void ) +static void UI_LoadAlienUpgrades(void) { - int weapons, upgrades; - int i, j = 0; + int i, j = 0; - uiInfo.tremHumanArmourySellCount = 0; - UI_ParseCarriageList( &weapons, &upgrades ); + int class, credits; + char ui_currentClass[MAX_STRING_CHARS]; + stage_t stage = UI_GetCurrentAlienStage(); - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - if( weapons & ( 1 << i ) ) - { - uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForWeapon( i ) ); - uiInfo.tremHumanArmourySellList[ j ].cmd = - String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForWeapon( i ) ) ); - uiInfo.tremHumanArmourySellList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) ); + trap_Cvar_VariableStringBuffer("ui_currentClass", ui_currentClass, MAX_STRING_CHARS); - j++; + sscanf(ui_currentClass, "%d %d", &class, &credits); - uiInfo.tremHumanArmourySellCount++; - } - } + uiInfo.alienUpgradeCount = 0; - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( upgrades & ( 1 << i ) ) + for (i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++) { - uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForUpgrade( i ) ); - uiInfo.tremHumanArmourySellList[ j ].cmd = - String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForUpgrade( i ) ) ); - uiInfo.tremHumanArmourySellList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) ); + if (BG_ClassCanEvolveFromTo(class, i, credits, stage, 0) >= 0) + { + uiInfo.alienUpgradeList[j].text = BG_ClassConfig(i)->humanName; + uiInfo.alienUpgradeList[j].cmd = String_Alloc(va("cmd class %s\n", BG_Class(i)->name)); + uiInfo.alienUpgradeList[j].type = INFOTYPE_CLASS; + uiInfo.alienUpgradeList[j].v.pclass = i; - j++; + j++; - uiInfo.tremHumanArmourySellCount++; + uiInfo.alienUpgradeCount++; + } } - } } /* =============== -UI_LoadTremAlienUpgrades +UI_LoadAlienBuilds =============== */ -static void UI_LoadTremAlienUpgrades( void ) +static void UI_LoadAlienBuilds(void) { - int i, j = 0; - int class, credits; - char ui_currentClass[ MAX_STRING_CHARS ]; - stage_t stage = UI_GetCurrentAlienStage( ); + int i, j = 0; + stage_t stage; - trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS ); - sscanf( ui_currentClass, "%d %d", &class, &credits ); + UI_ParseCarriageList(); + stage = UI_GetCurrentAlienStage(); - uiInfo.tremAlienUpgradeCount = 0; + uiInfo.alienBuildCount = 0; - for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) - { - if( BG_ClassCanEvolveFromTo( class, i, credits, 0 ) >= 0 && - BG_FindStagesForClass( i, stage ) && - BG_ClassIsAllowed( i ) ) + for (i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++) { - uiInfo.tremAlienUpgradeList[ j ].text = String_Alloc( BG_FindHumanNameForClassNum( i ) ); - uiInfo.tremAlienUpgradeList[ j ].cmd = - String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( i ) ) ); - uiInfo.tremAlienUpgradeList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( i ) ) ); + if (BG_Buildable(i)->team == TEAM_ALIENS && BG_Buildable(i)->buildWeapon & uiInfo.weapons && + BG_BuildableAllowedInStage(i, stage) && BG_BuildableIsAllowed(i)) + { + uiInfo.alienBuildList[j].text = BG_Buildable(i)->humanName; + uiInfo.alienBuildList[j].cmd = String_Alloc(va("cmd build %s\n", BG_Buildable(i)->name)); + uiInfo.alienBuildList[j].type = INFOTYPE_BUILDABLE; + uiInfo.alienBuildList[j].v.buildable = i; - j++; + j++; - uiInfo.tremAlienUpgradeCount++; + uiInfo.alienBuildCount++; + } } - } } /* =============== -UI_LoadTremAlienBuilds +UI_LoadHumanBuilds =============== */ -static void UI_LoadTremAlienBuilds( void ) +static void UI_LoadHumanBuilds(void) { - int weapons; - int i, j = 0; - stage_t stage; + int i, j = 0; + stage_t stage; - UI_ParseCarriageList( &weapons, NULL ); - stage = UI_GetCurrentAlienStage( ); + UI_ParseCarriageList(); + stage = UI_GetCurrentHumanStage(); - uiInfo.tremAlienBuildCount = 0; + uiInfo.humanBuildCount = 0; - for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ ) - { - if( BG_FindTeamForBuildable( i ) == BIT_ALIENS && - BG_FindBuildWeaponForBuildable( i ) & weapons && - BG_FindStagesForBuildable( i, stage ) && - BG_BuildableIsAllowed( i ) ) + for (i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++) { - uiInfo.tremAlienBuildList[ j ].text = - String_Alloc( BG_FindHumanNameForBuildable( i ) ); - uiInfo.tremAlienBuildList[ j ].cmd = - String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) ); - uiInfo.tremAlienBuildList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) ); + if (BG_Buildable(i)->team == TEAM_HUMANS && BG_Buildable(i)->buildWeapon & uiInfo.weapons && + BG_BuildableAllowedInStage(i, stage) && BG_BuildableIsAllowed(i)) + { + uiInfo.humanBuildList[j].text = BG_Buildable(i)->humanName; + uiInfo.humanBuildList[j].cmd = String_Alloc(va("cmd build %s\n", BG_Buildable(i)->name)); + uiInfo.humanBuildList[j].type = INFOTYPE_BUILDABLE; + uiInfo.humanBuildList[j].v.buildable = i; - j++; + j++; - uiInfo.tremAlienBuildCount++; + uiInfo.humanBuildCount++; + } } - } } /* =============== -UI_LoadTremHumanBuilds +UI_LoadVoiceCmds =============== */ -static void UI_LoadTremHumanBuilds( void ) +static void UI_LoadVoiceCmds(void) { - int weapons; - int i, j = 0; - stage_t stage; + voice_t *v; + voiceCmd_t *c; - UI_ParseCarriageList( &weapons, NULL ); - stage = UI_GetCurrentHumanStage( ); + const char *cmd; + char mode[2]; + char ui_voice[MAX_VOICE_CMD_LEN]; - uiInfo.tremHumanBuildCount = 0; + trap_Cvar_VariableStringBuffer("ui_voicemenu", mode, sizeof(mode)); + trap_Cvar_VariableStringBuffer("voice", ui_voice, sizeof(ui_voice)); - for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ ) - { - if( BG_FindTeamForBuildable( i ) == BIT_HUMANS && - BG_FindBuildWeaponForBuildable( i ) & weapons && - BG_FindStagesForBuildable( i, stage ) && - BG_BuildableIsAllowed( i ) ) + uiInfo.voiceCmdCount = 0; + + switch (mode[0]) { - uiInfo.tremHumanBuildList[ j ].text = - String_Alloc( BG_FindHumanNameForBuildable( i ) ); - uiInfo.tremHumanBuildList[ j ].cmd = - String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) ); - uiInfo.tremHumanBuildList[ j ].infopane = - UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) ); + default: + case '1': + cmd = "vsay"; + break; + case '2': + cmd = "vsay_team"; + break; + case '3': + cmd = "vsay_local"; + break; + }; + + v = BG_VoiceByName(uiInfo.voices, ui_voice); + if (!v) + return; - j++; + for (c = v->cmds; c; c = c->next) + { + uiInfo.voiceCmdList[uiInfo.voiceCmdCount].text = c->cmd; + uiInfo.voiceCmdList[uiInfo.voiceCmdCount].cmd = String_Alloc(va("cmd %s %s\n", cmd, c->cmd)); + uiInfo.voiceCmdList[uiInfo.voiceCmdCount].type = INFOTYPE_VOICECMD; + uiInfo.voiceCmdList[uiInfo.voiceCmdCount].v.text = c->tracks[0].text; - uiInfo.tremHumanBuildCount++; + uiInfo.voiceCmdCount++; } - } } /* @@ -3555,2156 +2784,1545 @@ static void UI_LoadTremHumanBuilds( void ) UI_LoadMods =============== */ -static void UI_LoadMods( void ) { - int numdirs; - char dirlist[2048]; - char *dirptr; - char *descptr; - int i; - int dirlen; - - uiInfo.modCount = 0; - numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof(dirlist) ); - dirptr = dirlist; - for( i = 0; i < numdirs; i++ ) { - dirlen = strlen( dirptr ) + 1; - descptr = dirptr + dirlen; - uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr); - uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc(descptr); - dirptr += dirlen + strlen(descptr) + 1; - uiInfo.modCount++; - if (uiInfo.modCount >= MAX_MODS) { - break; - } - } +static void UI_LoadMods(void) +{ + int numdirs; + char dirlist[2048]; + char *dirptr; + char *descptr; + int i; + int dirlen; -} + uiInfo.modCount = 0; + numdirs = trap_FS_GetFileList("$modlist", "", dirlist, sizeof(dirlist)); + dirptr = dirlist; + + for (i = 0; i < numdirs; i++) + { + dirlen = strlen(dirptr) + 1; + uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr); + dirptr += dirlen; + uiInfo.modCount++; + if (uiInfo.modCount >= MAX_MODS) + break; + } +} /* =============== UI_LoadMovies =============== */ -static void UI_LoadMovies( void ) { - char movielist[4096]; - char *moviename; - int i, len; - - uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 ); - - if (uiInfo.movieCount) { - if (uiInfo.movieCount > MAX_MOVIES) { - uiInfo.movieCount = MAX_MOVIES; - } - moviename = movielist; - for ( i = 0; i < uiInfo.movieCount; i++ ) { - len = strlen( moviename ); - if (!Q_stricmp(moviename + len - 4,".roq")) { - moviename[len-4] = '\0'; - } - Q_strupr(moviename); - uiInfo.movieList[i] = String_Alloc(moviename); - moviename += len + 1; - } - } +static void UI_LoadMovies(void) +{ + char movielist[4096]; + char *moviename; + int i, len; -} + uiInfo.movieCount = trap_FS_GetFileList("video", "roq", movielist, 4096); + + if (uiInfo.movieCount) + { + if (uiInfo.movieCount > MAX_MOVIES) + uiInfo.movieCount = MAX_MOVIES; + + moviename = movielist; + for (i = 0; i < uiInfo.movieCount; i++) + { + len = strlen(moviename); + + if (!Q_stricmp(moviename + len - 4, ".roq")) + moviename[len - 4] = '\0'; + Q_strupr(moviename); + uiInfo.movieList[i] = String_Alloc(moviename); + moviename += len + 1; + } + } +} /* =============== UI_LoadDemos =============== */ -static void UI_LoadDemos( void ) { - char demolist[4096]; - char demoExt[32]; - char *demoname; - int i, len; - - Com_sprintf(demoExt, sizeof(demoExt), "dm_%d", (int)trap_Cvar_VariableValue("protocol")); - - uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 ); +static void UI_LoadDemos(void) +{ + char demolist[4096]; + char demoExt[32]; + char *demoname; + int i = 0; + int len, protocol; - Com_sprintf(demoExt, sizeof(demoExt), ".dm_%d", (int)trap_Cvar_VariableValue("protocol")); + uiInfo.demoCount = 0; - if (uiInfo.demoCount) { - if (uiInfo.demoCount > MAX_DEMOS) { - uiInfo.demoCount = MAX_DEMOS; - } - demoname = demolist; - for ( i = 0; i < uiInfo.demoCount; i++ ) { - len = strlen( demoname ); - if (!Q_stricmp(demoname + len - strlen(demoExt), demoExt)) { - demoname[len-strlen(demoExt)] = '\0'; - } - Q_strupr(demoname); - uiInfo.demoList[i] = String_Alloc(demoname); - demoname += len + 1; - } - } + for(protocol = 0; protocol < 3; protocol++) { + Com_sprintf( + demoExt, sizeof(demoExt), "%s%d", DEMOEXT, + protocol == 2 ? 69 : protocol == 1 ? 70 : 71); -} + uiInfo.demoCount += trap_FS_GetFileList("demos", demoExt, demolist, 4096); + Com_sprintf( + demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, + protocol == 2 ? 69 : protocol == 1 ? 70 : 71); -static qboolean UI_SetNextMap(int actual, int index) { - int i; - for (i = actual + 1; i < uiInfo.mapCount; i++) { - if (uiInfo.mapList[i].active) { - Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish"); - return qtrue; - } - } - return qfalse; -} + if (uiInfo.demoCount) + { + if (uiInfo.demoCount > MAX_DEMOS) + uiInfo.demoCount = MAX_DEMOS; + demoname = demolist; -static void UI_StartSkirmish(qboolean next) { - int i, k, g, delay, temp; - float skill; - char buff[MAX_STRING_CHARS]; + for (; i < uiInfo.demoCount; i++) + { + len = strlen(demoname); - if (next) { - int actual; - int index = trap_Cvar_VariableValue("ui_mapIndex"); - UI_MapCountByGameType(qtrue); - UI_SelectedMap(index, &actual); - if (UI_SetNextMap(actual, index)) { - } else { - UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse); - UI_MapCountByGameType(qtrue); - Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, "skirmish"); - } - } - - g = uiInfo.gameTypes[ui_gameType.integer].gtEnum; - trap_Cvar_SetValue( "g_gametype", g ); - trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName) ); - skill = trap_Cvar_VariableValue( "g_spSkill" ); - trap_Cvar_Set("ui_scoreMap", uiInfo.mapList[ui_currentMap.integer].mapName); - - k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); - - trap_Cvar_Set("ui_singlePlayerActive", "1"); - - // set up sp overrides, will be replaced on postgame - temp = trap_Cvar_VariableValue( "capturelimit" ); - trap_Cvar_Set("ui_saveCaptureLimit", va("%i", temp)); - temp = trap_Cvar_VariableValue( "fraglimit" ); - trap_Cvar_Set("ui_saveFragLimit", va("%i", temp)); - - UI_SetCapFragLimits(qfalse); - - temp = trap_Cvar_VariableValue( "cg_drawTimer" ); - trap_Cvar_Set("ui_drawTimer", va("%i", temp)); - temp = trap_Cvar_VariableValue( "g_doWarmup" ); - trap_Cvar_Set("ui_doWarmup", va("%i", temp)); - temp = trap_Cvar_VariableValue( "g_friendlyFire" ); - trap_Cvar_Set("ui_friendlyFire", va("%i", temp)); - temp = trap_Cvar_VariableValue( "sv_maxClients" ); - trap_Cvar_Set("ui_maxClients", va("%i", temp)); - temp = trap_Cvar_VariableValue( "g_warmup" ); - trap_Cvar_Set("ui_Warmup", va("%i", temp)); - temp = trap_Cvar_VariableValue( "sv_pure" ); - trap_Cvar_Set("ui_pure", va("%i", temp)); - - trap_Cvar_Set("cg_cameraOrbit", "0"); - trap_Cvar_Set("cg_thirdPerson", "0"); - trap_Cvar_Set("cg_drawTimer", "1"); - trap_Cvar_Set("g_doWarmup", "1"); - trap_Cvar_Set("g_warmup", "15"); - trap_Cvar_Set("sv_pure", "0"); - trap_Cvar_Set("g_friendlyFire", "0"); - trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName")); - trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName")); - - if (trap_Cvar_VariableValue("ui_recordSPDemo")) { - Com_sprintf(buff, MAX_STRING_CHARS, "%s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, g); - trap_Cvar_Set("ui_recordSPDemoName", buff); - } - - delay = 500; - - { - temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2; - trap_Cvar_Set("sv_maxClients", va("%d", temp)); - for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++) { - Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]); - trap_Cmd_ExecuteText( EXEC_APPEND, buff ); - delay += 500; - } - k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers-1; i++) { - Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]); - trap_Cmd_ExecuteText( EXEC_APPEND, buff ); - delay += 500; - } - } -} + if (!Q_stricmp(demoname + len - strlen(demoExt), demoExt)) + demoname[len - strlen(demoExt)] = '\0'; -static void UI_Update(const char *name) { - int val = trap_Cvar_VariableValue(name); - - if (Q_stricmp(name, "ui_SetName") == 0) { - trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name")); - } else if (Q_stricmp(name, "ui_setRate") == 0) { - float rate = trap_Cvar_VariableValue("rate"); - if (rate >= 5000) { - trap_Cvar_Set("cl_maxpackets", "30"); - trap_Cvar_Set("cl_packetdup", "1"); - } else if (rate >= 4000) { - trap_Cvar_Set("cl_maxpackets", "15"); - trap_Cvar_Set("cl_packetdup", "2"); // favor less prediction errors when there's packet loss - } else { - trap_Cvar_Set("cl_maxpackets", "15"); - trap_Cvar_Set("cl_packetdup", "1"); // favor lower bandwidth - } - } else if (Q_stricmp(name, "ui_GetName") == 0) { - trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name")); - } else if (Q_stricmp(name, "r_colorbits") == 0) { - switch (val) { - case 0: - trap_Cvar_SetValue( "r_depthbits", 0 ); - trap_Cvar_SetValue( "r_stencilbits", 0 ); - break; - case 16: - trap_Cvar_SetValue( "r_depthbits", 16 ); - trap_Cvar_SetValue( "r_stencilbits", 0 ); - break; - case 32: - trap_Cvar_SetValue( "r_depthbits", 24 ); - break; - } - } else if (Q_stricmp(name, "r_lodbias") == 0) { - switch (val) { - case 0: - trap_Cvar_SetValue( "r_subdivisions", 4 ); - break; - case 1: - trap_Cvar_SetValue( "r_subdivisions", 12 ); - break; - case 2: - trap_Cvar_SetValue( "r_subdivisions", 20 ); - break; - } - } else if (Q_stricmp(name, "ui_glCustom") == 0) { - switch (val) { - case 0: // high quality - trap_Cvar_SetValue( "r_fullScreen", 1 ); - trap_Cvar_SetValue( "r_subdivisions", 4 ); - trap_Cvar_SetValue( "r_vertexlight", 0 ); - trap_Cvar_SetValue( "r_lodbias", 0 ); - trap_Cvar_SetValue( "r_colorbits", 32 ); - trap_Cvar_SetValue( "r_depthbits", 24 ); - trap_Cvar_SetValue( "r_picmip", 0 ); - trap_Cvar_SetValue( "r_mode", 4 ); - trap_Cvar_SetValue( "r_texturebits", 32 ); - trap_Cvar_SetValue( "r_fastSky", 0 ); - trap_Cvar_SetValue( "r_inGameVideo", 1 ); - trap_Cvar_SetValue( "cg_shadows", 1 ); - trap_Cvar_SetValue( "cg_brassTime", 2500 ); - trap_Cvar_SetValue( "cg_bounceParticles", 1 ); - trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); - break; - case 1: // normal - trap_Cvar_SetValue( "r_fullScreen", 1 ); - trap_Cvar_SetValue( "r_subdivisions", 12 ); - trap_Cvar_SetValue( "r_vertexlight", 0 ); - trap_Cvar_SetValue( "r_lodbias", 0 ); - trap_Cvar_SetValue( "r_colorbits", 0 ); - trap_Cvar_SetValue( "r_depthbits", 24 ); - trap_Cvar_SetValue( "r_picmip", 1 ); - trap_Cvar_SetValue( "r_mode", 3 ); - trap_Cvar_SetValue( "r_texturebits", 0 ); - trap_Cvar_SetValue( "r_fastSky", 0 ); - trap_Cvar_SetValue( "r_inGameVideo", 1 ); - trap_Cvar_SetValue( "cg_brassTime", 2500 ); - trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); - trap_Cvar_SetValue( "cg_shadows", 0 ); - trap_Cvar_SetValue( "cg_bounceParticles", 0 ); - break; - case 2: // fast - trap_Cvar_SetValue( "r_fullScreen", 1 ); - trap_Cvar_SetValue( "r_subdivisions", 8 ); - trap_Cvar_SetValue( "r_vertexlight", 0 ); - trap_Cvar_SetValue( "r_lodbias", 1 ); - trap_Cvar_SetValue( "r_colorbits", 0 ); - trap_Cvar_SetValue( "r_depthbits", 0 ); - trap_Cvar_SetValue( "r_picmip", 1 ); - trap_Cvar_SetValue( "r_mode", 3 ); - trap_Cvar_SetValue( "r_texturebits", 0 ); - trap_Cvar_SetValue( "cg_shadows", 0 ); - trap_Cvar_SetValue( "r_fastSky", 1 ); - trap_Cvar_SetValue( "r_inGameVideo", 0 ); - trap_Cvar_SetValue( "cg_brassTime", 0 ); - trap_Cvar_SetValue( "cg_bounceParticles", 0 ); - trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); - break; - case 3: // fastest - trap_Cvar_SetValue( "r_fullScreen", 1 ); - trap_Cvar_SetValue( "r_subdivisions", 20 ); - trap_Cvar_SetValue( "r_vertexlight", 1 ); - trap_Cvar_SetValue( "r_lodbias", 2 ); - trap_Cvar_SetValue( "r_colorbits", 16 ); - trap_Cvar_SetValue( "r_depthbits", 16 ); - trap_Cvar_SetValue( "r_mode", 3 ); - trap_Cvar_SetValue( "r_picmip", 2 ); - trap_Cvar_SetValue( "r_texturebits", 16 ); - trap_Cvar_SetValue( "cg_shadows", 0 ); - trap_Cvar_SetValue( "cg_brassTime", 0 ); - trap_Cvar_SetValue( "r_fastSky", 1 ); - trap_Cvar_SetValue( "r_inGameVideo", 0 ); - trap_Cvar_SetValue( "cg_bounceParticles", 0 ); - trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); - break; - } - } else if (Q_stricmp(name, "ui_mousePitch") == 0) { - if (val == 0) { - trap_Cvar_SetValue( "m_pitch", 0.022f ); - } else { - trap_Cvar_SetValue( "m_pitch", -0.022f ); + uiInfo.demoList[i] = String_Alloc(demoname); + demoname += len + 1; + } + } } - } } -static void UI_RunMenuScript(char **args) { - const char *name, *name2; - char buff[1024]; - const char *cmd; - - if (String_Parse(args, &name)) { - if (Q_stricmp(name, "StartServer") == 0) { - int i, clients, oldclients; - float skill; - trap_Cvar_Set("cg_thirdPerson", "0"); - trap_Cvar_Set("cg_cameraOrbit", "0"); - trap_Cvar_Set("ui_singlePlayerActive", "0"); - trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) ); - trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 8, uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) ); - trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName")); - trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName")); - trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) ); - skill = trap_Cvar_VariableValue( "g_spSkill" ); - // set max clients based on spots - oldclients = trap_Cvar_VariableValue( "sv_maxClients" ); - clients = 0; - for (i = 0; i < PLAYERS_PER_TEAM; i++) { - int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1)); - if (bot >= 0) { - clients++; - } - bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1)); - if (bot >= 0) { - clients++; - } - } - if (clients == 0) { - clients = 8; - } - - if (oldclients > clients) { - clients = oldclients; - } +static void UI_Update(const char *name) +{ + int val = trap_Cvar_VariableValue(name); - trap_Cvar_Set("sv_maxClients", va("%d",clients)); + if (Q_stricmp(name, "ui_SetName") == 0) + trap_Cvar_Set("name", UI_Cvar_VariableString("ui_Name")); + else if (Q_stricmp(name, "ui_setRate") == 0) + { + float rate = trap_Cvar_VariableValue("rate"); - for (i = 0; i < PLAYERS_PER_TEAM; i++) { - int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1)); - if (bot > 1) { - Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill); - trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + if (rate >= 5000) + { + trap_Cvar_Set("cl_maxpackets", "30"); + trap_Cvar_Set("cl_packetdup", "1"); } - bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1)); - if (bot > 1) { - Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill); - trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + else if (rate >= 4000) + { + trap_Cvar_Set("cl_maxpackets", "15"); + trap_Cvar_Set("cl_packetdup", "2"); // favor less prediction errors when there's packet loss + } + else + { + trap_Cvar_Set("cl_maxpackets", "15"); + trap_Cvar_Set("cl_packetdup", "1"); // favor lower bandwidth } - } - } else if (Q_stricmp(name, "updateSPMenu") == 0) { - UI_SetCapFragLimits(qtrue); - UI_MapCountByGameType(qtrue); - ui_mapIndex.integer = UI_GetIndexFromSelection(ui_currentMap.integer); - trap_Cvar_Set("ui_mapIndex", va("%d", ui_mapIndex.integer)); - Menu_SetFeederSelection(NULL, FEEDER_MAPS, ui_mapIndex.integer, "skirmish"); - UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse); - UI_GameType_HandleKey(0, NULL, K_MOUSE2, qfalse); - } else if (Q_stricmp(name, "resetDefaults") == 0) { - trap_Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n"); - trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n"); - Controls_SetDefaults(); - trap_Cvar_Set("com_introPlayed", "1" ); - trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" ); - } else if (Q_stricmp(name, "loadArenas") == 0) { - UI_LoadArenas(); - UI_MapCountByGameType(qfalse); - Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, "createserver"); - } else if (Q_stricmp(name, "loadServerInfo") == 0) { - UI_ServerInfo(); - } else if (Q_stricmp(name, "saveControls") == 0) { - Controls_SetConfig(qtrue); - } else if (Q_stricmp(name, "loadControls") == 0) { - Controls_GetConfig(); - } else if (Q_stricmp(name, "clearError") == 0) { - trap_Cvar_Set("com_errorMessage", ""); - } else if (Q_stricmp(name, "loadGameInfo") == 0) { -/* UI_ParseGameInfo("gameinfo.txt"); - UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);*/ - } else if (Q_stricmp(name, "resetScores") == 0) { - UI_ClearScores(); - } else if (Q_stricmp(name, "RefreshServers") == 0) { - UI_StartServerRefresh(qtrue); - UI_BuildServerDisplayList(qtrue); - } else if (Q_stricmp(name, "InitServerList") == 0) { - int time = trap_RealTime( NULL ); - int last; - int sortColumn; - - // set up default sorting - if(!uiInfo.serverStatus.sorted && Int_Parse(args, &sortColumn)) - { - uiInfo.serverStatus.sortKey = sortColumn; - uiInfo.serverStatus.sortDir = 0; - } - - // refresh if older than 3 days or if list is empty - last = atoi( UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i_time", - ui_netSource.integer ) ) ); - if( trap_LAN_GetServerCount( ui_netSource.integer ) < 1 || - ( time - last ) > 3600 ) - { - UI_StartServerRefresh(qtrue); - UI_BuildServerDisplayList(qtrue); - } - } else if (Q_stricmp(name, "RefreshFilter") == 0) { - UI_StartServerRefresh(qfalse); - UI_BuildServerDisplayList(qtrue); - } else if (Q_stricmp(name, "RunSPDemo") == 0) { - if (uiInfo.demoAvailable) { - trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s_%i\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum)); - } - } else if (Q_stricmp(name, "LoadDemos") == 0) { - UI_LoadDemos(); - } else if (Q_stricmp(name, "LoadMovies") == 0) { - UI_LoadMovies(); - } else if (Q_stricmp(name, "LoadMods") == 0) { - UI_LoadMods(); } - -//TA: tremulous menus - else if( Q_stricmp( name, "LoadTeams" ) == 0 ) - UI_LoadTremTeams( ); - else if( Q_stricmp( name, "JoinTeam" ) == 0 ) + else if (Q_stricmp(name, "ui_GetName") == 0) + trap_Cvar_Set("ui_Name", UI_Cvar_VariableString("name")); + else if (Q_stricmp(name, "r_colorbits") == 0) { - if( ( cmd = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + switch (val) + { + case 0: + trap_Cvar_SetValue("r_depthbits", 0); + trap_Cvar_SetValue("r_stencilbits", 0); + break; + + case 16: + trap_Cvar_SetValue("r_depthbits", 16); + trap_Cvar_SetValue("r_stencilbits", 0); + break; + + case 32: + trap_Cvar_SetValue("r_depthbits", 24); + break; + } } - else if( Q_stricmp( name, "LoadHumanItems" ) == 0 ) - UI_LoadTremHumanItems( ); - else if( Q_stricmp( name, "SpawnWithHumanItem" ) == 0 ) + else if (Q_stricmp(name, "r_lodbias") == 0) { - if( ( cmd = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + switch (val) + { + case 0: + trap_Cvar_SetValue("r_subdivisions", 4); + break; + + case 1: + trap_Cvar_SetValue("r_subdivisions", 12); + break; + + case 2: + trap_Cvar_SetValue("r_subdivisions", 20); + break; + } } - else if( Q_stricmp( name, "LoadAlienClasses" ) == 0 ) - UI_LoadTremAlienClasses( ); - else if( Q_stricmp( name, "SpawnAsAlienClass" ) == 0 ) + else if (Q_stricmp(name, "ui_glCustom") == 0) { - if( ( cmd = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + switch (val) + { + case 0: // high quality + trap_Cvar_SetValue("r_subdivisions", 4); + trap_Cvar_SetValue("r_vertexlight", 0); + trap_Cvar_SetValue("r_lodbias", 0); + trap_Cvar_SetValue("r_colorbits", 32); + trap_Cvar_SetValue("r_depthbits", 24); + trap_Cvar_SetValue("r_picmip", 0); + trap_Cvar_SetValue("r_texturebits", 32); + trap_Cvar_SetValue("r_fastSky", 0); + trap_Cvar_SetValue("r_inGameVideo", 1); + trap_Cvar_SetValue("cg_shadows", 1); + trap_Cvar_SetValue("cg_bounceParticles", 1); + trap_Cvar_Set("r_texturemode", "GL_LINEAR_MIPMAP_LINEAR"); + break; + + case 1: // normal + trap_Cvar_SetValue("r_subdivisions", 12); + trap_Cvar_SetValue("r_vertexlight", 0); + trap_Cvar_SetValue("r_lodbias", 0); + trap_Cvar_SetValue("r_colorbits", 0); + trap_Cvar_SetValue("r_depthbits", 24); + trap_Cvar_SetValue("r_picmip", 1); + trap_Cvar_SetValue("r_texturebits", 0); + trap_Cvar_SetValue("r_fastSky", 0); + trap_Cvar_SetValue("r_inGameVideo", 1); + trap_Cvar_Set("r_texturemode", "GL_LINEAR_MIPMAP_LINEAR"); + trap_Cvar_SetValue("cg_shadows", 0); + trap_Cvar_SetValue("cg_bounceParticles", 0); + break; + + case 2: // fast + trap_Cvar_SetValue("r_subdivisions", 8); + trap_Cvar_SetValue("r_vertexlight", 0); + trap_Cvar_SetValue("r_lodbias", 1); + trap_Cvar_SetValue("r_colorbits", 0); + trap_Cvar_SetValue("r_depthbits", 0); + trap_Cvar_SetValue("r_picmip", 1); + trap_Cvar_SetValue("r_texturebits", 0); + trap_Cvar_SetValue("cg_shadows", 0); + trap_Cvar_SetValue("r_fastSky", 1); + trap_Cvar_SetValue("r_inGameVideo", 0); + trap_Cvar_SetValue("cg_bounceParticles", 0); + trap_Cvar_Set("r_texturemode", "GL_LINEAR_MIPMAP_NEAREST"); + break; + + case 3: // fastest + trap_Cvar_SetValue("r_subdivisions", 20); + trap_Cvar_SetValue("r_vertexlight", 1); + trap_Cvar_SetValue("r_lodbias", 2); + trap_Cvar_SetValue("r_colorbits", 16); + trap_Cvar_SetValue("r_depthbits", 16); + trap_Cvar_SetValue("r_picmip", 2); + trap_Cvar_SetValue("r_texturebits", 16); + trap_Cvar_SetValue("cg_shadows", 0); + trap_Cvar_SetValue("r_fastSky", 1); + trap_Cvar_SetValue("r_inGameVideo", 0); + trap_Cvar_SetValue("cg_bounceParticles", 0); + trap_Cvar_Set("r_texturemode", "GL_LINEAR_MIPMAP_NEAREST"); + break; + } } - else if( Q_stricmp( name, "LoadHumanArmouryBuys" ) == 0 ) - UI_LoadTremHumanArmouryBuys( ); - else if( Q_stricmp( name, "BuyFromArmoury" ) == 0 ) + else if (Q_stricmp(name, "ui_mousePitch") == 0) { - if( ( cmd = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + if (val == 0) + trap_Cvar_SetValue("m_pitch", 0.022f); + else + trap_Cvar_SetValue("m_pitch", -0.022f); } - else if( Q_stricmp( name, "LoadHumanArmourySells" ) == 0 ) - UI_LoadTremHumanArmourySells( ); - else if( Q_stricmp( name, "SellToArmoury" ) == 0 ) - { - if( ( cmd = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); - } - else if( Q_stricmp( name, "LoadAlienUpgrades" ) == 0 ) - { - UI_LoadTremAlienUpgrades( ); +} - //disallow the menu if it would be empty - if( uiInfo.tremAlienUpgradeCount <= 0 ) - Menus_CloseAll( ); - } - else if( Q_stricmp( name, "UpgradeToNewClass" ) == 0 ) - { - if( ( cmd = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); - } - else if( Q_stricmp( name, "LoadAlienBuilds" ) == 0 ) - UI_LoadTremAlienBuilds( ); - else if( Q_stricmp( name, "BuildAlienBuildable" ) == 0 ) - { - if( ( cmd = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); - } - else if( Q_stricmp( name, "LoadHumanBuilds" ) == 0 ) - UI_LoadTremHumanBuilds( ); - else if( Q_stricmp( name, "BuildHumanBuildable" ) == 0 ) - { - if( ( cmd = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].cmd ) ) - trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); - } - else if( Q_stricmp( name, "Say" ) == 0 ) - { - char buffer[ MAX_CVAR_VALUE_STRING ]; - trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) ); +// FIXME: lookup table +static void UI_RunMenuScript(char **args) +{ + const char *name, *name2; + char buff[1024]; + const char *cmd; - if( !buffer[ 0 ] ) - ; - else if( ui_chatCommands.integer && ( buffer[ 0 ] == '/' || - buffer[ 0 ] == '\\' ) ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "%s\n", buffer + 1 ) ); - } - else if( uiInfo.chatTeam ) - trap_Cmd_ExecuteText( EXEC_APPEND, va( "say_team \"%s\"\n", buffer ) ); - else - trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) ); - } - else if( Q_stricmp( name, "PTRCRestore" ) == 0 ) + if (String_Parse(args, &name)) { - int len; - char text[ 16 ]; - fileHandle_t f; - char command[ 32 ]; + if (Q_stricmp(name, "StartServer") == 0) + { + trap_Cvar_SetValue("dedicated", Com_Clamp(0, 2, ui_dedicated.integer)); + trap_Cmd_ExecuteText( + EXEC_APPEND, va("wait ; wait ; map \"%s\"\n", uiInfo.mapList[ui_selectedMap.integer].mapLoadName)); + } + else if (Q_stricmp(name, "resetDefaults") == 0) + { + trap_Cmd_ExecuteText(EXEC_APPEND, "exec default.cfg\n"); + trap_Cmd_ExecuteText(EXEC_APPEND, "cvar_restart\n"); + Controls_SetDefaults(); + trap_Cvar_Set("com_introPlayed", "1"); + trap_Cmd_ExecuteText(EXEC_APPEND, "vid_restart\n"); + } + else if (Q_stricmp(name, "loadArenas") == 0) + { + UI_LoadArenas(); + Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, "createserver"); + } + else if (Q_stricmp(name, "loadServerInfo") == 0) + UI_ServerInfo(); + else if (Q_stricmp(name, "getNews") == 0) + UI_UpdateNews(qtrue); + else if (Q_stricmp(name, "checkForUpdate") == 0) + UI_UpdateGithubRelease(); + else if (Q_stricmp(name, "downloadUpdate") == 0) + trap_Cmd_ExecuteText(EXEC_APPEND, "downloadUpdate"); + else if (Q_stricmp(name, "installUpdate") == 0) + trap_Cmd_ExecuteText(EXEC_APPEND, "installUpdate"); + else if (Q_stricmp(name, "saveControls") == 0) + Controls_SetConfig(qtrue); + else if (Q_stricmp(name, "loadControls") == 0) + Controls_GetConfig(); + else if (Q_stricmp(name, "clearError") == 0) + { + trap_Cvar_Set("com_errorMessage", ""); + } + else if (Q_stricmp(name, "clearDemoError") == 0) + { + trap_Cvar_Set("com_demoErrorMessage", ""); + } + else if (Q_stricmp(name, "downloadIgnore") == 0) + { + trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_IGNORE)); + } + else if (Q_stricmp(name, "downloadCURL") == 0) + { + trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_CURL)); + } + else if (Q_stricmp(name, "downloadUDP") == 0) + { + trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_UDP)); + } + else if (Q_stricmp(name, "RefreshServers") == 0) + { + UI_StartServerRefresh(qtrue); + UI_BuildServerDisplayList(qtrue); + } + else if (Q_stricmp(name, "InitServerList") == 0) + { + int time = trap_RealTime(NULL); + int last; + int sortColumn; - // load the file - len = trap_FS_FOpenFile( "ptrc.cfg", &f, FS_READ ); + // set up default sorting - if( len > 0 && ( len < sizeof( text ) - 1 ) ) - { - trap_FS_Read( text, len, f ); - text[ len ] = 0; - trap_FS_FCloseFile( f ); + if (!uiInfo.serverStatus.sorted && Int_Parse(args, &sortColumn)) + { + uiInfo.serverStatus.sortKey = sortColumn; + uiInfo.serverStatus.sortDir = 0; + } - Com_sprintf( command, 32, "ptrcrestore %s", text ); + // refresh if older than 3 days or if list is empty + last = atoi(UI_Cvar_VariableString(va("ui_lastServerRefresh_%i_time", ui_netSource.integer))); - trap_Cmd_ExecuteText( EXEC_APPEND, command ); - } - } -//TA: tremulous menus + if (trap_LAN_GetServerCount(ui_netSource.integer) < 1 || (time - last) > 3600) + { + UI_StartServerRefresh(qtrue); + UI_BuildServerDisplayList(qtrue); + } + } + else if (Q_stricmp(name, "RefreshFilter") == 0) + { + UI_StartServerRefresh(qfalse); + UI_BuildServerDisplayList(qtrue); + } + else if (Q_stricmp(name, "LoadDemos") == 0) + UI_LoadDemos(); + else if (Q_stricmp(name, "LoadMovies") == 0) + UI_LoadMovies(); + else if (Q_stricmp(name, "LoadMods") == 0) + UI_LoadMods(); + else if (Q_stricmp(name, "LoadTeams") == 0) + UI_LoadTeams(); + else if (Q_stricmp(name, "JoinTeam") == 0) + { + if ((cmd = uiInfo.teamList[uiInfo.teamIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); + } + else if (Q_stricmp(name, "LoadHumanItems") == 0) + UI_LoadHumanItems(); + else if (Q_stricmp(name, "SpawnWithHumanItem") == 0) + { + if ((cmd = uiInfo.humanItemList[uiInfo.humanItemIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); + } + else if (Q_stricmp(name, "LoadAlienClasses") == 0) + UI_LoadAlienClasses(); + else if (Q_stricmp(name, "SpawnAsAlienClass") == 0) + { + if ((cmd = uiInfo.alienClassList[uiInfo.alienClassIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); + } + else if (Q_stricmp(name, "LoadHumanArmouryBuys") == 0) + UI_LoadHumanArmouryBuys(); + else if (Q_stricmp(name, "BuyFromArmoury") == 0) + { + if ((cmd = uiInfo.humanArmouryBuyList[uiInfo.humanArmouryBuyIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); - else if (Q_stricmp(name, "playMovie") == 0) { - if (uiInfo.previewMovie >= 0) { - trap_CIN_StopCinematic(uiInfo.previewMovie); - } - trap_Cmd_ExecuteText( EXEC_APPEND, va("cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex])); - } else if (Q_stricmp(name, "RunMod") == 0) { - trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName); - trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); - } else if (Q_stricmp(name, "RunDemo") == 0) { - trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s\n", uiInfo.demoList[uiInfo.demoIndex])); - } else if (Q_stricmp(name, "Tremulous") == 0) { - trap_Cvar_Set( "fs_game", ""); - trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); - } else if (Q_stricmp(name, "closeJoin") == 0) { - if (uiInfo.serverStatus.refreshActive) { - UI_StopServerRefresh(); - uiInfo.serverStatus.nextDisplayRefresh = 0; - uiInfo.nextServerStatusRefresh = 0; - uiInfo.nextFindPlayerRefresh = 0; - UI_BuildServerDisplayList(qtrue); - } else { - Menus_CloseByName("joinserver"); - Menus_OpenByName("main"); - } - } else if (Q_stricmp(name, "StopRefresh") == 0) { - UI_StopServerRefresh(); - uiInfo.serverStatus.nextDisplayRefresh = 0; - uiInfo.nextServerStatusRefresh = 0; - uiInfo.nextFindPlayerRefresh = 0; - } else if (Q_stricmp(name, "UpdateFilter") == 0) { - if (ui_netSource.integer == AS_LOCAL) { - UI_StartServerRefresh(qtrue); - } - UI_BuildServerDisplayList(qtrue); - UI_FeederSelection(FEEDER_SERVERS, 0); - } else if (Q_stricmp(name, "ServerStatus") == 0) { - trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof(uiInfo.serverStatusAddress)); - UI_BuildServerStatus(qtrue); - } else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) { - Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress)); - UI_BuildServerStatus(qtrue); - Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); - } else if (Q_stricmp(name, "FindPlayer") == 0) { - UI_BuildFindPlayerList(qtrue); - // clear the displayed server status info - uiInfo.serverStatusInfo.numLines = 0; - Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); - } else if (Q_stricmp(name, "JoinServer") == 0) { - trap_Cvar_Set("cg_thirdPerson", "0"); - trap_Cvar_Set("cg_cameraOrbit", "0"); - trap_Cvar_Set("ui_singlePlayerActive", "0"); - if (uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) { - trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024); - trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) ); - } - } else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) { - trap_Cvar_Set("ui_singlePlayerActive", "0"); - if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) ); - } - } else if (Q_stricmp(name, "Quit") == 0) { - trap_Cvar_Set("ui_singlePlayerActive", "0"); - trap_Cmd_ExecuteText( EXEC_NOW, "quit"); - } else if (Q_stricmp(name, "Controls") == 0) { - trap_Cvar_Set( "cl_paused", "1" ); - trap_Key_SetCatcher( KEYCATCH_UI ); - Menus_CloseAll(); - Menus_ActivateByName("setup_menu2"); - } else if (Q_stricmp(name, "Leave") == 0) { - trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" ); - trap_Key_SetCatcher( KEYCATCH_UI ); - Menus_CloseAll(); - Menus_ActivateByName("main"); - } else if (Q_stricmp(name, "ServerSort") == 0) { - int sortColumn; - if (Int_Parse(args, &sortColumn)) { - // if same column we're already sorting on then flip the direction - if (sortColumn == uiInfo.serverStatus.sortKey) { - uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir; - } - // make sure we sort again - UI_ServersSort(sortColumn, qtrue); - uiInfo.serverStatus.sorted = qtrue; - } - } else if (Q_stricmp(name, "nextSkirmish") == 0) { - UI_StartSkirmish(qtrue); - } else if (Q_stricmp(name, "SkirmishStart") == 0) { - UI_StartSkirmish(qfalse); - } else if (Q_stricmp(name, "closeingame") == 0) { - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); - Menus_CloseAll(); - } else if (Q_stricmp(name, "voteMap") == 0) { - if (ui_currentNetMap.integer >=0 && ui_currentNetMap.integer < uiInfo.mapCount) { - trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName) ); - } - } - else if( Q_stricmp( name, "voteKick" ) == 0 ) - { - if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick %d\n", - uiInfo.clientNums[ uiInfo.playerIndex ] ) ); - } - } - else if( Q_stricmp( name, "voteMute" ) == 0 ) - { - if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote mute %d\n", - uiInfo.clientNums[ uiInfo.playerIndex ] ) ); - } - } - else if( Q_stricmp( name, "voteUnMute" ) == 0 ) - { - if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote unmute %d\n", - uiInfo.clientNums[ uiInfo.playerIndex ] ) ); - } - } - else if( Q_stricmp( name, "voteTeamKick" ) == 0 ) - { - if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote kick %d\n", - uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); - } - } - else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 ) - { - if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote denybuild %d\n", - uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); - } - } - else if( Q_stricmp( name, "voteTeamAllowBuild" ) == 0 ) - { - if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) - { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote allowbuild %d\n", - uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); - } - } - else if (Q_stricmp(name, "addFavorite") == 0) { - if (ui_netSource.integer != AS_FAVORITES) { - char name[MAX_NAME_LENGTH]; - char addr[MAX_NAME_LENGTH]; - int res; - - trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); - name[0] = addr[0] = '\0'; - Q_strncpyz(name, Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH); - Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); - if (strlen(name) > 0 && strlen(addr) > 0) { - res = trap_LAN_AddServer(AS_FAVORITES, name, addr); - if (res == 0) { - // server already in the list - Com_Printf("Favorite already in list\n"); - } - else if (res == -1) { - // list full - Com_Printf("Favorite list full\n"); - } - else { - // successfully added - Com_Printf("Added favorite server %s\n", addr); - } + UI_InstallCaptureFunc(UI_ArmouryRefreshCb, NULL, 1000); } - } - } else if (Q_stricmp(name, "deleteFavorite") == 0) { - if (ui_netSource.integer == AS_FAVORITES) { - char addr[MAX_NAME_LENGTH]; - trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); - addr[0] = '\0'; - Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); - if (strlen(addr) > 0) { - trap_LAN_RemoveServer(AS_FAVORITES, addr); + else if (Q_stricmp(name, "LoadHumanArmourySells") == 0) + UI_LoadHumanArmourySells(); + else if (Q_stricmp(name, "SellToArmoury") == 0) + { + if ((cmd = uiInfo.humanArmourySellList[uiInfo.humanArmourySellIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); + + UI_InstallCaptureFunc(UI_ArmouryRefreshCb, NULL, 1000); } - } - } else if (Q_stricmp(name, "createFavorite") == 0) { - if (ui_netSource.integer == AS_FAVORITES) { - char name[MAX_NAME_LENGTH]; - char addr[MAX_NAME_LENGTH]; - int res; - - name[0] = addr[0] = '\0'; - Q_strncpyz(name, UI_Cvar_VariableString("ui_favoriteName"), MAX_NAME_LENGTH); - Q_strncpyz(addr, UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH); - if (strlen(name) > 0 && strlen(addr) > 0) { - res = trap_LAN_AddServer(AS_FAVORITES, name, addr); - if (res == 0) { - // server already in the list - Com_Printf("Favorite already in list\n"); - } - else if (res == -1) { - // list full - Com_Printf("Favorite list full\n"); - } - else { - // successfully added - Com_Printf("Added favorite server %s\n", addr); - } + else if (Q_stricmp(name, "LoadAlienUpgrades") == 0) + { + UI_LoadAlienUpgrades(); } - } - } else if (Q_stricmp(name, "orders") == 0) { - const char *orders; - if (String_Parse(args, &orders)) { - int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); - if (selectedPlayer < uiInfo.myTeamCount) { - strcpy(buff, orders); - trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) ); - trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); - } else { - int i; - for (i = 0; i < uiInfo.myTeamCount; i++) { - if (Q_stricmp(UI_Cvar_VariableString("name"), uiInfo.teamNames[i]) == 0) { - continue; - } - strcpy(buff, orders); - trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamNames[i]) ); - trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); - } + else if (Q_stricmp(name, "UpgradeToNewClass") == 0) + { + if ((cmd = uiInfo.alienUpgradeList[uiInfo.alienUpgradeIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); } - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); - Menus_CloseAll(); - } - } else if (Q_stricmp(name, "voiceOrdersTeam") == 0) { - const char *orders; - if (String_Parse(args, &orders)) { - int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); - if (selectedPlayer == uiInfo.myTeamCount) { - trap_Cmd_ExecuteText( EXEC_APPEND, orders ); - trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); - } - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); - Menus_CloseAll(); - } - } else if (Q_stricmp(name, "voiceOrders") == 0) { - const char *orders; - if (String_Parse(args, &orders)) { - int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); - if (selectedPlayer < uiInfo.myTeamCount) { - strcpy(buff, orders); - trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) ); - trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); - } - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); - Menus_CloseAll(); - } - } else if (Q_stricmp(name, "glCustom") == 0) { - trap_Cvar_Set("ui_glCustom", "4"); - } else if (Q_stricmp(name, "update") == 0) { - if (String_Parse(args, &name2)) - UI_Update(name2); - } else if (Q_stricmp(name, "InitIgnoreList") == 0) { - UI_BuildPlayerList(); - } else if (Q_stricmp(name, "ToggleIgnore") == 0) { - if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) - { - if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) + else if (Q_stricmp(name, "LoadAlienBuilds") == 0) + UI_LoadAlienBuilds(); + else if (Q_stricmp(name, "BuildAlienBuildable") == 0) { - BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ); - trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n", - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + if ((cmd = uiInfo.alienBuildList[uiInfo.alienBuildIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); } - else + else if (Q_stricmp(name, "LoadHumanBuilds") == 0) + UI_LoadHumanBuilds(); + else if (Q_stricmp(name, "BuildHumanBuildable") == 0) { - BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ); - trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n", - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + if ((cmd = uiInfo.humanBuildList[uiInfo.humanBuildIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); } - } - } else if (Q_stricmp(name, "IgnorePlayer") == 0) { - if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) - { - if( !BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) + else if (Q_stricmp(name, "LoadVoiceCmds") == 0) { - BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ); - trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n", - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + UI_LoadVoiceCmds(); } - } - } else if (Q_stricmp(name, "UnIgnorePlayer") == 0) { - if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) - { - if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) + else if (Q_stricmp(name, "ExecuteVoiceCmd") == 0) { - BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ uiInfo.ignoreIndex ] ); - trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n", - uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + if ((cmd = uiInfo.voiceCmdList[uiInfo.voiceCmdIndex].cmd)) + trap_Cmd_ExecuteText(EXEC_APPEND, cmd); } - } - } else if (Q_stricmp(name, "setPbClStatus") == 0) { - int stat; - if ( Int_Parse( args, &stat ) ) - trap_SetPbClStatus( stat ); - } - else { - Com_Printf("unknown UI script %s\n", name); - } - } -} + else if (Q_stricmp(name, "Say") == 0) + { + char buffer[MAX_CVAR_VALUE_STRING]; + trap_Cvar_VariableStringBuffer("ui_sayBuffer", buffer, sizeof(buffer)); -static void UI_GetTeamColor(vec4_t *color) { -} + if (!buffer[0]) + ; + else if (ui_chatCommands.integer && (buffer[0] == '/' || buffer[0] == '\\')) + { + trap_Cmd_ExecuteText(EXEC_APPEND, va("%s\n", buffer + 1)); + } + else if (uiInfo.chatTeam) + trap_Cmd_ExecuteText(EXEC_APPEND, va("say_team \"%s\"\n", buffer)); + else + trap_Cmd_ExecuteText(EXEC_APPEND, va("say \"%s\"\n", buffer)); + } + else if (Q_stricmp(name, "SayKeydown") == 0) + { + if (ui_chatCommands.integer) + { + char buffer[MAX_CVAR_VALUE_STRING]; + trap_Cvar_VariableStringBuffer("ui_sayBuffer", buffer, sizeof(buffer)); + + if (buffer[0] == '/' || buffer[0] == '\\') + Menus_ReplaceActiveByName("say_command"); + else if (uiInfo.chatTeam) + Menus_ReplaceActiveByName("say_team"); + else + Menus_ReplaceActiveByName("say"); + } + } + else if (Q_stricmp(name, "playMovie") == 0) + { + if (uiInfo.previewMovie >= 0) + trap_CIN_StopCinematic(uiInfo.previewMovie); -/* -================== -UI_MapCountByGameType -================== -*/ -static int UI_MapCountByGameType(qboolean singlePlayer) { - int i, c, game; - c = 0; - game = singlePlayer ? uiInfo.gameTypes[ui_gameType.integer].gtEnum : uiInfo.gameTypes[ui_netGameType.integer].gtEnum; - - for (i = 0; i < uiInfo.mapCount; i++) { - uiInfo.mapList[i].active = qfalse; - if ( uiInfo.mapList[i].typeBits & (1 << game)) { - if (singlePlayer) { - if (!(uiInfo.mapList[i].typeBits & (1 << 2))) { - continue; + trap_Cmd_ExecuteText(EXEC_APPEND, va("cinematic \"%s.roq\" 2\n", uiInfo.movieList[uiInfo.movieIndex])); } - } - c++; - uiInfo.mapList[i].active = qtrue; - } - } - return c; -} + else if (Q_stricmp(name, "RunMod") == 0) + { + trap_Cvar_Set("fs_game", uiInfo.modList[uiInfo.modIndex].modName); + trap_Cmd_ExecuteText(EXEC_APPEND, "vid_restart\n"); + } + else if (Q_stricmp(name, "RunDemo") == 0) + { + if(uiInfo.demoList[uiInfo.demoIndex]) + { + trap_Cmd_ExecuteText(EXEC_APPEND, va("demo \"%s\"\n", uiInfo.demoList[uiInfo.demoIndex])); + } else { + trap_Cvar_Set("com_demoErrorMessage", "No demo selected."); + Menus_ActivateByName("demo_error_popmenu"); + } + } + else if (Q_stricmp(name, "Tremulous") == 0) + { + trap_Cvar_Set("fs_game", ""); + trap_Cmd_ExecuteText(EXEC_APPEND, "vid_restart\n"); + } + else if (Q_stricmp(name, "closeJoin") == 0) + { + if (uiInfo.serverStatus.refreshActive) + { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + UI_BuildServerDisplayList(qtrue); + } + else + { + Menus_CloseByName("joinserver"); + Menus_ActivateByName("main"); + } + } + else if (Q_stricmp(name, "StopRefresh") == 0) + { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + } + else if (Q_stricmp(name, "UpdateFilter") == 0) + { + if (ui_netSource.integer == AS_LOCAL) + UI_StartServerRefresh(qtrue); -qboolean UI_hasSkinForBase(const char *base, const char *team) { - char test[1024]; + UI_BuildServerDisplayList(qtrue); + UI_FeederSelection(FEEDER_SERVERS, 0); + } + else if (Q_stricmp(name, "ServerStatus") == 0) + { + trap_LAN_GetServerAddressString(ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, + sizeof(uiInfo.serverStatusAddress)); + UI_BuildServerStatus(qtrue); + } + else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) + { + Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], + sizeof(uiInfo.serverStatusAddress)); + UI_BuildServerStatus(qtrue); + Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); + } + else if (Q_stricmp(name, "FindPlayer") == 0) + { + UI_BuildFindPlayerList(qtrue); + // clear the displayed server status info + uiInfo.serverStatusInfo.numLines = 0; + Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); + } + else if (Q_stricmp(name, "JoinServer") == 0) + { + if (uiInfo.serverStatus.currentServer >= 0 && + uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) + { + trap_LAN_GetServerAddressString(ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024); + trap_Cmd_ExecuteText(EXEC_APPEND, va("connect %s\n", buff)); + } + } + else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) + { + if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) + { + trap_Cmd_ExecuteText(EXEC_APPEND, + va("connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer])); + } + } + else if (Q_stricmp(name, "Quit") == 0) + trap_Cmd_ExecuteText(EXEC_APPEND, "quit\n"); + else if (Q_stricmp(name, "Leave") == 0) + { + trap_Cmd_ExecuteText(EXEC_APPEND, "disconnect\n"); + trap_Key_SetCatcher(KEYCATCH_UI); + Menus_CloseAll(); + Menus_ActivateByName("main"); + } + else if (Q_stricmp(name, "ServerSort") == 0) + { + int sortColumn; - Com_sprintf( test, sizeof( test ), "models/players/%s/%s/lower_default.skin", base, team ); + if (Int_Parse(args, &sortColumn)) + { + // if same column we're already sorting on then flip the direction - if (trap_FS_FOpenFile(test, NULL, FS_READ)) { - return qtrue; - } - Com_sprintf( test, sizeof( test ), "models/players/characters/%s/%s/lower_default.skin", base, team ); + if (sortColumn == uiInfo.serverStatus.sortKey) + uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir; - if (trap_FS_FOpenFile(test, NULL, FS_READ)) { - return qtrue; - } - return qfalse; -} + // make sure we sort again + UI_ServersSort(sortColumn, qtrue); -/* -================== -UI_MapCountByTeam -================== -*/ -static int UI_HeadCountByTeam( void ) { - static int init = 0; - int i, j, k, c, tIndex; - - c = 0; - if (!init) { - for (i = 0; i < uiInfo.characterCount; i++) { - uiInfo.characterList[i].reference = 0; - for (j = 0; j < uiInfo.teamCount; j++) { - if (UI_hasSkinForBase(uiInfo.characterList[i].base, uiInfo.teamList[j].teamName)) { - uiInfo.characterList[i].reference |= (1<= 0 && ui_selectedMap.integer < uiInfo.mapCount) + { + trap_Cmd_ExecuteText( + EXEC_APPEND, va("callvote map \"%s\"\n", uiInfo.mapList[ui_selectedMap.integer].mapLoadName)); + } + } + else if (Q_stricmp(name, "voteNextMap") == 0) + { + if (ui_selectedMap.integer >= 0 && ui_selectedMap.integer < uiInfo.mapCount) + { + trap_Cmd_ExecuteText( + EXEC_APPEND, va("callvote nextmap \"%s\"\n", uiInfo.mapList[ui_selectedMap.integer].mapLoadName)); + } + } + else if (Q_stricmp(name, "voteKick") == 0) + { + if (uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount) + { + char buffer[MAX_CVAR_VALUE_STRING]; + trap_Cvar_VariableStringBuffer("ui_reason", buffer, sizeof(buffer)); - tIndex = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + trap_Cmd_ExecuteText(EXEC_APPEND, va("callvote kick %d%s\n", uiInfo.clientNums[uiInfo.playerIndex], + (buffer[0] ? va(" \"%s\"", buffer) : ""))); + trap_Cvar_Set("ui_reason", ""); + } + } + else if (Q_stricmp(name, "voteMute") == 0) + { + if (uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount) + { + char buffer[MAX_CVAR_VALUE_STRING]; + trap_Cvar_VariableStringBuffer("ui_reason", buffer, sizeof(buffer)); - // do names - for (i = 0; i < uiInfo.characterCount; i++) { - uiInfo.characterList[i].active = qfalse; - for(j = 0; j < TEAM_MEMBERS; j++) { - if (uiInfo.teamList[tIndex].teamMembers[j] != NULL) { - if (uiInfo.characterList[i].reference&(1<= 0 && uiInfo.playerIndex < uiInfo.playerCount) + { + trap_Cmd_ExecuteText(EXEC_APPEND, va("callvote unmute %d\n", uiInfo.clientNums[uiInfo.playerIndex])); } - } } - } - } - } - return c; -} + else if (Q_stricmp(name, "voteTeamKick") == 0) + { + if (uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount) + { + char buffer[MAX_CVAR_VALUE_STRING]; + trap_Cvar_VariableStringBuffer("ui_reason", buffer, sizeof(buffer)); -/* -================== -UI_InsertServerIntoDisplayList -================== -*/ -static void UI_InsertServerIntoDisplayList(int num, int position) { - int i; - - if (position < 0 || position > uiInfo.serverStatus.numDisplayServers ) { - return; - } - // - uiInfo.serverStatus.numDisplayServers++; - for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) { - uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1]; - } - uiInfo.serverStatus.displayServers[position] = num; -} + trap_Cmd_ExecuteText( + EXEC_APPEND, va("callteamvote kick %d%s\n", uiInfo.teamClientNums[uiInfo.teamPlayerIndex], + (buffer[0] ? va(" \"%s\"", buffer) : ""))); + trap_Cvar_Set("ui_reason", ""); + } + } + else if (Q_stricmp(name, "voteTeamDenyBuild") == 0) + { + if (uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount) + { + char buffer[MAX_CVAR_VALUE_STRING]; + trap_Cvar_VariableStringBuffer("ui_reason", buffer, sizeof(buffer)); -/* -================== -UI_RemoveServerFromDisplayList -================== -*/ -static void UI_RemoveServerFromDisplayList(int num) { - int i, j; - - for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) { - if (uiInfo.serverStatus.displayServers[i] == num) { - uiInfo.serverStatus.numDisplayServers--; - for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) { - uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1]; - } - return; + trap_Cmd_ExecuteText( + EXEC_APPEND, va("callteamvote denybuild %d%s\n", uiInfo.teamClientNums[uiInfo.teamPlayerIndex], + (buffer[0] ? va(" \"%s\"", buffer) : ""))); + trap_Cvar_Set("ui_reason", ""); + } + } + else if (Q_stricmp(name, "voteTeamAllowBuild") == 0) + { + if (uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount) + { + trap_Cmd_ExecuteText( + EXEC_APPEND, va("callteamvote allowbuild %d\n", uiInfo.teamClientNums[uiInfo.teamPlayerIndex])); + } + } + else if (Q_stricmp(name, "addFavorite") == 0) + { + if (ui_netSource.integer != AS_FAVORITES) + { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + trap_LAN_GetServerInfo(ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); + name[0] = addr[0] = '\0'; + Q_strncpyz(name, Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH); + Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); + + if (strlen(name) > 0 && strlen(addr) > 0) + { + res = trap_LAN_AddServer(AS_FAVORITES, name, addr); + + if (res == 0) + { + // server already in the list + Com_Printf("Favorite already in list\n"); + } + else if (res == -1) + { + // list full + Com_Printf("Favorite list full\n"); + } + else + { + // successfully added + Com_Printf("Added favorite server %s\n", addr); + } + } + } + } + else if (Q_stricmp(name, "deleteFavorite") == 0) + { + if (ui_netSource.integer == AS_FAVORITES) + { + char addr[MAX_NAME_LENGTH]; + trap_LAN_GetServerInfo(ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); + addr[0] = '\0'; + Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); + + if (strlen(addr) > 0) + trap_LAN_RemoveServer(AS_FAVORITES, addr); + } + } + else if (Q_stricmp(name, "createFavorite") == 0) + { + if (ui_netSource.integer == AS_FAVORITES) + { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + name[0] = addr[0] = '\0'; + Q_strncpyz(name, UI_Cvar_VariableString("ui_favoriteName"), MAX_NAME_LENGTH); + Q_strncpyz(addr, UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH); + + if (strlen(name) > 0 && strlen(addr) > 0) + { + res = trap_LAN_AddServer(AS_FAVORITES, name, addr); + + if (res == 0) + { + // server already in the list + Com_Printf("Favorite already in list\n"); + } + else if (res == -1) + { + // list full + Com_Printf("Favorite list full\n"); + } + else + { + // successfully added + Com_Printf("Added favorite server %s\n", addr); + } + } + } + } + else if (Q_stricmp(name, "glCustom") == 0) + trap_Cvar_Set("ui_glCustom", "4"); + else if (Q_stricmp(name, "update") == 0) + { + if (String_Parse(args, &name2)) + UI_Update(name2); + } + else if (Q_stricmp(name, "InitIgnoreList") == 0) + UI_BuildPlayerList(); + else if (Q_stricmp(name, "ToggleIgnore") == 0) + { + if (uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount) + { + if (Com_ClientListContains( + &uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex])) + { + Com_ClientListRemove( + &uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex]); + trap_Cmd_ExecuteText(EXEC_APPEND, va("unignore %i\n", uiInfo.clientNums[uiInfo.ignoreIndex])); + } + else + { + Com_ClientListAdd(&uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex]); + trap_Cmd_ExecuteText(EXEC_APPEND, va("ignore %i\n", uiInfo.clientNums[uiInfo.ignoreIndex])); + } + } + } + else if (Q_stricmp(name, "IgnorePlayer") == 0) + { + if (uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount) + { + if (!Com_ClientListContains( + &uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex])) + { + Com_ClientListAdd(&uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex]); + trap_Cmd_ExecuteText(EXEC_APPEND, va("ignore %i\n", uiInfo.clientNums[uiInfo.ignoreIndex])); + } + } + } + else if (Q_stricmp(name, "UnIgnorePlayer") == 0) + { + if (uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount) + { + if (Com_ClientListContains( + &uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex])) + { + Com_ClientListRemove( + &uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[uiInfo.ignoreIndex]); + trap_Cmd_ExecuteText(EXEC_APPEND, va("unignore %i\n", uiInfo.clientNums[uiInfo.ignoreIndex])); + } + } + } + else + Com_Printf("unknown UI script %s\n", name); } - } } -/* -================== -UI_BinaryServerInsertion -================== -*/ -static void UI_BinaryServerInsertion(int num) { - int mid, offset, res, len; - - // use binary search to insert server - len = uiInfo.serverStatus.numDisplayServers; - mid = len; - offset = 0; - res = 0; - while(mid > 0) { - mid = len >> 1; - // - res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, - uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid]); - // if equal - if (res == 0) { - UI_InsertServerIntoDisplayList(num, offset+mid); - return; - } - // if larger - else if (res == 1) { - offset += mid; - len -= mid; - } - // if smaller - else { - len -= mid; - } - } - if (res == 1) { - offset++; - } - UI_InsertServerIntoDisplayList(num, offset); -} +static int UI_FeederInitialise(int feederID); /* ================== -UI_BuildServerDisplayList +UI_FeederCount ================== */ -static void UI_BuildServerDisplayList(int force) { - int i, count, clients, maxClients, ping, game, len, visible; - char info[MAX_STRING_CHARS]; -// qboolean startRefresh = qtrue; TTimo: unused - static int numinvisible; - - if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) { - return; - } - // if we shouldn't reset - if ( force == 2 ) { - force = 0; - } - - // do motd updates here too - trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd) ); - len = strlen(uiInfo.serverStatus.motd); - if (len != uiInfo.serverStatus.motdLen) { - uiInfo.serverStatus.motdLen = len; - uiInfo.serverStatus.motdWidth = -1; - } - - if (force) { - numinvisible = 0; - // clear number of displayed servers - uiInfo.serverStatus.numDisplayServers = 0; - uiInfo.serverStatus.numPlayersOnServers = 0; - // set list box index to zero - Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL); - // mark all servers as visible so we store ping updates for them - trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); - } +static int UI_FeederCount(int feederID) +{ + if (feederID == FEEDER_CINEMATICS) + return uiInfo.movieCount; + else if (feederID == FEEDER_MAPS) + return uiInfo.mapCount; + else if (feederID == FEEDER_SERVERS) + return uiInfo.serverStatus.numDisplayServers; + else if (feederID == FEEDER_SERVERSTATUS) + return uiInfo.serverStatusInfo.numLines; + else if (feederID == FEEDER_NEWS) + return uiInfo.newsInfo.numLines; + else if (feederID == FEEDER_GITHUB_RELEASE) + return uiInfo.githubRelease.numLines; + else if (feederID == FEEDER_FINDPLAYER) + return uiInfo.numFoundPlayerServers; + else if (feederID == FEEDER_PLAYER_LIST) + { + if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) + { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } - // get the server count (comes from the master) - count = trap_LAN_GetServerCount(ui_netSource.integer); - if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0) ) { - // still waiting on a response from the master - uiInfo.serverStatus.numDisplayServers = 0; - uiInfo.serverStatus.numPlayersOnServers = 0; - uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500; - return; - } + return uiInfo.playerCount; + } + else if (feederID == FEEDER_TEAM_LIST) + { + if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) + { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } - visible = qfalse; - for (i = 0; i < count; i++) { - // if we already got info for this server - if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) { - continue; + return uiInfo.myTeamCount; + } + else if (feederID == FEEDER_IGNORE_LIST) + return uiInfo.playerCount; + else if (feederID == FEEDER_HELP_LIST) + return uiInfo.helpCount; + else if (feederID == FEEDER_MODS) + return uiInfo.modCount; + else if (feederID == FEEDER_DEMOS) + return uiInfo.demoCount; + else if (feederID == FEEDER_TREMTEAMS) + return uiInfo.teamCount; + else if (feederID == FEEDER_TREMHUMANITEMS) + return uiInfo.humanItemCount; + else if (feederID == FEEDER_TREMALIENCLASSES) + return uiInfo.alienClassCount; + else if (feederID == FEEDER_TREMHUMANARMOURYBUY) + return uiInfo.humanArmouryBuyCount; + else if (feederID == FEEDER_TREMHUMANARMOURYSELL) + return uiInfo.humanArmourySellCount; + else if (feederID == FEEDER_TREMALIENUPGRADE) + return uiInfo.alienUpgradeCount; + else if (feederID == FEEDER_TREMALIENBUILD) + return uiInfo.alienBuildCount; + else if (feederID == FEEDER_TREMHUMANBUILD) + return uiInfo.humanBuildCount; + else if (feederID == FEEDER_RESOLUTIONS) + { + if (UI_FeederInitialise(feederID) == uiInfo.numResolutions) + return uiInfo.numResolutions + 1; + else + return uiInfo.numResolutions; } - visible = qtrue; - // get the ping for this server - ping = trap_LAN_GetServerPing(ui_netSource.integer, i); - if (ping > 0 || ui_netSource.integer == AS_FAVORITES) { + else if (feederID == FEEDER_TREMVOICECMD) + return uiInfo.voiceCmdCount; - trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS); + return 0; +} - clients = atoi(Info_ValueForKey(info, "clients")); - uiInfo.serverStatus.numPlayersOnServers += clients; +static const char *UI_SelectedMap(int index, int *actual) +{ + int i, c; + c = 0; + *actual = 0; - if (ui_browserShowEmpty.integer == 0) { - if (clients == 0) { - trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); - continue; + for (i = 0; i < uiInfo.mapCount; i++) + { + if (c == index) + { + *actual = i; + return uiInfo.mapList[i].mapName; } - } + else + c++; + } - if (ui_browserShowFull.integer == 0) { - maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); - if (clients == maxClients) { - trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); - continue; - } - } + return ""; +} - if (uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1) { - game = atoi(Info_ValueForKey(info, "gametype")); - if (game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) { - trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); - continue; - } - } +static int GCD(int a, int b) +{ + int c; - // make sure we never add a favorite server twice - if (ui_netSource.integer == AS_FAVORITES) { - UI_RemoveServerFromDisplayList(i); - } - // insert the server into the list - UI_BinaryServerInsertion(i); - // done with this server - if (ping > 0) { - trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); - numinvisible++; - } + while (b != 0) + { + c = a % b; + a = b; + b = c; } - } - - uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime; - // if there were no servers visible for ping updates - if (!visible) { -// UI_StopServerRefresh(); -// uiInfo.serverStatus.nextDisplayRefresh = 0; - } + return a; } -typedef struct +static const char *UI_DisplayAspectString(int w, int h) { - char *name, *altName; -} serverStatusCvar_t; - -serverStatusCvar_t serverStatusCvars[] = { - {"sv_hostname", "Name"}, - {"Address", ""}, - {"gamename", "Game name"}, - {"g_gametype", "Game type"}, - {"mapname", "Map"}, - {"version", ""}, - {"protocol", ""}, - {"timelimit", ""}, - {"fraglimit", ""}, - {NULL, NULL} -}; + int gcd = GCD(w, h); -/* -================== -UI_SortServerStatusInfo -================== -*/ -static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) { - int i, j, index; - char *tmp1, *tmp2; - - // FIXME: if "gamename" == "baseq3" or "missionpack" then - // replace the gametype number by FFA, CTF etc. - // - index = 0; - for (i = 0; serverStatusCvars[i].name; i++) { - for (j = 0; j < info->numLines; j++) { - if ( !info->lines[j][1] || info->lines[j][1][0] ) { - continue; - } - if ( !Q_stricmp(serverStatusCvars[i].name, info->lines[j][0]) ) { - // swap lines - tmp1 = info->lines[index][0]; - tmp2 = info->lines[index][3]; - info->lines[index][0] = info->lines[j][0]; - info->lines[index][3] = info->lines[j][3]; - info->lines[j][0] = tmp1; - info->lines[j][3] = tmp2; - // - if ( strlen(serverStatusCvars[i].altName) ) { - info->lines[index][0] = serverStatusCvars[i].altName; - } - index++; - } + w /= gcd; + h /= gcd; + + // For some reason 8:5 is usually referred to as 16:10 + if (w == 8 && h == 5) + { + w = 16; + h = 10; } - } + + return va("%d:%d", w, h); } -/* -================== -UI_GetServerStatusInfo -================== -*/ -static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) { - char *p, *score, *ping, *name; - int i, len; +static const char *UI_FeederItemText(int feederID, int index, int column, qhandle_t *handle) +{ + if (handle) + *handle = -1; - if (!info) { - trap_LAN_ServerStatus( serverAddress, NULL, 0); - return qfalse; - } - memset(info, 0, sizeof(*info)); - if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) { - Q_strncpyz(info->address, serverAddress, sizeof(info->address)); - p = info->text; - info->numLines = 0; - info->lines[info->numLines][0] = "Address"; - info->lines[info->numLines][1] = ""; - info->lines[info->numLines][2] = ""; - info->lines[info->numLines][3] = info->address; - info->numLines++; - // get the cvars - while (p && *p) { - p = strchr(p, '\\'); - if (!p) break; - *p++ = '\0'; - if (*p == '\\') - break; - info->lines[info->numLines][0] = p; - info->lines[info->numLines][1] = ""; - info->lines[info->numLines][2] = ""; - p = strchr(p, '\\'); - if (!p) break; - *p++ = '\0'; - info->lines[info->numLines][3] = p; - - info->numLines++; - if (info->numLines >= MAX_SERVERSTATUS_LINES) - break; - } - // get the player list - if (info->numLines < MAX_SERVERSTATUS_LINES-3) { - // empty line - info->lines[info->numLines][0] = ""; - info->lines[info->numLines][1] = ""; - info->lines[info->numLines][2] = ""; - info->lines[info->numLines][3] = ""; - info->numLines++; - // header - info->lines[info->numLines][0] = "num"; - info->lines[info->numLines][1] = "score"; - info->lines[info->numLines][2] = "ping"; - info->lines[info->numLines][3] = "name"; - info->numLines++; - // parse players - i = 0; - len = 0; - while (p && *p) { - if (*p == '\\') - *p++ = '\0'; - if (!p) - break; - score = p; - p = strchr(p, ' '); - if (!p) - break; - *p++ = '\0'; - ping = p; - p = strchr(p, ' '); - if (!p) - break; - *p++ = '\0'; - name = p; - Com_sprintf(&info->pings[len], sizeof(info->pings)-len, "%d", i); - info->lines[info->numLines][0] = &info->pings[len]; - len += strlen(&info->pings[len]) + 1; - info->lines[info->numLines][1] = score; - info->lines[info->numLines][2] = ping; - info->lines[info->numLines][3] = name; - info->numLines++; - if (info->numLines >= MAX_SERVERSTATUS_LINES) - break; - p = strchr(p, '\\'); - if (!p) - break; - *p++ = '\0'; - // - i++; - } + if (feederID == FEEDER_MAPS) + { + int actual; + return UI_SelectedMap(index, &actual); } - UI_SortServerStatusInfo( info ); - return qtrue; - } - return qfalse; -} + else if (feederID == FEEDER_SERVERS) + { + if (index >= 0 && index < UI_FeederCount(feederID)) + { + static char info[MAX_STRING_CHARS]; + static char clientBuff[32]; + static char cleaned[MAX_STRING_CHARS]; + static int lastColumn = -1; + static int lastTime = 0; + int ping; + + if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) + { + trap_LAN_GetServerInfo( + ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); + lastColumn = column; + lastTime = uiInfo.uiDC.realTime; + } -/* -================== -stristr -================== -*/ -static char *stristr(char *str, char *charset) { - int i; + ping = atoi(Info_ValueForKey(info, "ping")); - while(*str) { - for (i = 0; charset[i] && str[i]; i++) { - if (toupper(charset[i]) != toupper(str[i])) break; - } - if (!charset[i]) return str; - str++; - } - return NULL; -} + UI_EscapeEmoticons(cleaned, Info_ValueForKey(info, "hostname"), sizeof(cleaned)); -/* -================== -UI_BuildFindPlayerList -================== -*/ -static void UI_BuildFindPlayerList(qboolean force) { - static int numFound, numTimeOuts; - int i, j, resend; - serverStatusInfo_t info; - char name[MAX_NAME_LENGTH+2]; - char infoString[MAX_STRING_CHARS]; - - if (!force) { - if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) { - return; - } - } - else { - memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus)); - uiInfo.numFoundPlayerServers = 0; - uiInfo.currentFoundPlayerServer = 0; - trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName)); - Q_CleanStr(uiInfo.findPlayerName); - // should have a string of some length - if (!strlen(uiInfo.findPlayerName)) { - uiInfo.nextFindPlayerRefresh = 0; - return; - } - // set resend time - resend = ui_serverStatusTimeOut.integer / 2 - 10; - if (resend < 50) { - resend = 50; - } - trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend)); - // reset all server status requests - trap_LAN_ServerStatus( NULL, NULL, 0); - // - uiInfo.numFoundPlayerServers = 1; - Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], - sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), - "searching %d...", uiInfo.pendingServerStatus.num); - numFound = 0; - numTimeOuts++; - } - for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { - // if this pending server is valid - if (uiInfo.pendingServerStatus.server[i].valid) { - // try to get the server status for this server - if (UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) { - // - numFound++; - // parse through the server status lines - for (j = 0; j < info.numLines; j++) { - // should have ping info - if ( !info.lines[j][2] || !info.lines[j][2][0] ) { - continue; - } - // clean string first - Q_strncpyz(name, info.lines[j][3], sizeof(name)); - Q_CleanStr(name); - // if the player name is a substring - if (stristr(name, uiInfo.findPlayerName)) { - // add to found server list if we have space (always leave space for a line with the number found) - if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS-1) { - // - Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1], - uiInfo.pendingServerStatus.server[i].adrstr, - sizeof(uiInfo.foundPlayerServerAddresses[0])); - Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], - uiInfo.pendingServerStatus.server[i].name, - sizeof(uiInfo.foundPlayerServerNames[0])); - uiInfo.numFoundPlayerServers++; + switch (column) + { + case SORT_HOST: + if (ping <= 0) + return Info_ValueForKey(info, "addr"); + else + { + static char hostname[1024]; + + if (ui_netSource.integer == AS_LOCAL) + { + Com_sprintf(hostname, sizeof(hostname), "%s [%s]", cleaned, + netnames[atoi(Info_ValueForKey(info, "nettype"))]); + return hostname; + } + else + { + char *text; + char *label; + + label = Info_ValueForKey(info, "label"); + if (label[0]) + { + // First char of the label response is a sorting tag. Skip it. + label += 1; + + Com_sprintf(hostname, sizeof(hostname), "%s %s", label, cleaned); + } + else + { + Com_sprintf(hostname, sizeof(hostname), "%s", cleaned); + } + + // Strip leading whitespace + text = hostname; + + while (*text != '\0' && *text == ' ') + text++; + + return text; + } + } + + case SORT_GAME: + return Info_ValueForKey(info, "game"); + + case SORT_MAP: + return Info_ValueForKey(info, "mapname"); + + case SORT_CLIENTS: + Com_sprintf(clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), + Info_ValueForKey(info, "sv_maxclients")); + return clientBuff; + + case SORT_PING: + if (ping <= 0) + return "..."; + else + return Info_ValueForKey(info, "ping"); } - else { - // can't add any more so we're done - uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers; + } + } + else if (feederID == FEEDER_SERVERSTATUS) + { + if (index >= 0 && index < uiInfo.serverStatusInfo.numLines) + { + if (column >= 0 && column < 4) + return uiInfo.serverStatusInfo.lines[index][column]; + } + } + else if (feederID == FEEDER_NEWS) + { + if (index >= 0 && index < uiInfo.newsInfo.numLines) + return uiInfo.newsInfo.text[index]; + } + else if (feederID == FEEDER_GITHUB_RELEASE) + { + if (index >= 0 && index < uiInfo.githubRelease.numLines) + return uiInfo.githubRelease.text[index]; + } + else if (feederID == FEEDER_FINDPLAYER) + { + if (index >= 0 && index < uiInfo.numFoundPlayerServers) + return uiInfo.foundPlayerServerNames[index]; + } + else if (feederID == FEEDER_PLAYER_LIST) + { + if (index >= 0 && index < uiInfo.playerCount) + return uiInfo.playerNames[index]; + } + else if (feederID == FEEDER_TEAM_LIST) + { + if (index >= 0 && index < uiInfo.myTeamCount) + return uiInfo.teamNames[index]; + } + else if (feederID == FEEDER_IGNORE_LIST) + { + if (index >= 0 && index < uiInfo.playerCount) + { + switch (column) + { + case 1: + // am I ignoring him + return Com_ClientListContains(&uiInfo.ignoreList[uiInfo.myPlayerIndex], uiInfo.clientNums[index]) + ? "X" + : ""; + + case 2: + // is he ignoring me + return Com_ClientListContains(&uiInfo.ignoreList[index], uiInfo.playerNumber) ? "X" : ""; + + default: + return uiInfo.playerNames[index]; } - } } - Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], - sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), - "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound); - // retrieved the server status so reuse this spot - uiInfo.pendingServerStatus.server[i].valid = qfalse; - } } - // if empty pending slot or timed out - if (!uiInfo.pendingServerStatus.server[i].valid || - uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) { - if (uiInfo.pendingServerStatus.server[i].valid) { - numTimeOuts++; - } - // reset server status request for this address - UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL ); - // reuse pending slot - uiInfo.pendingServerStatus.server[i].valid = qfalse; - // if we didn't try to get the status of all servers in the main browser yet - if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) { - uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime; - trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], - uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr)); - trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString)); - Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), sizeof(uiInfo.pendingServerStatus.server[0].name)); - uiInfo.pendingServerStatus.server[i].valid = qtrue; - uiInfo.pendingServerStatus.num++; - Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], - sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), - "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound); - } + else if (feederID == FEEDER_HELP_LIST) + { + if (index >= 0 && index < uiInfo.helpCount) + return uiInfo.helpList[index].text; } - } - for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { - if (uiInfo.pendingServerStatus.server[i].valid) { - break; - } - } - // if still trying to retrieve server status info - if (i < MAX_SERVERSTATUSREQUESTS) { - uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25; - } - else { - // add a line that shows the number of servers found - if (!uiInfo.numFoundPlayerServers) { - Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found"); - } - else { - Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), - "%d server%s found with player %s", uiInfo.numFoundPlayerServers-1, - uiInfo.numFoundPlayerServers == 2 ? "":"s", uiInfo.findPlayerName); - } - uiInfo.nextFindPlayerRefresh = 0; - // show the server status info for the selected server - UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer); - } -} + else if (feederID == FEEDER_MODS) + { + if (index >= 0 && index < uiInfo.modCount) + { + if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) + return uiInfo.modList[index].modDescr; + else + return uiInfo.modList[index].modName; + } + } + else if (feederID == FEEDER_CINEMATICS) + { + if (index >= 0 && index < uiInfo.movieCount) + return uiInfo.movieList[index]; + } + else if (feederID == FEEDER_DEMOS) + { + if (index >= 0 && index < uiInfo.demoCount) + return uiInfo.demoList[index]; + } + else if (feederID == FEEDER_TREMTEAMS) + { + if (index >= 0 && index < uiInfo.teamCount) + return uiInfo.teamList[index].text; + } + else if (feederID == FEEDER_TREMHUMANITEMS) + { + if (index >= 0 && index < uiInfo.humanItemCount) + return uiInfo.humanItemList[index].text; + } + else if (feederID == FEEDER_TREMALIENCLASSES) + { + if (index >= 0 && index < uiInfo.alienClassCount) + return uiInfo.alienClassList[index].text; + } + else if (feederID == FEEDER_TREMHUMANARMOURYBUY) + { + if (index >= 0 && index < uiInfo.humanArmouryBuyCount) + return uiInfo.humanArmouryBuyList[index].text; + } + else if (feederID == FEEDER_TREMHUMANARMOURYSELL) + { + if (index >= 0 && index < uiInfo.humanArmourySellCount) + return uiInfo.humanArmourySellList[index].text; + } + else if (feederID == FEEDER_TREMALIENUPGRADE) + { + if (index >= 0 && index < uiInfo.alienUpgradeCount) + return uiInfo.alienUpgradeList[index].text; + } + else if (feederID == FEEDER_TREMALIENBUILD) + { + if (index >= 0 && index < uiInfo.alienBuildCount) + return uiInfo.alienBuildList[index].text; + } + else if (feederID == FEEDER_TREMHUMANBUILD) + { + if (index >= 0 && index < uiInfo.humanBuildCount) + return uiInfo.humanBuildList[index].text; + } + else if (feederID == FEEDER_RESOLUTIONS) + { + static char resolution[MAX_STRING_CHARS]; + int w, h; -/* -================== -UI_BuildServerStatus -================== -*/ -static void UI_BuildServerStatus(qboolean force) { - - if (uiInfo.nextFindPlayerRefresh) { - return; - } - if (!force) { - if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) { - return; - } - } - else { - Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); - uiInfo.serverStatusInfo.numLines = 0; - // reset all server status requests - trap_LAN_ServerStatus( NULL, NULL, 0); - } - if (uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0) { - return; - } - if (UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) { - uiInfo.nextServerStatusRefresh = 0; - UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL ); - } - else { - uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500; - } -} + if (index >= 0 && index < uiInfo.numResolutions) + { + w = uiInfo.resolutions[index].w; + h = uiInfo.resolutions[index].h; -/* -================== -UI_FeederCount -================== -*/ -static int UI_FeederCount(float feederID) { - - if (feederID == FEEDER_HEADS) { - return UI_HeadCountByTeam(); - } else if (feederID == FEEDER_Q3HEADS) { - return uiInfo.q3HeadCount; - } else if (feederID == FEEDER_CINEMATICS) { - return uiInfo.movieCount; - } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { - return UI_MapCountByGameType(feederID == FEEDER_MAPS ? qtrue : qfalse); - } else if (feederID == FEEDER_SERVERS) { - return uiInfo.serverStatus.numDisplayServers; - } else if (feederID == FEEDER_SERVERSTATUS) { - return uiInfo.serverStatusInfo.numLines; - } else if (feederID == FEEDER_FINDPLAYER) { - return uiInfo.numFoundPlayerServers; - } else if (feederID == FEEDER_PLAYER_LIST) { - if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { - uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; - UI_BuildPlayerList(); - } - return uiInfo.playerCount; - } else if (feederID == FEEDER_TEAM_LIST) { - if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { - uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; - UI_BuildPlayerList(); - } - return uiInfo.myTeamCount; - } else if (feederID == FEEDER_IGNORE_LIST) { - return uiInfo.playerCount; - } else if (feederID == FEEDER_MODS) { - return uiInfo.modCount; - } else if (feederID == FEEDER_DEMOS) { - return uiInfo.demoCount; - } - -//TA: tremulous menus - else if( feederID == FEEDER_TREMTEAMS ) - return uiInfo.tremTeamCount; - else if( feederID == FEEDER_TREMHUMANITEMS ) - return uiInfo.tremHumanItemCount; - else if( feederID == FEEDER_TREMALIENCLASSES ) - return uiInfo.tremAlienClassCount; - else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) - return uiInfo.tremHumanArmouryBuyCount; - else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) - return uiInfo.tremHumanArmourySellCount; - else if( feederID == FEEDER_TREMALIENUPGRADE ) - return uiInfo.tremAlienUpgradeCount; - else if( feederID == FEEDER_TREMALIENBUILD ) - return uiInfo.tremAlienBuildCount; - else if( feederID == FEEDER_TREMHUMANBUILD ) - return uiInfo.tremHumanBuildCount; -//TA: tremulous menus - - return 0; -} + Com_sprintf(resolution, sizeof(resolution), "%dx%d (%s)", w, h, UI_DisplayAspectString(w, h)); -static const char *UI_SelectedMap(int index, int *actual) { - int i, c; - c = 0; - *actual = 0; - for (i = 0; i < uiInfo.mapCount; i++) { - if (uiInfo.mapList[i].active) { - if (c == index) { - *actual = i; - return uiInfo.mapList[i].mapName; - } else { - c++; - } - } - } - return ""; -} + return resolution; + } -static const char *UI_SelectedHead(int index, int *actual) { - int i, c; - c = 0; - *actual = 0; - for (i = 0; i < uiInfo.characterCount; i++) { - if (uiInfo.characterList[i].active) { - if (c == index) { - *actual = i; - return uiInfo.characterList[i].name; - } else { - c++; - } - } - } - return ""; -} + w = (int)trap_Cvar_VariableValue("r_width"); + h = (int)trap_Cvar_VariableValue("r_height"); + Com_sprintf(resolution, sizeof(resolution), "Custom (%dx%d)", w, h); -static int UI_GetIndexFromSelection(int actual) { - int i, c; - c = 0; - for (i = 0; i < uiInfo.mapCount; i++) { - if (uiInfo.mapList[i].active) { - if (i == actual) { - return c; - } - c++; + return resolution; } - } - return 0; + else if (feederID == FEEDER_TREMVOICECMD) + { + if (index >= 0 && index < uiInfo.voiceCmdCount) + return uiInfo.voiceCmdList[index].text; + } + + return ""; } -static void UI_UpdatePendingPings( void ) { - trap_LAN_ResetPings(ui_netSource.integer); - uiInfo.serverStatus.refreshActive = qtrue; - uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; +static qhandle_t UI_FeederItemImage(int feederID, int index) +{ + if (feederID == FEEDER_MAPS) + { + int actual; + UI_SelectedMap(index, &actual); + index = actual; + + if (index >= 0 && index < uiInfo.mapCount) + { + if (uiInfo.mapList[index].levelShot == -1) + uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName); + + return uiInfo.mapList[index].levelShot; + } + } + return 0; } -static const char *UI_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { - static char info[MAX_STRING_CHARS]; - static char hostname[1024]; - static char clientBuff[32]; - static int lastColumn = -1; - static int lastTime = 0; - *handle = -1; - if (feederID == FEEDER_HEADS) { - int actual; - return UI_SelectedHead(index, &actual); - } else if (feederID == FEEDER_Q3HEADS) { - if (index >= 0 && index < uiInfo.q3HeadCount) { - return uiInfo.q3HeadNames[index]; - } - } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { - int actual; - return UI_SelectedMap(index, &actual); - } else if (feederID == FEEDER_SERVERS) { - if (index >= 0 && index < uiInfo.serverStatus.numDisplayServers) { - int ping; - if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) { - trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); - lastColumn = column; - lastTime = uiInfo.uiDC.realTime; - } +static void UI_FeederSelection(int feederID, int index) +{ + static char info[MAX_STRING_CHARS]; - ping = atoi(Info_ValueForKey(info, "ping")); - if (ping == -1) { - // if we ever see a ping that is out of date, do a server refresh - // UI_UpdatePendingPings(); - } - switch (column) { - case SORT_HOST : - if (ping <= 0) { - return Info_ValueForKey(info, "addr"); - } else { - if ( ui_netSource.integer == AS_LOCAL ) { - Com_sprintf( hostname, sizeof(hostname), "%s [%s]", - Info_ValueForKey(info, "hostname"), - netnames[atoi(Info_ValueForKey(info, "nettype"))] ); - return hostname; - } - else - { - char *text; + if (feederID == FEEDER_MAPS) + { + int actual, map; + map = ui_selectedMap.integer; - Com_sprintf( hostname, sizeof(hostname), "%s", Info_ValueForKey(info, "hostname")); + if (uiInfo.mapList[map].cinematic >= 0) + { + trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic); + uiInfo.mapList[map].cinematic = -1; + } - // Strip leading whitespace - text = hostname; - while( *text != '\0' && *text == ' ' ) - text++; + UI_SelectedMap(index, &actual); - return text; - } - } - case SORT_MAP : - return Info_ValueForKey(info, "mapname"); - case SORT_CLIENTS : - Com_sprintf( clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), Info_ValueForKey(info, "sv_maxclients")); - return clientBuff; - case SORT_PING : - if (ping <= 0) { - return "..."; - } else { - return Info_ValueForKey(info, "ping"); - } - } + ui_selectedMap.integer = actual; + trap_Cvar_Set("ui_selectedMap", va("%d", actual)); + uiInfo.mapList[ui_selectedMap.integer].cinematic = trap_CIN_PlayCinematic( + va("%s.roq", uiInfo.mapList[ui_selectedMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent)); } - } else if (feederID == FEEDER_SERVERSTATUS) { - if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) { - if ( column >= 0 && column < 4 ) { - return uiInfo.serverStatusInfo.lines[index][column]; - } - } - } else if (feederID == FEEDER_FINDPLAYER) { - if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) { - //return uiInfo.foundPlayerServerAddresses[index]; - return uiInfo.foundPlayerServerNames[index]; - } - } else if (feederID == FEEDER_PLAYER_LIST) { - if (index >= 0 && index < uiInfo.playerCount) { - return uiInfo.playerNames[index]; + else if (feederID == FEEDER_SERVERS) + { + const char *mapName = NULL; + + uiInfo.serverStatus.currentServer = index; + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); + uiInfo.serverStatus.currentServerPreview = + trap_R_RegisterShaderNoMip(va("levelshots/%s", Info_ValueForKey(info, "mapname"))); + + if (uiInfo.serverStatus.currentServerCinematic >= 0) + { + trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); + uiInfo.serverStatus.currentServerCinematic = -1; + } + + mapName = Info_ValueForKey(info, "mapname"); + + if (mapName && *mapName) + { + uiInfo.serverStatus.currentServerCinematic = + trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent)); + } } - } else if (feederID == FEEDER_TEAM_LIST) { - if (index >= 0 && index < uiInfo.myTeamCount) { - return uiInfo.teamNames[index]; + else if (feederID == FEEDER_SERVERSTATUS) + { } - } else if (feederID == FEEDER_IGNORE_LIST) { - if (index >= 0 && index < uiInfo.playerCount) { - switch( column ) - { - case 1: - // am I ignoring him - return ( BG_ClientListTest(&uiInfo.ignoreList[ uiInfo.myPlayerIndex ], - uiInfo.clientNums[ index ] ) ) ? "X" : ""; - case 2: - // is he ignoring me - return ( BG_ClientListTest( &uiInfo.ignoreList[ index ], - uiInfo.playerNumber ) ) ? "X" : ""; - default: - return uiInfo.playerNames[index]; - } + else if (feederID == FEEDER_FINDPLAYER) + { + uiInfo.currentFoundPlayerServer = index; + // + + if (index < uiInfo.numFoundPlayerServers - 1) + { + // build a new server status for this server + Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], + sizeof(uiInfo.serverStatusAddress)); + Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); + UI_BuildServerStatus(qtrue); + } } - } else if (feederID == FEEDER_MODS) { - if (index >= 0 && index < uiInfo.modCount) { - if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) { - return uiInfo.modList[index].modDescr; - } else { - return uiInfo.modList[index].modName; - } + else if (feederID == FEEDER_PLAYER_LIST) + uiInfo.playerIndex = index; + else if (feederID == FEEDER_TEAM_LIST) + uiInfo.teamPlayerIndex = index; + else if (feederID == FEEDER_IGNORE_LIST) + uiInfo.ignoreIndex = index; + else if (feederID == FEEDER_HELP_LIST) + uiInfo.helpIndex = index; + else if (feederID == FEEDER_MODS) + uiInfo.modIndex = index; + else if (feederID == FEEDER_CINEMATICS) + { + uiInfo.movieIndex = index; + + if (uiInfo.previewMovie >= 0) + trap_CIN_StopCinematic(uiInfo.previewMovie); + + uiInfo.previewMovie = -1; + } + else if (feederID == FEEDER_DEMOS) + uiInfo.demoIndex = index; + else if (feederID == FEEDER_TREMTEAMS) + uiInfo.teamIndex = index; + else if (feederID == FEEDER_TREMHUMANITEMS) + uiInfo.humanItemIndex = index; + else if (feederID == FEEDER_TREMALIENCLASSES) + uiInfo.alienClassIndex = index; + else if (feederID == FEEDER_TREMHUMANARMOURYBUY) + uiInfo.humanArmouryBuyIndex = index; + else if (feederID == FEEDER_TREMHUMANARMOURYSELL) + uiInfo.humanArmourySellIndex = index; + else if (feederID == FEEDER_TREMALIENUPGRADE) + uiInfo.alienUpgradeIndex = index; + else if (feederID == FEEDER_TREMALIENBUILD) + uiInfo.alienBuildIndex = index; + else if (feederID == FEEDER_TREMHUMANBUILD) + uiInfo.humanBuildIndex = index; + else if (feederID == FEEDER_RESOLUTIONS) + { + if (index >= 0 && index < uiInfo.numResolutions) + { + trap_Cvar_Set("r_width", va("%d", uiInfo.resolutions[index].w)); + trap_Cvar_Set("r_height", va("%d", uiInfo.resolutions[index].h)); + } + + uiInfo.resolutionIndex = index; } - } else if (feederID == FEEDER_CINEMATICS) { - if (index >= 0 && index < uiInfo.movieCount) { - return uiInfo.movieList[index]; - } - } else if (feederID == FEEDER_DEMOS) { - if (index >= 0 && index < uiInfo.demoCount) { - return uiInfo.demoList[index]; - } - } - -//TA: tremulous menus - else if( feederID == FEEDER_TREMTEAMS ) - { - if( index >= 0 && index < uiInfo.tremTeamCount ) - return uiInfo.tremTeamList[ index ].text; - } - else if( feederID == FEEDER_TREMHUMANITEMS ) - { - if( index >= 0 && index < uiInfo.tremHumanItemCount ) - return uiInfo.tremHumanItemList[ index ].text; - } - else if( feederID == FEEDER_TREMALIENCLASSES ) - { - if( index >= 0 && index < uiInfo.tremAlienClassCount ) - return uiInfo.tremAlienClassList[ index ].text; - } - else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) - { - if( index >= 0 && index < uiInfo.tremHumanArmouryBuyCount ) - return uiInfo.tremHumanArmouryBuyList[ index ].text; - } - else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) - { - if( index >= 0 && index < uiInfo.tremHumanArmourySellCount ) - return uiInfo.tremHumanArmourySellList[ index ].text; - } - else if( feederID == FEEDER_TREMALIENUPGRADE ) - { - if( index >= 0 && index < uiInfo.tremAlienUpgradeCount ) - return uiInfo.tremAlienUpgradeList[ index ].text; - } - else if( feederID == FEEDER_TREMALIENBUILD ) - { - if( index >= 0 && index < uiInfo.tremAlienBuildCount ) - return uiInfo.tremAlienBuildList[ index ].text; - } - else if( feederID == FEEDER_TREMHUMANBUILD ) - { - if( index >= 0 && index < uiInfo.tremHumanBuildCount ) - return uiInfo.tremHumanBuildList[ index ].text; - } -//TA: tremulous menus - - return ""; + else if (feederID == FEEDER_TREMVOICECMD) + uiInfo.voiceCmdIndex = index; } +static int UI_FeederInitialise(int feederID) +{ + if (feederID == FEEDER_RESOLUTIONS) + { + int i; + int w = trap_Cvar_VariableValue("r_width"); + int h = trap_Cvar_VariableValue("r_height"); -static qhandle_t UI_FeederItemImage(float feederID, int index) { - if (feederID == FEEDER_HEADS) { - int actual; - UI_SelectedHead(index, &actual); - index = actual; - if (index >= 0 && index < uiInfo.characterCount) { - if (uiInfo.characterList[index].headImage == -1) { - uiInfo.characterList[index].headImage = trap_R_RegisterShaderNoMip(uiInfo.characterList[index].imageName); - } - return uiInfo.characterList[index].headImage; - } - } else if (feederID == FEEDER_Q3HEADS) { - if (index >= 0 && index < uiInfo.q3HeadCount) { - return uiInfo.q3HeadIcons[index]; - } - } else if (feederID == FEEDER_ALLMAPS || feederID == FEEDER_MAPS) { - int actual; - UI_SelectedMap(index, &actual); - index = actual; - if (index >= 0 && index < uiInfo.mapCount) { - if (uiInfo.mapList[index].levelShot == -1) { - uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName); - } - return uiInfo.mapList[index].levelShot; - } - } - return 0; -} + for (i = 0; i < uiInfo.numResolutions; i++) + { + if (w == uiInfo.resolutions[i].w && h == uiInfo.resolutions[i].h) + return i; + } -static void UI_FeederSelection(float feederID, int index) { - static char info[MAX_STRING_CHARS]; - if (feederID == FEEDER_HEADS) { - int actual; - UI_SelectedHead(index, &actual); - index = actual; - if (index >= 0 && index < uiInfo.characterCount) { - trap_Cvar_Set( "team_model", va("%s", uiInfo.characterList[index].base)); - trap_Cvar_Set( "team_headmodel", va("*%s", uiInfo.characterList[index].name)); - updateModel = qtrue; - } - } else if (feederID == FEEDER_Q3HEADS) { - if (index >= 0 && index < uiInfo.q3HeadCount) { - trap_Cvar_Set( "model", uiInfo.q3HeadNames[index]); - trap_Cvar_Set( "headmodel", uiInfo.q3HeadNames[index]); - updateModel = qtrue; - } - } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { - int actual, map; - map = (feederID == FEEDER_ALLMAPS) ? ui_currentNetMap.integer : ui_currentMap.integer; - if (uiInfo.mapList[map].cinematic >= 0) { - trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic); - uiInfo.mapList[map].cinematic = -1; - } - UI_SelectedMap(index, &actual); - trap_Cvar_Set("ui_mapIndex", va("%d", index)); - ui_mapIndex.integer = index; - - if (feederID == FEEDER_MAPS) { - ui_currentMap.integer = actual; - trap_Cvar_Set("ui_currentMap", va("%d", actual)); - uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); - UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); - trap_Cvar_Set("ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName); - updateOpponentModel = qtrue; - } else { - ui_currentNetMap.integer = actual; - trap_Cvar_Set("ui_currentNetMap", va("%d", actual)); - uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + return uiInfo.numResolutions; } - } else if (feederID == FEEDER_SERVERS) { - const char *mapName = NULL; - uiInfo.serverStatus.currentServer = index; - trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); - uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip(va("levelshots/%s", Info_ValueForKey(info, "mapname"))); - if (uiInfo.serverStatus.currentServerCinematic >= 0) { - trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); - uiInfo.serverStatus.currentServerCinematic = -1; - } - mapName = Info_ValueForKey(info, "mapname"); - if (mapName && *mapName) { - uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + return 0; +} + +static void UI_Pause(qboolean b) +{ + if (b) + { + // pause the game and set the ui keycatcher + trap_Cvar_Set("cl_paused", "1"); + trap_Key_SetCatcher(KEYCATCH_UI); } - } else if (feederID == FEEDER_SERVERSTATUS) { - // - } else if (feederID == FEEDER_FINDPLAYER) { - uiInfo.currentFoundPlayerServer = index; - // - if ( index < uiInfo.numFoundPlayerServers-1) { - // build a new server status for this server - Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress)); - Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); - UI_BuildServerStatus(qtrue); - } - } else if (feederID == FEEDER_PLAYER_LIST) { - uiInfo.playerIndex = index; - } else if (feederID == FEEDER_TEAM_LIST) { - uiInfo.teamIndex = index; - } else if (feederID == FEEDER_IGNORE_LIST) { - uiInfo.ignoreIndex = index; - } else if (feederID == FEEDER_MODS) { - uiInfo.modIndex = index; - } else if (feederID == FEEDER_CINEMATICS) { - uiInfo.movieIndex = index; - if (uiInfo.previewMovie >= 0) { - trap_CIN_StopCinematic(uiInfo.previewMovie); + else + { + // unpause the game and clear the ui keycatcher + trap_Key_SetCatcher(trap_Key_GetCatcher() & ~KEYCATCH_UI); + trap_Key_ClearStates(); + trap_Cvar_Set("cl_paused", "0"); } - uiInfo.previewMovie = -1; - } else if (feederID == FEEDER_DEMOS) { - uiInfo.demoIndex = index; - } - -//TA: tremulous menus - else if( feederID == FEEDER_TREMTEAMS ) - uiInfo.tremTeamIndex = index; - else if( feederID == FEEDER_TREMHUMANITEMS ) - uiInfo.tremHumanItemIndex = index; - else if( feederID == FEEDER_TREMALIENCLASSES ) - uiInfo.tremAlienClassIndex = index; - else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) - uiInfo.tremHumanArmouryBuyIndex = index; - else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) - uiInfo.tremHumanArmourySellIndex = index; - else if( feederID == FEEDER_TREMALIENUPGRADE ) - uiInfo.tremAlienUpgradeIndex = index; - else if( feederID == FEEDER_TREMALIENBUILD ) - uiInfo.tremAlienBuildIndex = index; - else if( feederID == FEEDER_TREMHUMANBUILD ) - uiInfo.tremHumanBuildIndex = index; -//TA: tremulous menus } -static void UI_Pause(qboolean b) { - if (b) { - // pause the game and set the ui keycatcher - trap_Cvar_Set( "cl_paused", "1" ); - trap_Key_SetCatcher( KEYCATCH_UI ); - } else { - // unpause the game and clear the ui keycatcher - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); - } +static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) +{ + return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent)); } -static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) { - return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent)); -} +static void UI_StopCinematic(int handle) +{ + if (handle >= 0) + trap_CIN_StopCinematic(handle); + else + { + handle = abs(handle); -static void UI_StopCinematic(int handle) { - if (handle >= 0) { - trap_CIN_StopCinematic(handle); - } else { - handle = abs(handle); - if (handle == UI_MAPCINEMATIC) { - if (uiInfo.mapList[ui_currentMap.integer].cinematic >= 0) { - trap_CIN_StopCinematic(uiInfo.mapList[ui_currentMap.integer].cinematic); - uiInfo.mapList[ui_currentMap.integer].cinematic = -1; - } - } else if (handle == UI_NETMAPCINEMATIC) { - if (uiInfo.serverStatus.currentServerCinematic >= 0) { - trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); - uiInfo.serverStatus.currentServerCinematic = -1; - } - } else if (handle == UI_CLANCINEMATIC) { - int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); - if (i >= 0 && i < uiInfo.teamCount) { - if (uiInfo.teamList[i].cinematic >= 0) { - trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic); - uiInfo.teamList[i].cinematic = -1; + if (handle == UI_NETMAPCINEMATIC) + { + if (uiInfo.serverStatus.currentServerCinematic >= 0) + { + trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); + uiInfo.serverStatus.currentServerCinematic = -1; + } } - } } - } } -static void UI_DrawCinematic(int handle, float x, float y, float w, float h) { - trap_CIN_SetExtents(handle, x, y, w, h); - trap_CIN_DrawCinematic(handle); +static void UI_DrawCinematic(int handle, float x, float y, float w, float h) +{ + trap_CIN_SetExtents(handle, x, y, w, h); + trap_CIN_DrawCinematic(handle); } -static void UI_RunCinematicFrame(int handle) { - trap_CIN_RunCinematic(handle); -} +static void UI_RunCinematicFrame(int handle) { trap_CIN_RunCinematic(handle); } +static float UI_GetValue(int ownerDraw) { return 0.0f; } /* ================= -UI_Init +UI_ParseResolutions ================= */ -void _UI_Init( qboolean inGameLoad ) { - const char *menuSet; - int start; - - BG_InitClassOverrides( ); - BG_InitAllowedGameElements( ); - - //uiInfo.inGameLoad = inGameLoad; - - UI_RegisterCvars(); - UI_InitMemory(); - - // cache redundant calulations - trap_GetGlconfig( &uiInfo.uiDC.glconfig ); - - // for 640x480 virtualized screen - uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0/480.0); - uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0/640.0); - if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) { - // wide screen - uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) ); - } - else { - // no wide screen - uiInfo.uiDC.bias = 0; - } - - - //UI_Load(); - uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; - uiInfo.uiDC.setColor = &UI_SetColor; - uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; - uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic; - uiInfo.uiDC.drawText = &Text_Paint; - uiInfo.uiDC.textWidth = &Text_Width; - uiInfo.uiDC.textHeight = &Text_Height; - uiInfo.uiDC.registerModel = &trap_R_RegisterModel; - uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; - uiInfo.uiDC.fillRect = &UI_FillRect; - uiInfo.uiDC.drawRect = &_UI_DrawRect; - uiInfo.uiDC.drawSides = &_UI_DrawSides; - uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom; - uiInfo.uiDC.clearScene = &trap_R_ClearScene; - uiInfo.uiDC.drawSides = &_UI_DrawSides; - uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; - uiInfo.uiDC.renderScene = &trap_R_RenderScene; - uiInfo.uiDC.registerFont = &trap_R_RegisterFont; - uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; - uiInfo.uiDC.getValue = &UI_GetValue; - uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; - uiInfo.uiDC.runScript = &UI_RunMenuScript; - uiInfo.uiDC.getTeamColor = &UI_GetTeamColor; - uiInfo.uiDC.setCVar = trap_Cvar_Set; - uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer; - uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; - uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor; - uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; - uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; - uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; - uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; - uiInfo.uiDC.feederCount = &UI_FeederCount; - uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; - uiInfo.uiDC.feederItemText = &UI_FeederItemText; - uiInfo.uiDC.feederSelection = &UI_FeederSelection; - uiInfo.uiDC.setBinding = &trap_Key_SetBinding; - uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf; - uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; - uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText; - uiInfo.uiDC.Error = &Com_Error; - uiInfo.uiDC.Print = &Com_Printf; - uiInfo.uiDC.Pause = &UI_Pause; - uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; - uiInfo.uiDC.registerSound = &trap_S_RegisterSound; - uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; - uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; - uiInfo.uiDC.playCinematic = &UI_PlayCinematic; - uiInfo.uiDC.stopCinematic = &UI_StopCinematic; - uiInfo.uiDC.drawCinematic = &UI_DrawCinematic; - uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame; - - Init_Display(&uiInfo.uiDC); - - String_Init(); - - uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" ); - - AssetCache(); - - start = trap_Milliseconds(); - - uiInfo.teamCount = 0; - uiInfo.characterCount = 0; - uiInfo.aliasCount = 0; - -/* UI_ParseTeamInfo("teaminfo.txt"); - UI_LoadTeams(); - UI_ParseGameInfo("gameinfo.txt");*/ - - menuSet = UI_Cvar_VariableString("ui_menuFiles"); - if (menuSet == NULL || menuSet[0] == '\0') { - menuSet = "ui/menus.txt"; - } - -#if 0 - if (uiInfo.inGameLoad) { - UI_LoadMenus("ui/ingame.txt", qtrue); - } else { // bk010222: left this: UI_LoadMenus(menuSet, qtrue); - } -#else - UI_LoadMenus(menuSet, qtrue); - UI_LoadMenus("ui/ingame.txt", qfalse); - UI_LoadMenus("ui/tremulous.txt", qfalse); - - UI_LoadInfoPanes( "ui/infopanes.def" ); +void UI_ParseResolutions(void) +{ + char buf[MAX_STRING_CHARS]; + char w[16], h[16]; + char *p; + const char *out; + char *s = NULL; - if( uiInfo.uiDC.debug ) - { - int i, j; + trap_Cvar_VariableStringBuffer("r_availableModes", buf, sizeof(buf)); + p = buf; + uiInfo.numResolutions = 0; - for( i = 0; i < uiInfo.tremInfoPaneCount; i++ ) + while (uiInfo.numResolutions < MAX_RESOLUTIONS && String_Parse(&p, &out)) { - Com_Printf( "name: %s\n", uiInfo.tremInfoPanes[ i ].name ); + Q_strncpyz(w, out, sizeof(w)); + s = strchr(w, 'x'); + if (!s) + return; - Com_Printf( "text: %s\n", uiInfo.tremInfoPanes[ i ].text ); + *s++ = '\0'; + Q_strncpyz(h, s, sizeof(h)); - for( j = 0; j < uiInfo.tremInfoPanes[ i ].numGraphics; j++ ) - Com_Printf( "graphic %d: %d %d %d %d\n", j, uiInfo.tremInfoPanes[ i ].graphics[ j ].side, - uiInfo.tremInfoPanes[ i ].graphics[ j ].offset, - uiInfo.tremInfoPanes[ i ].graphics[ j ].width, - uiInfo.tremInfoPanes[ i ].graphics[ j ].height ); + uiInfo.resolutions[uiInfo.numResolutions].w = atoi(w); + uiInfo.resolutions[uiInfo.numResolutions].h = atoi(h); + uiInfo.numResolutions++; } - } -#endif +} - Menus_CloseAll(); +/* +================= +UI_Init +================= +*/ +void UI_Init(qboolean inGameLoad) +{ + BG_InitClassConfigs(); + BG_InitAllowedGameElements(); - trap_LAN_LoadCachedServers(); - UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); + uiInfo.inGameLoad = inGameLoad; - // sets defaults for ui temp cvars - uiInfo.effectsColor = gamecodetoui[(int)trap_Cvar_VariableValue("color1")-1]; - uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair"); - trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + UI_RegisterCvars(); + UI_InitMemory(); + BG_InitMemory(); // FIXIT-M: Merge with UI_InitMemory or something - uiInfo.serverStatus.currentServerCinematic = -1; - uiInfo.previewMovie = -1; + // cache redundant calulations + trap_GetGlconfig(&uiInfo.uiDC.glconfig); - if (trap_Cvar_VariableValue("ui_TeamArenaFirstRun") == 0) { - trap_Cvar_Set("s_volume", "0.8"); - trap_Cvar_Set("s_musicvolume", "0.5"); - trap_Cvar_Set("ui_TeamArenaFirstRun", "1"); - } + // for 640x480 virtualized screen + uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0f / 480.0f); + uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0f / 640.0f); - trap_Cvar_Register(NULL, "debug_protocol", "", 0 ); + // wide screen + uiInfo.uiDC.aspectScale = ((640.0f * uiInfo.uiDC.glconfig.vidHeight) / (480.0f * uiInfo.uiDC.glconfig.vidWidth)); + + uiInfo.uiDC.smallFontScale = trap_Cvar_VariableValue("ui_smallFont"); + uiInfo.uiDC.bigFontScale = trap_Cvar_VariableValue("ui_bigFont"); + + uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + uiInfo.uiDC.setColor = &UI_SetColor; + uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; + uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic; + uiInfo.uiDC.registerModel = &trap_R_RegisterModel; + uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; + uiInfo.uiDC.fillRect = &UI_FillRect; + uiInfo.uiDC.drawRect = &UI_DrawRect; + uiInfo.uiDC.drawSides = &UI_DrawSides; + uiInfo.uiDC.drawTopBottom = &UI_DrawTopBottom; + uiInfo.uiDC.clearScene = &trap_R_ClearScene; + uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + uiInfo.uiDC.renderScene = &trap_R_RenderScene; + uiInfo.uiDC.registerFont = &trap_R_RegisterFont; + uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; + uiInfo.uiDC.getValue = &UI_GetValue; + uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; + uiInfo.uiDC.runScript = &UI_RunMenuScript; + uiInfo.uiDC.setCVar = trap_Cvar_Set; + uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer; + uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; + uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; + uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; + uiInfo.uiDC.feederCount = &UI_FeederCount; + uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; + uiInfo.uiDC.feederItemText = &UI_FeederItemText; + uiInfo.uiDC.feederSelection = &UI_FeederSelection; + uiInfo.uiDC.feederInitialise = &UI_FeederInitialise; + uiInfo.uiDC.setBinding = &trap_Key_SetBinding; + uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf; + uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText; + uiInfo.uiDC.Error = &Com_Error; + uiInfo.uiDC.Print = &Com_Printf; + uiInfo.uiDC.Pause = &UI_Pause; + uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; + uiInfo.uiDC.ownerDrawText = &UI_OwnerDrawText; + uiInfo.uiDC.registerSound = &trap_S_RegisterSound; + uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + uiInfo.uiDC.playCinematic = &UI_PlayCinematic; + uiInfo.uiDC.stopCinematic = &UI_StopCinematic; + uiInfo.uiDC.drawCinematic = &UI_DrawCinematic; + uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame; + + Init_Display(&uiInfo.uiDC); + + String_Init(); + + uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip("white"); + + AssetCache(); + + UI_LoadMenus("ui/menus.txt", qtrue); + UI_LoadMenus("ui/ingame.txt", qfalse); + UI_LoadMenus("ui/tremulous.txt", qfalse); + UI_LoadHelp("ui/help.txt"); + + Menus_CloseAll(); + + trap_LAN_LoadCachedServers(); + + // sets defaults for ui temp cvars + trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + + uiInfo.serverStatus.currentServerCinematic = -1; + uiInfo.previewMovie = -1; - trap_Cvar_Set("ui_actualNetGameType", va("%d", ui_netGameType.integer)); + UI_ParseResolutions(); + uiInfo.voices = BG_VoiceInit(); } - /* ================= UI_KeyEvent ================= */ -void _UI_KeyEvent( int key, qboolean down ) { +void UI_KeyEvent(int key, qboolean down) +{ + if (Menu_Count() > 0) + { + menuDef_t *menu = Menu_GetFocused(); - if (Menu_Count() > 0) { - menuDef_t *menu = Menu_GetFocused(); - if (menu) { - if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) { - Menus_CloseAll(); - } else { - Menu_HandleKey(menu, key, down ); - } - } else { - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); + if (menu) + { + if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) + Menus_CloseAll(); + else + Menu_HandleKey(menu, key, down); + } + else + { + trap_Key_SetCatcher(trap_Key_GetCatcher() & ~KEYCATCH_UI); + trap_Key_ClearStates(); + trap_Cvar_Set("cl_paused", "0"); + } } - } - - //if ((s > 0) && (s != menu_null_sound)) { - // trap_S_StartLocalSound( s, CHAN_LOCAL_SOUND ); - //} } /* @@ -5712,27 +4330,25 @@ void _UI_KeyEvent( int key, qboolean down ) { UI_MouseEvent ================= */ -void _UI_MouseEvent( int dx, int dy ) +void UI_MouseEvent(int dx, int dy) { - // update mouse screen position - uiInfo.uiDC.cursorx += dx; - if (uiInfo.uiDC.cursorx < 0) - uiInfo.uiDC.cursorx = 0; - else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH) - uiInfo.uiDC.cursorx = SCREEN_WIDTH; - - uiInfo.uiDC.cursory += dy; - if (uiInfo.uiDC.cursory < 0) - uiInfo.uiDC.cursory = 0; - else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT) - uiInfo.uiDC.cursory = SCREEN_HEIGHT; - - if (Menu_Count() > 0) { - //menuDef_t *menu = Menu_GetFocused(); - //Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); - Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); - } + // update mouse screen position + uiInfo.uiDC.cursorx += (dx * uiInfo.uiDC.aspectScale); + if (uiInfo.uiDC.cursorx < 0) + uiInfo.uiDC.cursorx = 0; + else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH) + uiInfo.uiDC.cursorx = SCREEN_WIDTH; + + uiInfo.uiDC.cursory += dy; + + if (uiInfo.uiDC.cursory < 0) + uiInfo.uiDC.cursory = 0; + else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT) + uiInfo.uiDC.cursory = SCREEN_HEIGHT; + + if (Menu_Count() > 0) + Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); } /* @@ -5740,791 +4356,523 @@ void _UI_MouseEvent( int dx, int dy ) UI_MousePosition ================= */ -int _UI_MousePosition( void ) -{ - return (int)rint( uiInfo.uiDC.cursorx ) | - (int)rint( uiInfo.uiDC.cursory ) << 16; -} +int UI_MousePosition(void) { return (int)rint(uiInfo.uiDC.cursorx) | (int)rint(uiInfo.uiDC.cursory) << 16; } /* ================= UI_SetMousePosition ================= */ -void _UI_SetMousePosition( int x, int y ) +void UI_SetMousePosition(int x, int y) { - uiInfo.uiDC.cursorx = x; - uiInfo.uiDC.cursory = y; + uiInfo.uiDC.cursorx = x; + uiInfo.uiDC.cursory = y; - if( Menu_Count( ) > 0 ) - Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory ); + if (Menu_Count() > 0) + Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); } -void UI_LoadNonIngame( void ) { - const char *menuSet = UI_Cvar_VariableString("ui_menuFiles"); - if (menuSet == NULL || menuSet[0] == '\0') { - menuSet = "ui/menus.txt"; - } - UI_LoadMenus(menuSet, qfalse); - uiInfo.inGameLoad = qfalse; -} +void UI_SetActiveMenu(uiMenuCommand_t menu) +{ + char buf[256]; -void _UI_SetActiveMenu( uiMenuCommand_t menu ) { - char buf[256]; - - // this should be the ONLY way the menu system is brought up - // enusure minumum menu data is cached - if (Menu_Count() > 0) { - vec3_t v; - v[0] = v[1] = v[2] = 0; - switch ( menu ) { - case UIMENU_NONE: - trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); - trap_Key_ClearStates(); - trap_Cvar_Set( "cl_paused", "0" ); - Menus_CloseAll(); - - return; - case UIMENU_MAIN: - //trap_Cvar_Set( "sv_killserver", "1" ); - trap_Key_SetCatcher( KEYCATCH_UI ); - //trap_S_StartLocalSound( trap_S_RegisterSound("sound/misc/menu_background.wav", qfalse) , CHAN_LOCAL_SOUND ); - //trap_S_StartBackgroundTrack("sound/misc/menu_background.wav", NULL); - if (uiInfo.inGameLoad) { - UI_LoadNonIngame(); - } - Menus_CloseAll(); - Menus_ActivateByName("main"); - trap_Cvar_Set( "ui_loading", "0" ); - trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf)); - if (strlen(buf)) { - if (!ui_singlePlayerActive.integer) { - if( trap_Cvar_VariableValue( "com_errorCode" ) == ERR_SERVERDISCONNECT ) - Menus_ActivateByName("drop_popmenu"); - else - Menus_ActivateByName("error_popmenu"); - } else { - trap_Cvar_Set("com_errorMessage", ""); - } - } - return; - case UIMENU_TEAM: - trap_Key_SetCatcher( KEYCATCH_UI ); - Menus_ActivateByName("team"); - return; - case UIMENU_POSTGAME: - //trap_Cvar_Set( "sv_killserver", "1" ); - trap_Key_SetCatcher( KEYCATCH_UI ); - if (uiInfo.inGameLoad) { - UI_LoadNonIngame(); - } - Menus_CloseAll(); - Menus_ActivateByName("endofgame"); - return; - case UIMENU_INGAME: - trap_Cvar_Set( "cl_paused", "1" ); - trap_Key_SetCatcher( KEYCATCH_UI ); - UI_BuildPlayerList(); - Menus_CloseAll(); - Menus_ActivateByName("ingame"); - return; - } - } -} + // this should be the ONLY way the menu system is brought up + // enusure minumum menu data is cached -qboolean _UI_IsFullscreen( void ) { - return Menus_AnyFullScreenVisible(); -} + if (Menu_Count() > 0) + { + vec3_t v; + v[0] = v[1] = v[2] = 0; + switch (menu) + { + case UIMENU_NONE: + trap_Key_SetCatcher(trap_Key_GetCatcher() & ~KEYCATCH_UI); + trap_Key_ClearStates(); + trap_Cvar_Set("cl_paused", "0"); + Menus_CloseAll(); + return; + + case UIMENU_MAIN: + trap_Cvar_Set("sv_killserver", "1"); + trap_Key_SetCatcher(KEYCATCH_UI); + Menus_CloseAll(); + Menus_ActivateByName("main"); + trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf)); + + if (strlen(buf)) + { + if (trap_Cvar_VariableValue("com_errorCode") == ERR_SERVERDISCONNECT) + Menus_ActivateByName("drop_popmenu"); + else + Menus_ActivateByName("error_popmenu"); + } + return; + + case UIMENU_INGAME: + trap_Cvar_Set("cl_paused", "1"); + trap_Key_SetCatcher(KEYCATCH_UI); + UI_BuildPlayerList(); + Menus_CloseAll(); + Menus_ActivateByName("ingame"); + return; + } + } +} +qboolean UI_IsFullscreen(void) { return Menus_AnyFullScreenVisible(); } -static connstate_t lastConnState; -static char lastLoadingText[MAX_INFO_VALUE]; +static connstate_t lastConnState; +static char lastLoadingText[MAX_INFO_VALUE]; -static void UI_ReadableSize ( char *buf, int bufsize, int value ) +static void UI_ReadableSize(char *buf, int bufsize, int value) { - if (value > 1024*1024*1024 ) { // gigs - Com_sprintf( buf, bufsize, "%d", value / (1024*1024*1024) ); - Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d GB", - (value % (1024*1024*1024))*100 / (1024*1024*1024) ); - } else if (value > 1024*1024 ) { // megs - Com_sprintf( buf, bufsize, "%d", value / (1024*1024) ); - Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d MB", - (value % (1024*1024))*100 / (1024*1024) ); - } else if (value > 1024 ) { // kilos - Com_sprintf( buf, bufsize, "%d KB", value / 1024 ); - } else { // bytes - Com_sprintf( buf, bufsize, "%d bytes", value ); - } + if (value > 1024 * 1024 * 1024) + { // gigs + Com_sprintf(buf, bufsize, "%d", value / (1024 * 1024 * 1024)); + Com_sprintf(buf + strlen(buf), bufsize - strlen(buf), ".%02d GB", + (value % (1024 * 1024 * 1024)) * 100 / (1024 * 1024 * 1024)); + } + else if (value > 1024 * 1024) + { // megs + Com_sprintf(buf, bufsize, "%d", value / (1024 * 1024)); + Com_sprintf( + buf + strlen(buf), bufsize - strlen(buf), ".%02d MB", (value % (1024 * 1024)) * 100 / (1024 * 1024)); + } + else if (value > 1024) + { // kilos + Com_sprintf(buf, bufsize, "%d KB", value / 1024); + } + else + { // bytes + Com_sprintf(buf, bufsize, "%d bytes", value); + } } // Assumes time is in msec -static void UI_PrintTime ( char *buf, int bufsize, int time ) { - time /= 1000; // change to seconds - - if (time > 3600) { // in the hours range - Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, (time % 3600) / 60 ); - } else if (time > 60) { // mins - Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 ); - } else { // secs - Com_sprintf( buf, bufsize, "%d sec", time ); - } -} - -void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust) { - int len = Text_Width(text, scale, 0); - Text_Paint(x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -} +static void UI_PrintTime(char *buf, int bufsize, int time) +{ + time /= 1000; // change to seconds -void Text_PaintCenter_AutoWrapped(float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust) { - int width; - char *s1,*s2,*s3; - char c_bcp; - char buf[1024]; - - if (!str || str[0]=='\0') - return; - - Q_strncpyz(buf, str, sizeof(buf)); - s1 = s2 = s3 = buf; - - while (1) { - do { - s3++; - } while (*s3!=' ' && *s3!='\0'); - c_bcp = *s3; - *s3 = '\0'; - width = Text_Width(s1, scale, 0); - *s3 = c_bcp; - if (width > xmax) { - if (s1==s2) - { - // fuck, don't have a clean cut, we'll overflow - s2 = s3; - } - *s2 = '\0'; - Text_PaintCenter(x, y, scale, color, s1, adjust); - y += ystep; - if (c_bcp == '\0') - { - // that was the last word - // we could start a new loop, but that wouldn't be much use - // even if the word is too long, we would overflow it (see above) - // so just print it now if needed - s2++; - if (*s2 != '\0') // if we are printing an overflowing line we have s2 == s3 - Text_PaintCenter(x, y, scale, color, s2, adjust); - break; - } - s2++; - s1 = s2; - s3 = s2; + if (time > 3600) + { // in the hours range + Com_sprintf(buf, bufsize, "%d hr %d min", time / 3600, (time % 3600) / 60); + } + else if (time > 60) + { // mins + Com_sprintf(buf, bufsize, "%d min %d sec", time / 60, time % 60); } else - { - s2 = s3; - if (c_bcp == '\0') // we reached the end - { - Text_PaintCenter(x, y, scale, color, s1, adjust); - break; - } + { // secs + Com_sprintf(buf, bufsize, "%d sec", time); } - } } +// FIXME: move to ui_shared.c? +void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust) +{ + int len = UI_Text_Width(text, scale); + UI_Text_Paint(x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +} -static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) { - static char dlText[] = "Downloading:"; - static char etaText[] = "Estimated time left:"; - static char xferText[] = "Transfer rate:"; +void Text_PaintCenter_AutoWrapped( + float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust) +{ + int width; + char *s1, *s2, *s3; + char c_bcp; + char buf[1024]; - int downloadSize, downloadCount, downloadTime; - char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64]; - int xferRate; - int leftWidth; - const char *s; + if (!str || str[0] == '\0') + return; - downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" ); - downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" ); - downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" ); + Q_strncpyz(buf, str, sizeof(buf)); - leftWidth = 320; + s1 = s2 = s3 = buf; - UI_SetColor(colorWhite); - Text_PaintCenter(centerPoint, yStart + 112, scale, colorWhite, dlText, 0); - Text_PaintCenter(centerPoint, yStart + 192, scale, colorWhite, etaText, 0); - Text_PaintCenter(centerPoint, yStart + 248, scale, colorWhite, xferText, 0); + while (1) + { + do + s3++; + while (*s3 != ' ' && *s3 != '\0'); - if (downloadSize > 0) { - s = va( "%s (%d%%)", downloadName, downloadCount * 100 / downloadSize ); - } else { - s = downloadName; - } + c_bcp = *s3; - Text_PaintCenter(centerPoint, yStart+136, scale, colorWhite, s, 0); + *s3 = '\0'; - UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount ); - UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize ); + width = UI_Text_Width(s1, scale); - if (downloadCount < 4096 || !downloadTime) { - Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0); - Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); - } else { - if ((uiInfo.uiDC.realTime - downloadTime) / 1000) { - xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000); - } else { - xferRate = 0; + *s3 = c_bcp; + + if (width > xmax) + { + if (s1 == s2) + { + // fuck, don't have a clean cut, we'll overflow + s2 = s3; + } + + *s2 = '\0'; + Text_PaintCenter(x, y, scale, color, s1, adjust); + y += ystep; + + if (c_bcp == '\0') + { + // that was the last word + // we could start a new loop, but that wouldn't be much use + // even if the word is too long, we would overflow it (see above) + // so just print it now if needed + s2++; + + if (*s2 != '\0') // if we are printing an overflowing line we have s2 == s3 + Text_PaintCenter(x, y, scale, color, s2, adjust); + + break; + } + + s2++; + s1 = s2; + s3 = s2; + } + else + { + s2 = s3; + + if (c_bcp == '\0') // we reached the end + { + Text_PaintCenter(x, y, scale, color, s1, adjust); + break; + } + } } - UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate ); +} + +static void UI_DisplayDownloadInfo(const char *downloadName, float centerPoint, float yStart, float scale) +{ + static char dlText[] = "Downloading:"; + static char etaText[] = "Estimated time left:"; + static char xferText[] = "Transfer rate:"; - // Extrapolate estimated completion time - if (downloadSize && xferRate) { - int n = downloadSize / xferRate; // estimated time for entire d/l in secs + int downloadSize, downloadCount, downloadTime; + char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64]; + int xferRate; + int leftWidth; + const char *s; - // We do it in K (/1024) because we'd overflow around 4MB - UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf, - (n - (((downloadCount/1024) * n) / (downloadSize/1024))) * 1000); + downloadSize = trap_Cvar_VariableValue("cl_downloadSize"); + downloadCount = trap_Cvar_VariableValue("cl_downloadCount"); + downloadTime = trap_Cvar_VariableValue("cl_downloadTime"); - Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, dlTimeBuf, 0); - Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); - } else { - Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0); - if (downloadSize) { - Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); - } else { - Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s copied)", dlSizeBuf), 0); - } + leftWidth = 320; + + UI_SetColor(colorWhite); + Text_PaintCenter(centerPoint, yStart + 112, scale, colorWhite, dlText, 0); + Text_PaintCenter(centerPoint, yStart + 192, scale, colorWhite, etaText, 0); + Text_PaintCenter(centerPoint, yStart + 248, scale, colorWhite, xferText, 0); + + if (downloadSize > 0) + s = va("%s (%d%%)", downloadName, (int)((float)downloadCount * 100.0f / downloadSize)); + else + s = downloadName; + + Text_PaintCenter(centerPoint, yStart + 136, scale, colorWhite, s, 0); + + UI_ReadableSize(dlSizeBuf, sizeof dlSizeBuf, downloadCount); + UI_ReadableSize(totalSizeBuf, sizeof totalSizeBuf, downloadSize); + + if (downloadCount < 4096 || !downloadTime) + { + Text_PaintCenter(leftWidth, yStart + 216, scale, colorWhite, "estimating", 0); + Text_PaintCenter( + leftWidth, yStart + 160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); } + else + { + if ((uiInfo.uiDC.realTime - downloadTime) / 1000) + xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000); + else + xferRate = 0; + + UI_ReadableSize(xferRateBuf, sizeof xferRateBuf, xferRate); + + // Extrapolate estimated completion time + + if (downloadSize && xferRate) + { + int n = downloadSize / xferRate; // estimated time for entire d/l in secs + + // We do it in K (/1024) because we'd overflow around 4MB + UI_PrintTime( + dlTimeBuf, sizeof dlTimeBuf, (n - (((downloadCount / 1024) * n) / (downloadSize / 1024))) * 1000); + + Text_PaintCenter(leftWidth, yStart + 216, scale, colorWhite, dlTimeBuf, 0); + Text_PaintCenter( + leftWidth, yStart + 160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); + } + else + { + Text_PaintCenter(leftWidth, yStart + 216, scale, colorWhite, "estimating", 0); + + if (downloadSize) + Text_PaintCenter( + leftWidth, yStart + 160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0); + else + Text_PaintCenter(leftWidth, yStart + 160, scale, colorWhite, va("(%s copied)", dlSizeBuf), 0); + } - if (xferRate) { - Text_PaintCenter(leftWidth, yStart+272, scale, colorWhite, va("%s/Sec", xferRateBuf), 0); + if (xferRate) + Text_PaintCenter(leftWidth, yStart + 272, scale, colorWhite, va("%s/Sec", xferRateBuf), 0); } - } } /* ======================== UI_DrawConnectScreen - -This will also be overlaid on the cgame info screen during loading -to prevent it from blinking away too rapidly on local or lan games. ======================== */ -void UI_DrawConnectScreen( qboolean overlay ) { - char *s; - uiClientState_t cstate; - char info[MAX_INFO_VALUE]; - char text[256]; - float centerPoint, yStart, scale; - - menuDef_t *menu = Menus_FindByName("Connect"); - - - if ( !overlay && menu ) { - Menu_Paint(menu, qtrue); - } - - if (!overlay) { - centerPoint = 320; - yStart = 130; - scale = 0.5f; - } else { - centerPoint = 320; - yStart = 32; - scale = 0.6f; - return; - } - - // see what information we should display - trap_GetClientState( &cstate ); - - info[0] = '\0'; - if( trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ) ) { - Text_PaintCenter(centerPoint, yStart, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" )), 0); - } - - if (!Q_stricmp(cstate.servername,"localhost")) { - Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, va("Starting up..."), ITEM_TEXTSTYLE_SHADOWEDMORE); - } else { - strcpy(text, va("Connecting to %s", cstate.servername)); - Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite,text , ITEM_TEXTSTYLE_SHADOWEDMORE); - } - - - // display global MOTD at bottom - Text_PaintCenter(centerPoint, 600, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0); - // print any server info (server full, bad version, etc) - if ( cstate.connState < CA_CONNECTED ) { - Text_PaintCenter_AutoWrapped(centerPoint, yStart + 176, 630, 20, scale, colorWhite, cstate.messageString, 0); - } - - if ( lastConnState > cstate.connState ) { - lastLoadingText[0] = '\0'; - } - lastConnState = cstate.connState; - - switch ( cstate.connState ) { - case CA_CONNECTING: - s = va("Awaiting connection...%i", cstate.connectPacketCount); - break; - case CA_CHALLENGING: - s = va("Awaiting challenge...%i", cstate.connectPacketCount); - break; - case CA_CONNECTED: { - char downloadName[MAX_INFO_VALUE]; - - trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof(downloadName) ); - if (*downloadName) { - UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale ); - return; - } +void UI_DrawConnectScreen() +{ + const char *s; + uiClientState_t cstate; + char info[MAX_INFO_VALUE]; + char text[256]; + float centerPoint = 320, yStart = 130, scale = 0.5f; + + menuDef_t *menu = Menus_FindByName("Connect"); + + if ( menu ) + Menu_Paint(menu, qtrue); + + // see what information we should display + trap_GetClientState(&cstate); + + info[0] = '\0'; + + if (trap_GetConfigString(CS_SERVERINFO, info, sizeof(info))) + Text_PaintCenter( + centerPoint, yStart, scale, colorWhite, va("Loading %s", Info_ValueForKey(info, "mapname")), 0); + + if (!Q_stricmp(cstate.servername, "localhost")) + Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, "Starting up...", ITEM_TEXTSTYLE_SHADOWEDMORE); + else + { + Com_sprintf(text, sizeof(text), "Connecting to %s", cstate.servername); + Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, text, ITEM_TEXTSTYLE_SHADOWEDMORE); } - s = "Awaiting gamestate..."; - break; - case CA_LOADING: - return; - case CA_PRIMED: - return; - default: - return; - } + // display global MOTD at bottom + Text_PaintCenter(centerPoint, 600, scale, colorWhite, Info_ValueForKey(cstate.updateInfoString, "motd"), 0); - if (Q_stricmp(cstate.servername,"localhost")) { - Text_PaintCenter(centerPoint, yStart + 80, scale, colorWhite, s, 0); - } + // print any server info (server full, bad version, etc) + if (cstate.connState < CA_CONNECTED) + Text_PaintCenter(centerPoint, yStart + 176, scale, colorWhite, cstate.messageString, 0); - // password required / connection rejected information goes here -} + if (lastConnState > cstate.connState) + lastLoadingText[0] = '\0'; + lastConnState = cstate.connState; -/* -================ -cvars -================ -*/ + switch (cstate.connState) + { + case CA_CONNECTING: + s = va("Awaiting connection...%i", cstate.connectPacketCount); + break; -typedef struct { - vmCvar_t *vmCvar; - char *cvarName; - char *defaultString; - int cvarFlags; -} cvarTable_t; - -vmCvar_t ui_ffa_fraglimit; -vmCvar_t ui_ffa_timelimit; - -vmCvar_t ui_tourney_fraglimit; -vmCvar_t ui_tourney_timelimit; - -vmCvar_t ui_team_fraglimit; -vmCvar_t ui_team_timelimit; -vmCvar_t ui_team_friendly; - -vmCvar_t ui_ctf_capturelimit; -vmCvar_t ui_ctf_timelimit; -vmCvar_t ui_ctf_friendly; - -vmCvar_t ui_arenasFile; -vmCvar_t ui_botsFile; -vmCvar_t ui_spScores1; -vmCvar_t ui_spScores2; -vmCvar_t ui_spScores3; -vmCvar_t ui_spScores4; -vmCvar_t ui_spScores5; -vmCvar_t ui_spAwards; -vmCvar_t ui_spVideos; -vmCvar_t ui_spSkill; - -vmCvar_t ui_spSelection; - -vmCvar_t ui_browserMaster; -vmCvar_t ui_browserGameType; -vmCvar_t ui_browserSortKey; -vmCvar_t ui_browserShowFull; -vmCvar_t ui_browserShowEmpty; - -vmCvar_t ui_brassTime; -vmCvar_t ui_drawCrosshair; -vmCvar_t ui_drawCrosshairNames; -vmCvar_t ui_marks; - -vmCvar_t ui_server1; -vmCvar_t ui_server2; -vmCvar_t ui_server3; -vmCvar_t ui_server4; -vmCvar_t ui_server5; -vmCvar_t ui_server6; -vmCvar_t ui_server7; -vmCvar_t ui_server8; -vmCvar_t ui_server9; -vmCvar_t ui_server10; -vmCvar_t ui_server11; -vmCvar_t ui_server12; -vmCvar_t ui_server13; -vmCvar_t ui_server14; -vmCvar_t ui_server15; -vmCvar_t ui_server16; - -vmCvar_t ui_redteam; -vmCvar_t ui_redteam1; -vmCvar_t ui_redteam2; -vmCvar_t ui_redteam3; -vmCvar_t ui_redteam4; -vmCvar_t ui_redteam5; -vmCvar_t ui_blueteam; -vmCvar_t ui_blueteam1; -vmCvar_t ui_blueteam2; -vmCvar_t ui_blueteam3; -vmCvar_t ui_blueteam4; -vmCvar_t ui_blueteam5; -vmCvar_t ui_teamName; -vmCvar_t ui_dedicated; -vmCvar_t ui_gameType; -vmCvar_t ui_netGameType; -vmCvar_t ui_actualNetGameType; -vmCvar_t ui_joinGameType; -vmCvar_t ui_netSource; -vmCvar_t ui_serverFilterType; -vmCvar_t ui_opponentName; -vmCvar_t ui_menuFiles; -vmCvar_t ui_currentTier; -vmCvar_t ui_currentMap; -vmCvar_t ui_currentNetMap; -vmCvar_t ui_mapIndex; -vmCvar_t ui_currentOpponent; -vmCvar_t ui_selectedPlayer; -vmCvar_t ui_selectedPlayerName; -vmCvar_t ui_lastServerRefresh_0; -vmCvar_t ui_lastServerRefresh_1; -vmCvar_t ui_lastServerRefresh_2; -vmCvar_t ui_lastServerRefresh_3; -vmCvar_t ui_lastServerRefresh_0_time; -vmCvar_t ui_lastServerRefresh_1_time; -vmCvar_t ui_lastServerRefresh_2_time; -vmCvar_t ui_lastServerRefresh_3_time; -vmCvar_t ui_singlePlayerActive; -vmCvar_t ui_scoreAccuracy; -vmCvar_t ui_scoreImpressives; -vmCvar_t ui_scoreExcellents; -vmCvar_t ui_scoreCaptures; -vmCvar_t ui_scoreDefends; -vmCvar_t ui_scoreAssists; -vmCvar_t ui_scoreGauntlets; -vmCvar_t ui_scoreScore; -vmCvar_t ui_scorePerfect; -vmCvar_t ui_scoreTeam; -vmCvar_t ui_scoreBase; -vmCvar_t ui_scoreTimeBonus; -vmCvar_t ui_scoreSkillBonus; -vmCvar_t ui_scoreShutoutBonus; -vmCvar_t ui_scoreTime; -vmCvar_t ui_captureLimit; -vmCvar_t ui_fragLimit; -vmCvar_t ui_smallFont; -vmCvar_t ui_bigFont; -vmCvar_t ui_findPlayer; -vmCvar_t ui_Q3Model; -vmCvar_t ui_hudFiles; -vmCvar_t ui_recordSPDemo; -vmCvar_t ui_realCaptureLimit; -vmCvar_t ui_realWarmUp; -vmCvar_t ui_serverStatusTimeOut; - -//TA: bank values -vmCvar_t ui_bank; -vmCvar_t ui_winner; - -vmCvar_t ui_chatCommands; - - -// bk001129 - made static to avoid aliasing -static cvarTable_t cvarTable[] = { - { &ui_ffa_fraglimit, "ui_ffa_fraglimit", "20", CVAR_ARCHIVE }, - { &ui_ffa_timelimit, "ui_ffa_timelimit", "0", CVAR_ARCHIVE }, - - { &ui_tourney_fraglimit, "ui_tourney_fraglimit", "0", CVAR_ARCHIVE }, - { &ui_tourney_timelimit, "ui_tourney_timelimit", "15", CVAR_ARCHIVE }, - - { &ui_team_fraglimit, "ui_team_fraglimit", "0", CVAR_ARCHIVE }, - { &ui_team_timelimit, "ui_team_timelimit", "20", CVAR_ARCHIVE }, - { &ui_team_friendly, "ui_team_friendly", "1", CVAR_ARCHIVE }, - - { &ui_ctf_capturelimit, "ui_ctf_capturelimit", "8", CVAR_ARCHIVE }, - { &ui_ctf_timelimit, "ui_ctf_timelimit", "30", CVAR_ARCHIVE }, - { &ui_ctf_friendly, "ui_ctf_friendly", "0", CVAR_ARCHIVE }, - - { &ui_arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM }, - { &ui_botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM }, - { &ui_spScores1, "g_spScores1", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spScores2, "g_spScores2", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spScores3, "g_spScores3", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spScores4, "g_spScores4", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spScores5, "g_spScores5", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spAwards, "g_spAwards", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spVideos, "g_spVideos", "", CVAR_ARCHIVE | CVAR_ROM }, - { &ui_spSkill, "g_spSkill", "2", CVAR_ARCHIVE }, - - { &ui_spSelection, "ui_spSelection", "", CVAR_ROM }, - { &ui_winner, "ui_winner", "", CVAR_ROM }, - - { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE }, - { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE }, - { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE }, - { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE }, - { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE }, - - { &ui_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, - { &ui_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, - { &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, - { &ui_marks, "cg_marks", "1", CVAR_ARCHIVE }, - - { &ui_server1, "server1", "", CVAR_ARCHIVE }, - { &ui_server2, "server2", "", CVAR_ARCHIVE }, - { &ui_server3, "server3", "", CVAR_ARCHIVE }, - { &ui_server4, "server4", "", CVAR_ARCHIVE }, - { &ui_server5, "server5", "", CVAR_ARCHIVE }, - { &ui_server6, "server6", "", CVAR_ARCHIVE }, - { &ui_server7, "server7", "", CVAR_ARCHIVE }, - { &ui_server8, "server8", "", CVAR_ARCHIVE }, - { &ui_server9, "server9", "", CVAR_ARCHIVE }, - { &ui_server10, "server10", "", CVAR_ARCHIVE }, - { &ui_server11, "server11", "", CVAR_ARCHIVE }, - { &ui_server12, "server12", "", CVAR_ARCHIVE }, - { &ui_server13, "server13", "", CVAR_ARCHIVE }, - { &ui_server14, "server14", "", CVAR_ARCHIVE }, - { &ui_server15, "server15", "", CVAR_ARCHIVE }, - { &ui_server16, "server16", "", CVAR_ARCHIVE }, - { &ui_new, "ui_new", "0", CVAR_TEMP }, - { &ui_debug, "ui_debug", "0", CVAR_TEMP }, - { &ui_initialized, "ui_initialized", "0", CVAR_TEMP }, - { &ui_teamName, "ui_teamName", "Pagans", CVAR_ARCHIVE }, - { &ui_opponentName, "ui_opponentName", "Stroggs", CVAR_ARCHIVE }, - { &ui_redteam, "ui_redteam", "Pagans", CVAR_ARCHIVE }, - { &ui_blueteam, "ui_blueteam", "Stroggs", CVAR_ARCHIVE }, - { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE }, - { &ui_gameType, "ui_gametype", "3", CVAR_ARCHIVE }, - { &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE }, - { &ui_netGameType, "ui_netGametype", "3", CVAR_ARCHIVE }, - { &ui_actualNetGameType, "ui_actualNetGametype", "3", CVAR_ARCHIVE }, - { &ui_redteam1, "ui_redteam1", "0", CVAR_ARCHIVE }, - { &ui_redteam2, "ui_redteam2", "0", CVAR_ARCHIVE }, - { &ui_redteam3, "ui_redteam3", "0", CVAR_ARCHIVE }, - { &ui_redteam4, "ui_redteam4", "0", CVAR_ARCHIVE }, - { &ui_redteam5, "ui_redteam5", "0", CVAR_ARCHIVE }, - { &ui_blueteam1, "ui_blueteam1", "0", CVAR_ARCHIVE }, - { &ui_blueteam2, "ui_blueteam2", "0", CVAR_ARCHIVE }, - { &ui_blueteam3, "ui_blueteam3", "0", CVAR_ARCHIVE }, - { &ui_blueteam4, "ui_blueteam4", "0", CVAR_ARCHIVE }, - { &ui_blueteam5, "ui_blueteam5", "0", CVAR_ARCHIVE }, - { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE }, - { &ui_menuFiles, "ui_menuFiles", "ui/menus.txt", CVAR_ARCHIVE }, - { &ui_currentTier, "ui_currentTier", "0", CVAR_ARCHIVE }, - { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE }, - { &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE }, - { &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE }, - { &ui_currentOpponent, "ui_currentOpponent", "0", CVAR_ARCHIVE }, - { &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE}, - { &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE}, - { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE}, - { &ui_singlePlayerActive, "ui_singlePlayerActive", "0", 0}, - { &ui_scoreAccuracy, "ui_scoreAccuracy", "0", CVAR_ARCHIVE}, - { &ui_scoreImpressives, "ui_scoreImpressives", "0", CVAR_ARCHIVE}, - { &ui_scoreExcellents, "ui_scoreExcellents", "0", CVAR_ARCHIVE}, - { &ui_scoreCaptures, "ui_scoreCaptures", "0", CVAR_ARCHIVE}, - { &ui_scoreDefends, "ui_scoreDefends", "0", CVAR_ARCHIVE}, - { &ui_scoreAssists, "ui_scoreAssists", "0", CVAR_ARCHIVE}, - { &ui_scoreGauntlets, "ui_scoreGauntlets", "0",CVAR_ARCHIVE}, - { &ui_scoreScore, "ui_scoreScore", "0", CVAR_ARCHIVE}, - { &ui_scorePerfect, "ui_scorePerfect", "0", CVAR_ARCHIVE}, - { &ui_scoreTeam, "ui_scoreTeam", "0 to 0", CVAR_ARCHIVE}, - { &ui_scoreBase, "ui_scoreBase", "0", CVAR_ARCHIVE}, - { &ui_scoreTime, "ui_scoreTime", "00:00", CVAR_ARCHIVE}, - { &ui_scoreTimeBonus, "ui_scoreTimeBonus", "0", CVAR_ARCHIVE}, - { &ui_scoreSkillBonus, "ui_scoreSkillBonus", "0", CVAR_ARCHIVE}, - { &ui_scoreShutoutBonus, "ui_scoreShutoutBonus", "0", CVAR_ARCHIVE}, - { &ui_fragLimit, "ui_fragLimit", "10", 0}, - { &ui_captureLimit, "ui_captureLimit", "5", 0}, - { &ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE}, - { &ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE}, - { &ui_findPlayer, "ui_findPlayer", "Sarge", CVAR_ARCHIVE}, - { &ui_Q3Model, "ui_q3model", "0", CVAR_ARCHIVE}, - { &ui_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, - { &ui_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, - { &ui_teamArenaFirstRun, "ui_teamArenaFirstRun", "0", CVAR_ARCHIVE}, - { &ui_realWarmUp, "g_warmup", "20", CVAR_ARCHIVE}, - { &ui_realCaptureLimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART}, - { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE}, - - { &ui_bank, "ui_bank", "0", 0 }, - - { &ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE}, -}; - -// bk001129 - made static to avoid aliasing -static int cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]); + case CA_CHALLENGING: + s = va("Awaiting challenge...%i", cstate.connectPacketCount); + break; + case CA_CONNECTED: + { + char downloadName[MAX_INFO_VALUE]; + int prompt = trap_Cvar_VariableValue("com_downloadPrompt"); -/* -================= -UI_RegisterCvars -================= -*/ -void UI_RegisterCvars( void ) { - int i; - cvarTable_t *cv; + if (prompt & DLP_SHOW) + { + trap_Key_SetCatcher(KEYCATCH_UI); + Menus_ActivateByName("download_popmenu"); + trap_Cvar_Set("com_downloadPrompt", "0"); + } + + trap_Cvar_VariableStringBuffer("cl_downloadName", downloadName, sizeof(downloadName)); + + if (*downloadName) + { + UI_DisplayDownloadInfo(downloadName, centerPoint, yStart, scale); + return; + } + } + + s = "Awaiting gamestate..."; + break; + + case CA_LOADING: + return; - for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { - trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); - } + case CA_PRIMED: + return; + + default: + return; + } + + if (Q_stricmp(cstate.servername, "localhost")) + Text_PaintCenter(centerPoint, yStart + 80, scale, colorWhite, s, 0); + + // password required / connection rejected information goes here } /* ================= -UI_UpdateCvars +UI_RegisterCvars ================= */ -void UI_UpdateCvars( void ) { - int i; - cvarTable_t *cv; +void UI_RegisterCvars(void) +{ + size_t i; + cvarTable_t *cv; - for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { - trap_Cvar_Update( cv->vmCvar ); - } + for (i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++) + trap_Cvar_Register(cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags); } - /* ================= -ArenaServers_StopRefresh +UI_UpdateCvars ================= */ -static void UI_StopServerRefresh( void ) +void UI_UpdateCvars(void) { - int count; - - if (!uiInfo.serverStatus.refreshActive) { - // not currently refreshing - return; - } - uiInfo.serverStatus.refreshActive = qfalse; - Com_Printf("%d servers listed in browser with %d players.\n", - uiInfo.serverStatus.numDisplayServers, - uiInfo.serverStatus.numPlayersOnServers); - count = trap_LAN_GetServerCount(ui_netSource.integer); - if (count - uiInfo.serverStatus.numDisplayServers > 0) { - Com_Printf("%d servers not listed due to packet loss or pings higher than %d\n", - count - uiInfo.serverStatus.numDisplayServers, - (int) trap_Cvar_VariableValue("cl_maxPing")); - } + size_t i; + cvarTable_t *cv; + for (i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++) + trap_Cvar_Update(cv->vmCvar); } /* ================= -UI_DoServerRefresh +UI_UpdateNews ================= */ -static void UI_DoServerRefresh( void ) +void UI_UpdateNews(qboolean begin) { - qboolean wait = qfalse; - - if (!uiInfo.serverStatus.refreshActive) { - return; - } - if (ui_netSource.integer != AS_FAVORITES) { - if (ui_netSource.integer == AS_LOCAL) { - if (!trap_LAN_GetServerCount(ui_netSource.integer)) { - wait = qtrue; - } - } else { - if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) { - wait = qtrue; - } + char newsString[MAX_NEWS_STRING]; + const char *c; + const char *wrapped; + int line = 0; + int linePos = 0; + qboolean finished; + + if (begin && !uiInfo.newsInfo.refreshActive) + { + uiInfo.newsInfo.refreshtime = uiInfo.uiDC.realTime + 10000; + uiInfo.newsInfo.refreshActive = qtrue; + } + else if (!uiInfo.newsInfo.refreshActive) // do nothing + { + return; + } + else if (uiInfo.uiDC.realTime > uiInfo.newsInfo.refreshtime) + { + strcpy(uiInfo.newsInfo.text[0], "^1Error: Timed out while contacting the server."); + uiInfo.newsInfo.numLines = 1; + return; } - } - if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) { - if (wait) { - return; + // start the news fetching + finished = trap_GetNews(begin); + + // parse what comes back. Parse newlines and otherwise chop when necessary + trap_Cvar_VariableStringBuffer("cl_newsString", newsString, sizeof(newsString)); + + // FIXME remove magic width constant + wrapped = Item_Text_Wrap(newsString, 0.25f, 325 * uiInfo.uiDC.aspectScale); + + for (c = wrapped; *c != '\0'; ++c) + { + if (linePos == (MAX_NEWS_LINEWIDTH - 1) || *c == '\n') + { + uiInfo.newsInfo.text[line][linePos] = '\0'; + + if (line == (MAX_NEWS_LINES - 1)) + break; + + linePos = 0; + line++; + + if (*c != '\n') + { + uiInfo.newsInfo.text[line][linePos] = *c; + linePos++; + } + } + else if (isprint(*c)) + { + uiInfo.newsInfo.text[line][linePos] = *c; + linePos++; + } } - } - // if still trying to retrieve pings - if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) { - uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; - } else if (!wait) { - // get the last servers in the list - UI_BuildServerDisplayList(2); - // stop the refresh - UI_StopServerRefresh(); - } - // - UI_BuildServerDisplayList(qfalse); + uiInfo.newsInfo.text[line][linePos] = '\0'; + uiInfo.newsInfo.numLines = line + 1; + + if (finished) + uiInfo.newsInfo.refreshActive = qfalse; } -/* -================= -UI_StartServerRefresh -================= -*/ -static void UI_StartServerRefresh(qboolean full) +void UI_UpdateGithubRelease() { - int i; - char *ptr; - int time; - qtime_t q; - - time = trap_RealTime(&q); - trap_Cvar_Set( va("ui_lastServerRefresh_%i_time", ui_netSource.integer ), - va( "%i", time ) ); - trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer), - va("%s-%i, %i at %i:%02i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min)); - - if (!full) { - UI_UpdatePendingPings(); - return; - } - - uiInfo.serverStatus.refreshActive = qtrue; - uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000; - // clear number of displayed servers - uiInfo.serverStatus.numDisplayServers = 0; - uiInfo.serverStatus.numPlayersOnServers = 0; - // mark all servers as visible so we store ping updates for them - trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); - // reset all the pings - trap_LAN_ResetPings(ui_netSource.integer); - // - if( ui_netSource.integer == AS_LOCAL ) { - trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" ); - uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; - return; - } + char newsString[MAX_NEWS_STRING]; + const char *c; + const char *wrapped; + int line = 0, linePos = 0; + int nexttime = uiInfo.githubRelease.nextTime; - uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; - if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) { - if( ui_netSource.integer == AS_GLOBAL ) { - i = 0; - } - else { - i = 1; - } + if (nexttime && !(nexttime > uiInfo.uiDC.realTime)) + return; - ptr = UI_Cvar_VariableString("debug_protocol"); - if (strlen(ptr)) { - trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", i, ptr)); - } - else { - trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", i, (int)trap_Cvar_VariableValue( "protocol" ) ) ); + // Limit checks to 1x every 10seconds + uiInfo.githubRelease.nextTime = uiInfo.uiDC.realTime + 10000; + trap_Cmd_ExecuteText(EXEC_INSERT, "checkForUpdate"); + + // parse what comes back. Parse newlines and otherwise chop when necessary + trap_Cvar_VariableStringBuffer("cl_latestRelease", newsString, sizeof(newsString)); + + // FIXME remove magic width constant + wrapped = Item_Text_Wrap(newsString, 0.33f, 450 * uiInfo.uiDC.aspectScale); + + for (c = wrapped; *c != '\0'; ++c) + { + if (linePos == (MAX_NEWS_LINEWIDTH - 1) || *c == '\n') + { + uiInfo.githubRelease.text[line][linePos] = '\0'; + + if (line == (MAX_NEWS_LINES - 1)) + break; + + linePos = 0; + line++; + + if (*c != '\n') + { + uiInfo.githubRelease.text[line][linePos] = *c; + linePos++; + } + } + else if (isprint(*c)) + { + uiInfo.githubRelease.text[line][linePos] = *c; + linePos++; + } } - } + + uiInfo.githubRelease.text[line][linePos] = '\0'; + uiInfo.githubRelease.numLines = line + 1; } +#ifdef MODULE_INTERFACE_11 +void trap_R_SetClipRegion(const float *region) {} + +qboolean trap_GetNews(qboolean force) { return qtrue; } +#endif diff --git a/src/ui/ui_players.c b/src/ui/ui_players.c deleted file mode 100644 index 55fce51..0000000 --- a/src/ui/ui_players.c +++ /dev/null @@ -1,1369 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous 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. - -Tremulous 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -// ui_players.c - -#include "ui_local.h" - - -#define UI_TIMER_GESTURE 2300 -#define UI_TIMER_JUMP 1000 -#define UI_TIMER_LAND 130 -#define UI_TIMER_WEAPON_SWITCH 300 -#define UI_TIMER_ATTACK 500 -#define UI_TIMER_MUZZLE_FLASH 20 -#define UI_TIMER_WEAPON_DELAY 250 - -#define JUMP_HEIGHT 56 - -#define SWINGSPEED 0.3f - -#define SPIN_SPEED 0.9f -#define COAST_TIME 1000 - - -static int dp_realtime; -static float jumpHeight; -sfxHandle_t weaponChangeSound; - - -/* -=============== -UI_PlayerInfo_SetWeapon -=============== -*/ -static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) -{ - //TA: FIXME: this is probably useless for trem -/* gitem_t * item; - char path[MAX_QPATH]; - - pi->currentWeapon = weaponNum; -tryagain: - pi->realWeapon = weaponNum; - pi->weaponModel = 0; - pi->barrelModel = 0; - pi->flashModel = 0; - - if ( weaponNum == WP_NONE ) { - return; - } - - if ( item->classname ) { - pi->weaponModel = trap_R_RegisterModel( item->world_model[0] ); - } - - if( pi->weaponModel == 0 ) { - if( weaponNum == WP_MACHINEGUN ) { - weaponNum = WP_NONE; - goto tryagain; - } - weaponNum = WP_MACHINEGUN; - goto tryagain; - } - - if ( weaponNum == WP_MACHINEGUN ) { - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_barrel.md3" ); - pi->barrelModel = trap_R_RegisterModel( path ); - } - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_flash.md3" ); - pi->flashModel = trap_R_RegisterModel( path ); - - switch( weaponNum ) { - case WP_GAUNTLET: - MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); - break; - - case WP_MACHINEGUN: - MAKERGB( pi->flashDlightColor, 1, 1, 0 ); - break; - - case WP_SHOTGUN: - MAKERGB( pi->flashDlightColor, 1, 1, 0 ); - break; - - case WP_GRENADE_LAUNCHER: - MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f ); - break; - - case WP_ROCKET_LAUNCHER: - MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 ); - break; - - case WP_TESLAGEN: - MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); - break; - - case WP_RAILGUN: - MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 ); - break; - - case WP_BFG: - MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 ); - break; - - case WP_GRAPPLING_HOOK: - MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); - break; - - default: - MAKERGB( pi->flashDlightColor, 1, 1, 1 ); - break; - }*/ -} - - -/* -=============== -UI_ForceLegsAnim -=============== -*/ -static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) { - pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; - - if ( anim == LEGS_JUMP ) { - pi->legsAnimationTimer = UI_TIMER_JUMP; - } -} - - -/* -=============== -UI_SetLegsAnim -=============== -*/ -static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) { - if ( pi->pendingLegsAnim ) { - anim = pi->pendingLegsAnim; - pi->pendingLegsAnim = 0; - } - UI_ForceLegsAnim( pi, anim ); -} - - -/* -=============== -UI_ForceTorsoAnim -=============== -*/ -static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) { - pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; - - if ( anim == TORSO_GESTURE ) { - pi->torsoAnimationTimer = UI_TIMER_GESTURE; - } - - if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) { - pi->torsoAnimationTimer = UI_TIMER_ATTACK; - } -} - - -/* -=============== -UI_SetTorsoAnim -=============== -*/ -static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) { - if ( pi->pendingTorsoAnim ) { - anim = pi->pendingTorsoAnim; - pi->pendingTorsoAnim = 0; - } - - UI_ForceTorsoAnim( pi, anim ); -} - - -/* -=============== -UI_TorsoSequencing -=============== -*/ -static void UI_TorsoSequencing( playerInfo_t *pi ) { - int currentAnim; - - currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; - - if ( pi->weapon != pi->currentWeapon ) { - if ( currentAnim != TORSO_DROP ) { - pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; - UI_ForceTorsoAnim( pi, TORSO_DROP ); - } - } - - if ( pi->torsoAnimationTimer > 0 ) { - return; - } - - if( currentAnim == TORSO_GESTURE ) { - UI_SetTorsoAnim( pi, TORSO_STAND ); - return; - } - - if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) { - UI_SetTorsoAnim( pi, TORSO_STAND ); - return; - } - - if ( currentAnim == TORSO_DROP ) { - UI_PlayerInfo_SetWeapon( pi, pi->weapon ); - pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; - UI_ForceTorsoAnim( pi, TORSO_RAISE ); - return; - } - - if ( currentAnim == TORSO_RAISE ) { - UI_SetTorsoAnim( pi, TORSO_STAND ); - return; - } -} - - -/* -=============== -UI_LegsSequencing -=============== -*/ -static void UI_LegsSequencing( playerInfo_t *pi ) { - int currentAnim; - - currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; - - if ( pi->legsAnimationTimer > 0 ) { - if ( currentAnim == LEGS_JUMP ) { - jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP ); - } - return; - } - - if ( currentAnim == LEGS_JUMP ) { - UI_ForceLegsAnim( pi, LEGS_LAND ); - pi->legsAnimationTimer = UI_TIMER_LAND; - jumpHeight = 0; - return; - } - - if ( currentAnim == LEGS_LAND ) { - UI_SetLegsAnim( pi, LEGS_IDLE ); - return; - } -} - - -/* -====================== -UI_PositionEntityOnTag -====================== -*/ -static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - clipHandle_t parentModel, char *tagName ) { - int i; - orientation_t lerped; - - // lerp the tag - trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, - 1.0 - parent->backlerp, tagName ); - - // FIXME: allow origin offsets along tag? - VectorCopy( parent->origin, entity->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); - } - - // cast away const because of compiler problems - MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis ); - entity->backlerp = parent->backlerp; -} - - -/* -====================== -UI_PositionRotatedEntityOnTag -====================== -*/ -static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - clipHandle_t parentModel, char *tagName ) { - int i; - orientation_t lerped; - vec3_t tempAxis[3]; - - // lerp the tag - trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, - 1.0 - parent->backlerp, tagName ); - - // FIXME: allow origin offsets along tag? - VectorCopy( parent->origin, entity->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); - } - - // cast away const because of compiler problems - MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis ); - MatrixMultiply( lerped.axis, tempAxis, entity->axis ); -} - - -/* -=============== -UI_SetLerpFrameAnimation -=============== -*/ -static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { - animation_t *anim; - - lf->animationNumber = newAnimation; - newAnimation &= ~ANIM_TOGGLEBIT; - - if ( newAnimation < 0 || newAnimation >= MAX_PLAYER_ANIMATIONS ) { - trap_Error( va("Bad animation number: %i", newAnimation) ); - } - - anim = &ci->animations[ newAnimation ]; - - lf->animation = anim; - lf->animationTime = lf->frameTime + anim->initialLerp; -} - - -/* -=============== -UI_RunLerpFrame -=============== -*/ -static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { - int f; - animation_t *anim; - - // see if the animation sequence is switching - if ( newAnimation != lf->animationNumber || !lf->animation ) { - UI_SetLerpFrameAnimation( ci, lf, newAnimation ); - } - - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if ( dp_realtime >= lf->frameTime ) { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; - - // get the next frame based on the animation - anim = lf->animation; - if ( dp_realtime < lf->animationTime ) { - lf->frameTime = lf->animationTime; // initial lerp - } else { - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - } - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - if ( f >= anim->numFrames ) { - f -= anim->numFrames; - if ( anim->loopFrames ) { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } else { - f = anim->numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = dp_realtime; - } - } - lf->frame = anim->firstFrame + f; - if ( dp_realtime > lf->frameTime ) { - lf->frameTime = dp_realtime; - } - } - - if ( lf->frameTime > dp_realtime + 200 ) { - lf->frameTime = dp_realtime; - } - - if ( lf->oldFrameTime > dp_realtime ) { - lf->oldFrameTime = dp_realtime; - } - // calculate current lerp value - if ( lf->frameTime == lf->oldFrameTime ) { - lf->backlerp = 0; - } else { - lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); - } -} - - -/* -=============== -UI_PlayerAnimation -=============== -*/ -static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp, - int *torsoOld, int *torso, float *torsoBackLerp ) { - - // legs animation - pi->legsAnimationTimer -= uiInfo.uiDC.frameTime; - if ( pi->legsAnimationTimer < 0 ) { - pi->legsAnimationTimer = 0; - } - - UI_LegsSequencing( pi ); - - if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { - UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN ); - } else { - UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim ); - } - *legsOld = pi->legs.oldFrame; - *legs = pi->legs.frame; - *legsBackLerp = pi->legs.backlerp; - - // torso animation - pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime; - if ( pi->torsoAnimationTimer < 0 ) { - pi->torsoAnimationTimer = 0; - } - - UI_TorsoSequencing( pi ); - - UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim ); - *torsoOld = pi->torso.oldFrame; - *torso = pi->torso.frame; - *torsoBackLerp = pi->torso.backlerp; -} - - -/* -================== -UI_SwingAngles -================== -*/ -static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance, - float speed, float *angle, qboolean *swinging ) { - float swing; - float move; - float scale; - - if ( !*swinging ) { - // see if a swing should be started - swing = AngleSubtract( *angle, destination ); - if ( swing > swingTolerance || swing < -swingTolerance ) { - *swinging = qtrue; - } - } - - if ( !*swinging ) { - return; - } - - // modify the speed depending on the delta - // so it doesn't seem so linear - swing = AngleSubtract( destination, *angle ); - scale = fabs( swing ); - if ( scale < swingTolerance * 0.5 ) { - scale = 0.5; - } else if ( scale < swingTolerance ) { - scale = 1.0; - } else { - scale = 2.0; - } - - // swing towards the destination angle - if ( swing >= 0 ) { - move = uiInfo.uiDC.frameTime * scale * speed; - if ( move >= swing ) { - move = swing; - *swinging = qfalse; - } - *angle = AngleMod( *angle + move ); - } else if ( swing < 0 ) { - move = uiInfo.uiDC.frameTime * scale * -speed; - if ( move <= swing ) { - move = swing; - *swinging = qfalse; - } - *angle = AngleMod( *angle + move ); - } - - // clamp to no more than tolerance - swing = AngleSubtract( destination, *angle ); - if ( swing > clampTolerance ) { - *angle = AngleMod( destination - (clampTolerance - 1) ); - } else if ( swing < -clampTolerance ) { - *angle = AngleMod( destination + (clampTolerance - 1) ); - } -} - - -/* -====================== -UI_MovedirAdjustment -====================== -*/ -static float UI_MovedirAdjustment( playerInfo_t *pi ) { - vec3_t relativeAngles; - vec3_t moveVector; - - VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles ); - AngleVectors( relativeAngles, moveVector, NULL, NULL ); - if ( Q_fabs( moveVector[0] ) < 0.01 ) { - moveVector[0] = 0.0; - } - if ( Q_fabs( moveVector[1] ) < 0.01 ) { - moveVector[1] = 0.0; - } - - if ( moveVector[1] == 0 && moveVector[0] > 0 ) { - return 0; - } - if ( moveVector[1] < 0 && moveVector[0] > 0 ) { - return 22; - } - if ( moveVector[1] < 0 && moveVector[0] == 0 ) { - return 45; - } - if ( moveVector[1] < 0 && moveVector[0] < 0 ) { - return -22; - } - if ( moveVector[1] == 0 && moveVector[0] < 0 ) { - return 0; - } - if ( moveVector[1] > 0 && moveVector[0] < 0 ) { - return 22; - } - if ( moveVector[1] > 0 && moveVector[0] == 0 ) { - return -45; - } - - return -22; -} - - -/* -=============== -UI_PlayerAngles -=============== -*/ -static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { - vec3_t legsAngles, torsoAngles, headAngles; - float dest; - float adjust; - - VectorCopy( pi->viewAngles, headAngles ); - headAngles[YAW] = AngleMod( headAngles[YAW] ); - VectorClear( legsAngles ); - VectorClear( torsoAngles ); - - // --------- yaw ------------- - - // allow yaw to drift a bit - if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE - || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { - // if not standing still, always point all in the same direction - pi->torso.yawing = qtrue; // always center - pi->torso.pitching = qtrue; // always center - pi->legs.yawing = qtrue; // always center - } - - // adjust legs for movement dir - adjust = UI_MovedirAdjustment( pi ); - legsAngles[YAW] = headAngles[YAW] + adjust; - torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust; - - - // torso - UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing ); - UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing ); - - torsoAngles[YAW] = pi->torso.yawAngle; - legsAngles[YAW] = pi->legs.yawAngle; - - // --------- pitch ------------- - - // only show a fraction of the pitch angle in the torso - if ( headAngles[PITCH] > 180 ) { - dest = (-360 + headAngles[PITCH]) * 0.75; - } else { - dest = headAngles[PITCH] * 0.75; - } - UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching ); - torsoAngles[PITCH] = pi->torso.pitchAngle; - - // pull the angles back out of the hierarchial chain - AnglesSubtract( headAngles, torsoAngles, headAngles ); - AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); - AnglesToAxis( legsAngles, legs ); - AnglesToAxis( torsoAngles, torso ); - AnglesToAxis( headAngles, head ); -} - - -/* -=============== -UI_PlayerFloatSprite -=============== -*/ -static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) { - refEntity_t ent; - - memset( &ent, 0, sizeof( ent ) ); - VectorCopy( origin, ent.origin ); - ent.origin[2] += 48; - ent.reType = RT_SPRITE; - ent.customShader = shader; - ent.radius = 10; - ent.renderfx = 0; - trap_R_AddRefEntityToScene( &ent ); -} - - -/* -====================== -UI_MachinegunSpinAngle -====================== -*/ -float UI_MachinegunSpinAngle( playerInfo_t *pi ) { - int delta; - float angle; - float speed; - int torsoAnim; - - delta = dp_realtime - pi->barrelTime; - if ( pi->barrelSpinning ) { - angle = pi->barrelAngle + delta * SPIN_SPEED; - } else { - if ( delta > COAST_TIME ) { - delta = COAST_TIME; - } - - speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); - angle = pi->barrelAngle + delta * speed; - } - - torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; - if( torsoAnim == TORSO_ATTACK2 ) { - torsoAnim = TORSO_ATTACK; - } - if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) { - pi->barrelTime = dp_realtime; - pi->barrelAngle = AngleMod( angle ); - pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK); - } - - return angle; -} - - -/* -=============== -UI_DrawPlayer -=============== -*/ -void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) { - refdef_t refdef; - refEntity_t legs; - refEntity_t torso; - refEntity_t head; - refEntity_t gun; - refEntity_t barrel; - refEntity_t flash; - vec3_t origin; - int renderfx; - vec3_t mins = {-16, -16, -24}; - vec3_t maxs = {16, 16, 32}; - float len; - float xx; - - if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) { - return; - } - - // this allows the ui to cache the player model on the main menu - if (w == 0 || h == 0) { - return; - } - - dp_realtime = time; - - if ( pi->pendingWeapon != WP_NUM_WEAPONS && dp_realtime > pi->weaponTimer ) { - pi->weapon = pi->pendingWeapon; - pi->lastWeapon = pi->pendingWeapon; - pi->pendingWeapon = WP_NUM_WEAPONS; - pi->weaponTimer = 0; - if( pi->currentWeapon != pi->weapon ) { - trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL ); - } - } - - UI_AdjustFrom640( &x, &y, &w, &h ); - - y -= jumpHeight; - - memset( &refdef, 0, sizeof( refdef ) ); - memset( &legs, 0, sizeof(legs) ); - memset( &torso, 0, sizeof(torso) ); - memset( &head, 0, sizeof(head) ); - - refdef.rdflags = RDF_NOWORLDMODEL; - - AxisClear( refdef.viewaxis ); - - refdef.x = x; - refdef.y = y; - refdef.width = w; - refdef.height = h; - - refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); - xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); - refdef.fov_y = atan2( refdef.height, xx ); - refdef.fov_y *= ( 360 / (float)M_PI ); - - // calculate distance so the player nearly fills the box - len = 0.7 * ( maxs[2] - mins[2] ); - origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - - refdef.time = dp_realtime; - - trap_R_ClearScene(); - - // get the rotation information - UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis ); - - // get the animation state (after rotation, to allow feet shuffle) - UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp, - &torso.oldframe, &torso.frame, &torso.backlerp ); - - renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; - - // - // add the legs - // - legs.hModel = pi->legsModel; - legs.customSkin = pi->legsSkin; - - VectorCopy( origin, legs.origin ); - - VectorCopy( origin, legs.lightingOrigin ); - legs.renderfx = renderfx; - VectorCopy (legs.origin, legs.oldorigin); - - trap_R_AddRefEntityToScene( &legs ); - - if (!legs.hModel) { - return; - } - - // - // add the torso - // - torso.hModel = pi->torsoModel; - if (!torso.hModel) { - return; - } - - torso.customSkin = pi->torsoSkin; - - VectorCopy( origin, torso.lightingOrigin ); - - UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso"); - - torso.renderfx = renderfx; - - trap_R_AddRefEntityToScene( &torso ); - - // - // add the head - // - head.hModel = pi->headModel; - if (!head.hModel) { - return; - } - head.customSkin = pi->headSkin; - - VectorCopy( origin, head.lightingOrigin ); - - UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head"); - - head.renderfx = renderfx; - - trap_R_AddRefEntityToScene( &head ); - - // - // add the gun - // - if ( pi->currentWeapon != WP_NONE ) { - memset( &gun, 0, sizeof(gun) ); - gun.hModel = pi->weaponModel; - VectorCopy( origin, gun.lightingOrigin ); - UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon"); - gun.renderfx = renderfx; - trap_R_AddRefEntityToScene( &gun ); - } - - // - // add the spinning barrel - // - if ( pi->realWeapon == WP_MACHINEGUN ) { - vec3_t angles; - - memset( &barrel, 0, sizeof(barrel) ); - VectorCopy( origin, barrel.lightingOrigin ); - barrel.renderfx = renderfx; - - barrel.hModel = pi->barrelModel; - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = UI_MachinegunSpinAngle( pi ); -/* if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) { - angles[PITCH] = angles[ROLL]; - angles[ROLL] = 0; - }*/ - AnglesToAxis( angles, barrel.axis ); - - UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel"); - - trap_R_AddRefEntityToScene( &barrel ); - } - - // - // add muzzle flash - // - if ( dp_realtime <= pi->muzzleFlashTime ) { - if ( pi->flashModel ) { - memset( &flash, 0, sizeof(flash) ); - flash.hModel = pi->flashModel; - VectorCopy( origin, flash.lightingOrigin ); - UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash"); - flash.renderfx = renderfx; - trap_R_AddRefEntityToScene( &flash ); - } - - // make a dlight for the flash - if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) { - trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0], - pi->flashDlightColor[1], pi->flashDlightColor[2] ); - } - } - - // - // add the chat icon - // - if ( pi->chat ) { - UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) ); - } - - // - // add an accent light - // - origin[0] -= 100; // + = behind, - = in front - origin[1] += 100; // + = left, - = right - origin[2] += 100; // + = above, - = below - trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 ); - - origin[0] -= 100; - origin[1] -= 100; - origin[2] -= 100; - trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 ); - - trap_R_RenderScene( &refdef ); -} - -/* -========================== -UI_FileExists -========================== -*/ -static qboolean UI_FileExists(const char *filename) { - int len; - - len = trap_FS_FOpenFile( filename, NULL, FS_READ ); - if (len>0) { - return qtrue; - } - return qfalse; -} - -/* -========================== -UI_FindClientHeadFile -========================== -*/ -static qboolean UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { - char *team, *headsFolder; - int i; - - team = "default"; - - if ( headModelName[0] == '*' ) { - headsFolder = "heads/"; - headModelName++; - } - else { - headsFolder = ""; - } - while(1) { - for ( i = 0; i < 2; i++ ) { - if ( i == 0 && teamName && *teamName ) { - Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); - } - else { - Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); - } - if ( UI_FileExists( filename ) ) { - return qtrue; - } - if ( i == 0 && teamName && *teamName ) { - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); - } - else { - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); - } - if ( UI_FileExists( filename ) ) { - return qtrue; - } - if ( !teamName || !*teamName ) { - break; - } - } - // if tried the heads folder first - if ( headsFolder[0] ) { - break; - } - headsFolder = "heads/"; - } - - return qfalse; -} - -/* -========================== -UI_RegisterClientSkin -========================== -*/ -static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) { - char filename[MAX_QPATH*2]; - - if (teamName && *teamName) { - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName ); - } else { - Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); - } - pi->legsSkin = trap_R_RegisterSkin( filename ); - if (!pi->legsSkin) { - if (teamName && *teamName) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName ); - } else { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName ); - } - pi->legsSkin = trap_R_RegisterSkin( filename ); - } - - if (teamName && *teamName) { - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName ); - } else { - Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); - } - pi->torsoSkin = trap_R_RegisterSkin( filename ); - if (!pi->torsoSkin) { - if (teamName && *teamName) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName ); - } else { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName ); - } - pi->torsoSkin = trap_R_RegisterSkin( filename ); - } - - if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) { - pi->headSkin = trap_R_RegisterSkin( filename ); - } - - if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) { - return qfalse; - } - - return qtrue; -} - - -/* -====================== -UI_ParseAnimationFile -====================== -*/ -static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) { - char *text_p, *prev; - int len; - int i; - char *token; - float fps; - int skip; - char text[20000]; - fileHandle_t f; - - memset( animations, 0, sizeof( animation_t ) * MAX_PLAYER_ANIMATIONS ); - - // load the file - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( len <= 0 ) { - return qfalse; - } - if ( len >= ( sizeof( text ) - 1 ) ) { - Com_Printf( "File %s too long\n", filename ); - trap_FS_FCloseFile( f ); - return qfalse; - } - trap_FS_Read( text, len, f ); - text[len] = 0; - trap_FS_FCloseFile( f ); - - COM_Compress(text); - - // parse the text - text_p = text; - skip = 0; // quite the compiler warning - - // read optional parameters - while ( 1 ) { - prev = text_p; // so we can unget - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( !Q_stricmp( token, "footsteps" ) ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - continue; - } else if ( !Q_stricmp( token, "headoffset" ) ) { - for ( i = 0 ; i < 3 ; i++ ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - } - continue; - } else if ( !Q_stricmp( token, "sex" ) ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - continue; - } - - // if it is a number, start parsing animations - if ( token[0] >= '0' && token[0] <= '9' ) { - text_p = prev; // unget the token - break; - } - - Com_Printf( "unknown token '%s' is %s\n", token, filename ); - } - - // read information for each frame - for ( i = 0 ; i < MAX_PLAYER_ANIMATIONS ; i++ ) { - - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - animations[i].firstFrame = atoi( token ); - // leg only frames are adjusted to not count the upper body only frames - if ( i == LEGS_WALKCR ) { - skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; - } - if ( i >= LEGS_WALKCR ) { - animations[i].firstFrame -= skip; - } - - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - animations[i].numFrames = atoi( token ); - - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - animations[i].loopFrames = atoi( token ); - - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - fps = atof( token ); - if ( fps == 0 ) { - fps = 1; - } - animations[i].frameLerp = 1000 / fps; - animations[i].initialLerp = 1000 / fps; - } - - if ( i != MAX_PLAYER_ANIMATIONS ) { - Com_Printf( "Error parsing animation file: %s", filename ); - return qfalse; - } - - return qtrue; -} - -/* -========================== -UI_RegisterClientModelname -========================== -*/ -qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) { - char modelName[MAX_QPATH]; - char skinName[MAX_QPATH]; - char headModelName[MAX_QPATH]; - char headSkinName[MAX_QPATH]; - char filename[MAX_QPATH]; - char *slash; - - pi->torsoModel = 0; - pi->headModel = 0; - - if ( !modelSkinName[0] ) { - return qfalse; - } - - Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) ); - - slash = strchr( modelName, '/' ); - if ( !slash ) { - // modelName did not include a skin name - Q_strncpyz( skinName, "default", sizeof( skinName ) ); - } else { - Q_strncpyz( skinName, slash + 1, sizeof( skinName ) ); - *slash = '\0'; - } - - Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) ); - slash = strchr( headModelName, '/' ); - if ( !slash ) { - // modelName did not include a skin name - Q_strncpyz( headSkinName, "default", sizeof( skinName ) ); - } else { - Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) ); - *slash = '\0'; - } - - // load cmodels before models so filecache works - - Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); - pi->legsModel = trap_R_RegisterModel( filename ); - if ( !pi->legsModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); - pi->legsModel = trap_R_RegisterModel( filename ); - if ( !pi->legsModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - } - - Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); - pi->torsoModel = trap_R_RegisterModel( filename ); - if ( !pi->torsoModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); - pi->torsoModel = trap_R_RegisterModel( filename ); - if ( !pi->torsoModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - } - - if (headModelName[0] == '*' ) { - Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); - } - else { - Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName ); - } - pi->headModel = trap_R_RegisterModel( filename ); - if ( !pi->headModel && headModelName[0] != '*') { - Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); - pi->headModel = trap_R_RegisterModel( filename ); - } - - if (!pi->headModel) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - - // if any skins failed to load, fall back to default - if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) { - if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) { - Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); - return qfalse; - } - } - - // load the animations - Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); - if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); - if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { - Com_Printf( "Failed to load animation file %s\n", filename ); - return qfalse; - } - } - - return qtrue; -} - - -/* -=============== -UI_PlayerInfo_SetModel -=============== -*/ -void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) { - memset( pi, 0, sizeof(*pi) ); - UI_RegisterClientModelname( pi, model, headmodel, teamName ); - pi->weapon = WP_MACHINEGUN; - pi->currentWeapon = pi->weapon; - pi->lastWeapon = pi->weapon; - pi->pendingWeapon = -1; - pi->weaponTimer = 0; - pi->chat = qfalse; - pi->newModel = qtrue; - UI_PlayerInfo_SetWeapon( pi, pi->weapon ); -} - - -/* -=============== -UI_PlayerInfo_SetInfo -=============== -*/ -void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) { - int currentAnim; - weapon_t weaponNum; - - pi->chat = chat; - - // view angles - VectorCopy( viewAngles, pi->viewAngles ); - - // move angles - VectorCopy( moveAngles, pi->moveAngles ); - - if ( pi->newModel ) { - pi->newModel = qfalse; - - jumpHeight = 0; - pi->pendingLegsAnim = 0; - UI_ForceLegsAnim( pi, legsAnim ); - pi->legs.yawAngle = viewAngles[YAW]; - pi->legs.yawing = qfalse; - - pi->pendingTorsoAnim = 0; - UI_ForceTorsoAnim( pi, torsoAnim ); - pi->torso.yawAngle = viewAngles[YAW]; - pi->torso.yawing = qfalse; - - if ( weaponNumber != WP_NUM_WEAPONS ) { - pi->weapon = weaponNumber; - pi->currentWeapon = weaponNumber; - pi->lastWeapon = weaponNumber; - pi->pendingWeapon = WP_NUM_WEAPONS; - pi->weaponTimer = 0; - UI_PlayerInfo_SetWeapon( pi, pi->weapon ); - } - - return; - } - - // weapon - if ( weaponNumber == WP_NUM_WEAPONS ) { - pi->pendingWeapon = WP_NUM_WEAPONS; - pi->weaponTimer = 0; - } - else if ( weaponNumber != WP_NONE ) { - pi->pendingWeapon = weaponNumber; - pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY; - } - weaponNum = pi->lastWeapon; - pi->weapon = weaponNum; - - if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) { - torsoAnim = legsAnim = BOTH_DEATH1; - pi->weapon = pi->currentWeapon = WP_NONE; - UI_PlayerInfo_SetWeapon( pi, pi->weapon ); - - jumpHeight = 0; - pi->pendingLegsAnim = 0; - UI_ForceLegsAnim( pi, legsAnim ); - - pi->pendingTorsoAnim = 0; - UI_ForceTorsoAnim( pi, torsoAnim ); - - return; - } - - // leg animation - currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; - if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) { - pi->pendingLegsAnim = legsAnim; - } - else if ( legsAnim != currentAnim ) { - jumpHeight = 0; - pi->pendingLegsAnim = 0; - UI_ForceLegsAnim( pi, legsAnim ); - } - - // torso animation - if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) { - if ( weaponNum == WP_NONE ) { - torsoAnim = TORSO_STAND2; - } - else { - torsoAnim = TORSO_STAND; - } - } - - if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) { - if ( weaponNum == WP_NONE ) { - torsoAnim = TORSO_ATTACK2; - } - else { - torsoAnim = TORSO_ATTACK; - } - pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH; - //FIXME play firing sound here - } - - currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; - - if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) { - pi->pendingTorsoAnim = torsoAnim; - } - else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) { - pi->pendingTorsoAnim = torsoAnim; - } - else if ( torsoAnim != currentAnim ) { - pi->pendingTorsoAnim = 0; - UI_ForceTorsoAnim( pi, torsoAnim ); - } -} diff --git a/src/ui/ui_public.h b/src/ui/ui_public.h index f62b8b9..2f85e10 100644 --- a/src/ui/ui_public.h +++ b/src/ui/ui_public.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,195 +17,189 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ -#ifndef __UI_PUBLIC_H__ -#define __UI_PUBLIC_H__ +#ifndef UI_PUBLIC_H +#define UI_PUBLIC_H + +#include "qcommon/q_shared.h" -#define UI_API_VERSION 6 +#define UI_API_VERSION 6 typedef struct { - connstate_t connState; - int connectPacketCount; - int clientNum; - char servername[MAX_STRING_CHARS]; - char updateInfoString[MAX_STRING_CHARS]; - char messageString[MAX_STRING_CHARS]; + connstate_t connState; + int connectPacketCount; + int clientNum; + char servername[MAX_STRING_CHARS]; + char updateInfoString[MAX_STRING_CHARS]; + char messageString[MAX_STRING_CHARS]; } uiClientState_t; typedef enum { - UI_ERROR, - UI_PRINT, - UI_MILLISECONDS, - UI_CVAR_SET, - UI_CVAR_VARIABLEVALUE, - UI_CVAR_VARIABLESTRINGBUFFER, - UI_CVAR_SETVALUE, - UI_CVAR_RESET, - UI_CVAR_CREATE, - UI_CVAR_INFOSTRINGBUFFER, - UI_ARGC, - UI_ARGV, - UI_CMD_EXECUTETEXT, - UI_FS_FOPENFILE, - UI_FS_READ, - UI_FS_WRITE, - UI_FS_FCLOSEFILE, - UI_FS_GETFILELIST, - UI_R_REGISTERMODEL, - UI_R_REGISTERSKIN, - UI_R_REGISTERSHADERNOMIP, - UI_R_CLEARSCENE, - UI_R_ADDREFENTITYTOSCENE, - UI_R_ADDPOLYTOSCENE, - UI_R_ADDLIGHTTOSCENE, - UI_R_RENDERSCENE, - UI_R_SETCOLOR, + UI_ERROR, + UI_PRINT, + UI_MILLISECONDS, + UI_CVAR_SET, + UI_CVAR_VARIABLEVALUE, + UI_CVAR_VARIABLESTRINGBUFFER, + UI_CVAR_SETVALUE, + UI_CVAR_RESET, + UI_CVAR_CREATE, + UI_CVAR_INFOSTRINGBUFFER, + UI_ARGC, + UI_ARGV, + UI_CMD_EXECUTETEXT, + UI_FS_FOPENFILE, + UI_FS_READ, + UI_FS_WRITE, + UI_FS_FCLOSEFILE, + UI_FS_GETFILELIST, + UI_R_REGISTERMODEL, + UI_R_REGISTERSKIN, + UI_R_REGISTERSHADERNOMIP, + UI_R_CLEARSCENE, + UI_R_ADDREFENTITYTOSCENE, + UI_R_ADDPOLYTOSCENE, + UI_R_ADDLIGHTTOSCENE, + UI_R_RENDERSCENE, + UI_R_SETCOLOR, #ifndef MODULE_INTERFACE_11 - UI_R_SETCLIPREGION, + UI_R_SETCLIPREGION, #endif - UI_R_DRAWSTRETCHPIC, - UI_UPDATESCREEN, - UI_CM_LERPTAG, - UI_CM_LOADMODEL, - UI_S_REGISTERSOUND, - UI_S_STARTLOCALSOUND, - UI_KEY_KEYNUMTOSTRINGBUF, - UI_KEY_GETBINDINGBUF, - UI_KEY_SETBINDING, - UI_KEY_ISDOWN, - UI_KEY_GETOVERSTRIKEMODE, - UI_KEY_SETOVERSTRIKEMODE, - UI_KEY_CLEARSTATES, - UI_KEY_GETCATCHER, - UI_KEY_SETCATCHER, - UI_GETCLIPBOARDDATA, - UI_GETGLCONFIG, - UI_GETCLIENTSTATE, - UI_GETCONFIGSTRING, - UI_LAN_GETPINGQUEUECOUNT, - UI_LAN_CLEARPING, - UI_LAN_GETPING, - UI_LAN_GETPINGINFO, - UI_CVAR_REGISTER, - UI_CVAR_UPDATE, - UI_MEMORY_REMAINING, - UI_R_REGISTERFONT, - UI_R_MODELBOUNDS, + UI_R_DRAWSTRETCHPIC, + UI_UPDATESCREEN, + UI_CM_LERPTAG, + UI_CM_LOADMODEL, + UI_S_REGISTERSOUND, + UI_S_STARTLOCALSOUND, + UI_KEY_KEYNUMTOSTRINGBUF, + UI_KEY_GETBINDINGBUF, + UI_KEY_SETBINDING, + UI_KEY_ISDOWN, + UI_KEY_GETOVERSTRIKEMODE, + UI_KEY_SETOVERSTRIKEMODE, + UI_KEY_CLEARSTATES, + UI_KEY_GETCATCHER, + UI_KEY_SETCATCHER, + UI_GETCLIPBOARDDATA, + UI_GETGLCONFIG, + UI_GETCLIENTSTATE, + UI_GETCONFIGSTRING, + UI_LAN_GETPINGQUEUECOUNT, + UI_LAN_CLEARPING, + UI_LAN_GETPING, + UI_LAN_GETPINGINFO, + UI_CVAR_REGISTER, + UI_CVAR_UPDATE, + UI_MEMORY_REMAINING, + UI_R_REGISTERFONT, + UI_R_MODELBOUNDS, #ifdef MODULE_INTERFACE_11 - UI_PARSE_ADD_GLOBAL_DEFINE, - UI_PARSE_LOAD_SOURCE, - UI_PARSE_FREE_SOURCE, - UI_PARSE_READ_TOKEN, - UI_PARSE_SOURCE_FILE_AND_LINE, + UI_PARSE_ADD_GLOBAL_DEFINE, + UI_PARSE_LOAD_SOURCE, + UI_PARSE_FREE_SOURCE, + UI_PARSE_READ_TOKEN, + UI_PARSE_SOURCE_FILE_AND_LINE, #endif - UI_S_STOPBACKGROUNDTRACK, - UI_S_STARTBACKGROUNDTRACK, - UI_REAL_TIME, - UI_LAN_GETSERVERCOUNT, - UI_LAN_GETSERVERADDRESSSTRING, - UI_LAN_GETSERVERINFO, - UI_LAN_MARKSERVERVISIBLE, - UI_LAN_UPDATEVISIBLEPINGS, - UI_LAN_RESETPINGS, - UI_LAN_LOADCACHEDSERVERS, - UI_LAN_SAVECACHEDSERVERS, - UI_LAN_ADDSERVER, - UI_LAN_REMOVESERVER, - UI_CIN_PLAYCINEMATIC, - UI_CIN_STOPCINEMATIC, - UI_CIN_RUNCINEMATIC, - UI_CIN_DRAWCINEMATIC, - UI_CIN_SETEXTENTS, - UI_R_REMAP_SHADER, - UI_LAN_SERVERSTATUS, - UI_LAN_GETSERVERPING, - UI_LAN_SERVERISVISIBLE, - UI_LAN_COMPARESERVERS, - // 1.32 - UI_FS_SEEK, - UI_SET_PBCLSTATUS, + UI_S_STOPBACKGROUNDTRACK, + UI_S_STARTBACKGROUNDTRACK, + UI_REAL_TIME, + UI_LAN_GETSERVERCOUNT, + UI_LAN_GETSERVERADDRESSSTRING, + UI_LAN_GETSERVERINFO, + UI_LAN_MARKSERVERVISIBLE, + UI_LAN_UPDATEVISIBLEPINGS, + UI_LAN_RESETPINGS, + UI_LAN_LOADCACHEDSERVERS, + UI_LAN_SAVECACHEDSERVERS, + UI_LAN_ADDSERVER, + UI_LAN_REMOVESERVER, + UI_CIN_PLAYCINEMATIC, + UI_CIN_STOPCINEMATIC, + UI_CIN_RUNCINEMATIC, + UI_CIN_DRAWCINEMATIC, + UI_CIN_SETEXTENTS, + UI_R_REMAP_SHADER, + UI_LAN_SERVERSTATUS, + UI_LAN_GETSERVERPING, + UI_LAN_SERVERISVISIBLE, + UI_LAN_COMPARESERVERS, + // 1.32 + UI_FS_SEEK, + UI_SET_PBCLSTATUS, #ifndef MODULE_INTERFACE_11 - UI_PARSE_ADD_GLOBAL_DEFINE, - UI_PARSE_LOAD_SOURCE, - UI_PARSE_FREE_SOURCE, - UI_PARSE_READ_TOKEN, - UI_PARSE_SOURCE_FILE_AND_LINE, - UI_GETNEWS, + UI_PARSE_ADD_GLOBAL_DEFINE, + UI_PARSE_LOAD_SOURCE, + UI_PARSE_FREE_SOURCE, + UI_PARSE_READ_TOKEN, + UI_PARSE_SOURCE_FILE_AND_LINE, + UI_GETNEWS, #endif - UI_MEMSET = 100, - UI_MEMCPY, - UI_STRNCPY, - UI_SIN, - UI_COS, - UI_ATAN2, - UI_SQRT, - UI_FLOOR, - UI_CEIL + // XXX THERE IS ROOM FOR _1_ (or 2? Did i count from 0?) + // SYSCALL BETWEEN UI_GETNEWS and UI_MEMSET + // UI_RESERVED_SYSCALL = 99, + + UI_MEMSET = 100, + UI_MEMCPY, + UI_STRNCPY, + UI_SIN, + UI_COS, + UI_ATAN2, + UI_SQRT, + UI_FLOOR, + UI_CEIL, } uiImport_t; -typedef enum { - UIMENU_NONE, - UIMENU_MAIN, - UIMENU_INGAME, - UIMENU_TEAM, - UIMENU_POSTGAME -} uiMenuCommand_t; - -typedef enum -{ - SORT_HOST, - SORT_MAP, - SORT_CLIENTS, - SORT_PING -} serverSortField_t; +typedef enum { UIMENU_NONE, UIMENU_MAIN, UIMENU_INGAME } uiMenuCommand_t; + +typedef enum { SORT_HOST, SORT_GAME, SORT_MAP, SORT_CLIENTS, SORT_PING } serverSortField_t; typedef enum { - UI_GETAPIVERSION = 0, // system reserved + UI_GETAPIVERSION = 0, // system reserved - UI_INIT, -// void UI_Init( void ); + UI_INIT, + // void UI_Init( void ); - UI_SHUTDOWN, -// void UI_Shutdown( void ); + UI_SHUTDOWN, + // void UI_Shutdown( void ); - UI_KEY_EVENT, -// void UI_KeyEvent( int key ); + UI_KEY_EVENT, + // void UI_KeyEvent( int key ); - UI_MOUSE_EVENT, + UI_MOUSE_EVENT, // void UI_MouseEvent( int dx, int dy ); #ifndef MODULE_INTERFACE_11 - UI_MOUSE_POSITION, -// int UI_MousePosition( void ); + UI_MOUSE_POSITION, + // int UI_MousePosition( void ); - UI_SET_MOUSE_POSITION, + UI_SET_MOUSE_POSITION, // void UI_SetMousePosition( int x, int y ); #endif - UI_REFRESH, -// void UI_Refresh( int time ); + UI_REFRESH, + // void UI_Refresh( int time ); - UI_IS_FULLSCREEN, -// qboolean UI_IsFullscreen( void ); + UI_IS_FULLSCREEN, + // qboolean UI_IsFullscreen( void ); - UI_SET_ACTIVE_MENU, -// void UI_SetActiveMenu( uiMenuCommand_t menu ); + UI_SET_ACTIVE_MENU, + // void UI_SetActiveMenu( uiMenuCommand_t menu ); - UI_CONSOLE_COMMAND, -// qboolean UI_ConsoleCommand( int realTime ); + UI_CONSOLE_COMMAND, + // qboolean UI_ConsoleCommand( int realTime ); - UI_DRAW_CONNECT_SCREEN -// void UI_DrawConnectScreen( qboolean overlay ); + UI_DRAW_CONNECT_SCREEN + // void UI_DrawConnectScreen( qboolean overlay ); -// if !overlay, the background will be drawn, otherwise it will be -// overlayed over whatever the cgame has drawn. -// a GetClientState syscall will be made to get the current strings + // if !overlay, the background will be drawn, otherwise it will be + // overlayed over whatever the cgame has drawn. + // a GetClientState syscall will be made to get the current strings } uiExport_t; #endif diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c index 5640e0e..8b6225e 100644 --- a/src/ui/ui_shared.c +++ b/src/ui/ui_shared.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,38 +17,40 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #include "ui_shared.h" -#define SCROLL_TIME_START 500 -#define SCROLL_TIME_ADJUST 150 -#define SCROLL_TIME_ADJUSTOFFSET 40 -#define SCROLL_TIME_FLOOR 20 - -typedef struct scrollInfo_s { - int nextScrollTime; - int nextAdjustTime; - int adjustValue; - int scrollKey; - float xStart; - float yStart; - itemDef_t *item; - qboolean scrollDir; +#define SCROLL_TIME_START 500 +#define SCROLL_TIME_ADJUST 150 +#define SCROLL_TIME_ADJUSTOFFSET 40 +#define SCROLL_TIME_FLOOR 20 + +typedef struct { + int nextScrollTime; + int nextAdjustTime; + int adjustValue; + int scrollKey; + float xStart; + float yStart; + itemDef_t *item; + qboolean scrollDir; } scrollInfo_t; static scrollInfo_t scrollInfo; -//TA: hack to prevent compiler warnings -void voidFunction( void *var ) { return; } -qboolean voidFunction2( itemDef_t *var1, int var2 ) { return qfalse; } +// prevent compiler warnings +void voidFunction(void *var) { return; } + +qboolean voidFunction2(itemDef_t *var1, int var2) { return qfalse; } -static void (*captureFunc) (void *p) = voidFunction; +static CaptureFunc *captureFunc = voidFunction; +static int captureFuncExpiry = 0; static void *captureData = NULL; -static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) +static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) displayContextDef_t *DC = NULL; @@ -56,20 +59,23 @@ static qboolean g_editingField = qfalse; static itemDef_t *g_bindItem = NULL; static itemDef_t *g_editItem = NULL; +static itemDef_t *g_comboBoxItem = NULL; -menuDef_t Menus[MAX_MENUS]; // defined menus -int menuCount = 0; // how many +menuDef_t Menus[MAX_MENUS]; // defined menus +int menuCount = 0; // how many menuDef_t *menuStack[MAX_OPEN_MENUS]; int openMenuCount = 0; -static qboolean debugMode = qfalse; - #define DOUBLE_CLICK_DELAY 300 static int lastListBoxClickTime = 0; +itemDataType_t Item_DataType(itemDef_t *item); void Item_RunScript(itemDef_t *item, const char *s); void Item_SetupKeywordHash(void); +static ID_INLINE qboolean Item_IsEditField(itemDef_t *item); +static ID_INLINE qboolean Item_IsListBox(itemDef_t *item); +static void Item_ListBox_SetStartPos(itemDef_t *item, int startPos); void Menu_SetupKeywordHash(void); int BindingIDFromName(const char *name); qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down); @@ -77,40 +83,68 @@ itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu); itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu); static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y); +/* +=============== +UI_InstallCaptureFunc +=============== +*/ +void UI_InstallCaptureFunc(CaptureFunc *f, void *data, int timeout) +{ + captureFunc = f; + captureData = data; + + if (timeout > 0) + captureFuncExpiry = DC->realTime + timeout; + else + captureFuncExpiry = 0; +} + +/* +=============== +UI_RemoveCaptureFunc +=============== +*/ +void UI_RemoveCaptureFunc(void) +{ + captureFunc = voidFunction; + captureData = NULL; + captureFuncExpiry = 0; +} + #ifdef CGAME -#define MEM_POOL_SIZE 128 * 1024 +#define MEM_POOL_SIZE 128 * 1024 #else -#define MEM_POOL_SIZE 1024 * 1024 +#define MEM_POOL_SIZE 1024 * 1024 #endif -//TA: hacked variable name to avoid conflict with new cgame Alloc -static char UI_memoryPool[MEM_POOL_SIZE]; -static int allocPoint, outOfMemory; +static char UI_memoryPool[MEM_POOL_SIZE]; +static int allocPoint, outOfMemory; /* =============== UI_Alloc =============== */ -void *UI_Alloc( int size ) +void *UI_Alloc(int size) { - char *p; + char *p; + + if (allocPoint + size > MEM_POOL_SIZE) + { + outOfMemory = qtrue; - if( allocPoint + size > MEM_POOL_SIZE ) - { - outOfMemory = qtrue; + if (DC->Print) + DC->Print("UI_Alloc: Failure. Out of memory!\n"); - if( DC->Print ) - DC->Print( "UI_Alloc: Failure. Out of memory!\n" ); - //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n"); - return NULL; - } + // DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n"); + return NULL; + } - p = &UI_memoryPool[ allocPoint ]; + p = &UI_memoryPool[allocPoint]; - allocPoint += ( size + 15 ) & ~15; + allocPoint += (size + 15) & ~15; - return p; + return p; } /* @@ -118,20 +152,13 @@ void *UI_Alloc( int size ) UI_InitMemory =============== */ -void UI_InitMemory( void ) -{ - allocPoint = 0; - outOfMemory = qfalse; -} - -qboolean UI_OutOfMemory( ) +void UI_InitMemory(void) { - return outOfMemory; + allocPoint = 0; + outOfMemory = qfalse; } - - - +qboolean UI_OutOfMemory() { return outOfMemory; } #define HASH_TABLE_SIZE 2048 /* @@ -139,25 +166,29 @@ qboolean UI_OutOfMemory( ) return a hash value for the string ================ */ -static long hashForString(const char *str) { - int i; - long hash; - char letter; - - hash = 0; - i = 0; - while (str[i] != '\0') { - letter = tolower(str[i]); - hash+=(long)(letter)*(i+119); - i++; - } - hash &= (HASH_TABLE_SIZE-1); - return hash; +static long hashForString(const char *str) +{ + int i; + long hash; + char letter; + + hash = 0; + i = 0; + + while (str[i] != '\0') + { + letter = tolower(str[i]); + hash += (long)(letter) * (i + 119); + i++; + } + + hash &= (HASH_TABLE_SIZE - 1); + return hash; } typedef struct stringDef_s { - struct stringDef_s *next; - const char *str; + struct stringDef_s *next; + const char *str; } stringDef_t; static int strPoolIndex = 0; @@ -166,69 +197,81 @@ static char strPool[STRING_POOL_SIZE]; static int strHandleCount = 0; static stringDef_t *strHandle[HASH_TABLE_SIZE]; +// Make a copy of a string for later use. Can safely be called on the +// same string repeatedly. Redundant on string literals or global +// constants. +const char *String_Alloc(const char *p) +{ + int len; + long hash; + stringDef_t *str, *last; -const char *String_Alloc(const char *p) { - int len; - long hash; - stringDef_t *str, *last; - static const char *staticNULL = ""; + if (p == NULL) + return NULL; - if (p == NULL) { - return NULL; - } + if (*p == 0) + return ""; - if (*p == 0) { - return staticNULL; - } + hash = hashForString(p); + + str = strHandle[hash]; - hash = hashForString(p); + while (str) + { + if (strcmp(p, str->str) == 0) + return str->str; - str = strHandle[hash]; - while (str) { - if (strcmp(p, str->str) == 0) { - return str->str; + str = str->next; } - str = str->next; - } - len = strlen(p); - if (len + strPoolIndex + 1 < STRING_POOL_SIZE) { - int ph = strPoolIndex; - strcpy(&strPool[strPoolIndex], p); - strPoolIndex += len + 1; + len = strlen(p); - str = strHandle[hash]; - last = str; - while (str && str->next) { - last = str; - str = str->next; - } - - str = UI_Alloc(sizeof(stringDef_t)); - str->next = NULL; - str->str = &strPool[ph]; - if (last) { - last->next = str; - } else { - strHandle[hash] = str; - } - return &strPool[ph]; - } - return NULL; -} - -void String_Report( void ) { - float f; - Com_Printf("Memory/String Pool Info\n"); - Com_Printf("----------------\n"); - f = strPoolIndex; - f /= STRING_POOL_SIZE; - f *= 100; - Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE); - f = allocPoint; - f /= MEM_POOL_SIZE; - f *= 100; - Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE); + if (len + strPoolIndex + 1 < STRING_POOL_SIZE) + { + int ph = strPoolIndex; + strcpy(&strPool[strPoolIndex], p); + strPoolIndex += len + 1; + + str = strHandle[hash]; + last = str; + + while (str && str->next) + { + last = str; + str = str->next; + } + + str = UI_Alloc(sizeof(stringDef_t)); + str->next = NULL; + str->str = &strPool[ph]; + + if (last) + last->next = str; + else + strHandle[hash] = str; + + return &strPool[ph]; + } + else + { + Com_Error(ERR_DROP, "String_Alloc( %s ): string pool full!", p); + return NULL; // not that we return at all + } +} + +void String_Report(void) +{ + float f; + Com_Printf("Memory/String Pool Info\n"); + Com_Printf("----------------\n"); + f = strPoolIndex; + f /= STRING_POOL_SIZE; + f *= 100; + Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE); + f = allocPoint; + f /= MEM_POOL_SIZE; + f *= 100; + Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE); } /* @@ -236,22 +279,29 @@ void String_Report( void ) { String_Init ================= */ -void String_Init( void ) +void String_Init(void) { - int i; - for( i = 0; i < HASH_TABLE_SIZE; i++ ) - strHandle[ i ] = 0; + int i; + + for (i = 0; i < HASH_TABLE_SIZE; i++) + strHandle[i] = 0; - strHandleCount = 0; - strPoolIndex = 0; - menuCount = 0; - openMenuCount = 0; - UI_InitMemory( ); - Item_SetupKeywordHash( ); - Menu_SetupKeywordHash( ); + strHandleCount = 0; - if( DC && DC->getBindingBuf ) - Controls_GetConfig( ); + strPoolIndex = 0; + + menuCount = 0; + + openMenuCount = 0; + + UI_InitMemory(); + + Item_SetupKeywordHash(); + + Menu_SetupKeywordHash(); + + if (DC && DC->getBindingBuf) + Controls_GetConfig(); } /* @@ -259,21 +309,22 @@ void String_Init( void ) PC_SourceWarning ================= */ -void PC_SourceWarning(int handle, char *format, ...) { - int line; - char filename[128]; - va_list argptr; - static char string[4096]; +__attribute__((format(printf, 2, 3))) void PC_SourceWarning(int handle, char *format, ...) +{ + int line; + char filename[128]; + va_list argptr; + static char string[4096]; - va_start (argptr, format); - vsprintf (string, format, argptr); - va_end (argptr); + va_start(argptr, format); + Q_vsnprintf(string, sizeof(string), format, argptr); + va_end(argptr); - filename[0] = '\0'; - line = 0; - trap_Parse_SourceFileAndLine(handle, filename, &line); + filename[0] = '\0'; + line = 0; + trap_Parse_SourceFileAndLine(handle, filename, &line); - Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string); + Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string); } /* @@ -281,21 +332,22 @@ void PC_SourceWarning(int handle, char *format, ...) { PC_SourceError ================= */ -void PC_SourceError(int handle, char *format, ...) { - int line; - char filename[128]; - va_list argptr; - static char string[4096]; +__attribute__((format(printf, 2, 3))) void PC_SourceError(int handle, char *format, ...) +{ + int line; + char filename[128]; + va_list argptr; + static char string[4096]; - va_start (argptr, format); - vsprintf (string, format, argptr); - va_end (argptr); + va_start(argptr, format); + Q_vsnprintf(string, sizeof(string), format, argptr); + va_end(argptr); - filename[0] = '\0'; - line = 0; - trap_Parse_SourceFileAndLine(handle, filename, &line); + filename[0] = '\0'; + line = 0; + trap_Parse_SourceFileAndLine(handle, filename, &line); - Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string); + Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string); } /* @@ -305,17 +357,19 @@ LerpColor */ void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t) { - int i; + int i; + + // lerp and clamp each component + + for (i = 0; i < 4; i++) + { + c[i] = a[i] + t * (b[i] - a[i]); - // lerp and clamp each component - for (i=0; i<4; i++) - { - c[i] = a[i] + t*(b[i]-a[i]); - if (c[i] < 0) - c[i] = 0; - else if (c[i] > 1.0) - c[i] = 1.0; - } + if (c[i] < 0) + c[i] = 0; + else if (c[i] > 1.0) + c[i] = 1.0; + } } /* @@ -323,15 +377,271 @@ void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t) Float_Parse ================= */ -qboolean Float_Parse(char **p, float *f) { - char *token; - token = COM_ParseExt(p, qfalse); - if (token && token[0] != 0) { - *f = atof(token); +qboolean Float_Parse(char **p, float *f) +{ + char *token; + token = COM_ParseExt(p, qfalse); + + if (token && token[0] != 0) + { + *f = atof(token); + return qtrue; + } + else + return qfalse; +} + +#define MAX_EXPR_ELEMENTS 32 + +typedef enum { EXPR_OPERATOR, EXPR_VALUE } exprType_t; + +typedef struct exprToken_s { + exprType_t type; + union { + char op; + float val; + } u; +} exprToken_t; + +typedef struct exprList_s { + exprToken_t l[MAX_EXPR_ELEMENTS]; + int f, b; +} exprList_t; + +/* +================= +OpPrec + +Return a value reflecting operator precedence +================= +*/ +static ID_INLINE int OpPrec(char op) +{ + switch (op) + { + case '*': + return 4; + + case '/': + return 3; + + case '+': + return 2; + + case '-': + return 1; + + case '(': + return 0; + + default: + return -1; + } +} + +/* +================= +PC_Expression_Parse +================= +*/ +static qboolean PC_Expression_Parse(int handle, float *f) +{ + pc_token_t token; + int unmatchedParentheses = 0; + exprList_t stack, fifo; + exprToken_t value; + qboolean expectingNumber = qtrue; + +#define FULL(a) (a.b >= (MAX_EXPR_ELEMENTS - 1)) +#define EMPTY(a) (a.f > a.b) + +#define PUSH_VAL(a, v) \ + { \ + if (FULL(a)) \ + return qfalse; \ + a.b++; \ + a.l[a.b].type = EXPR_VALUE; \ + a.l[a.b].u.val = v; \ + } + +#define PUSH_OP(a, o) \ + { \ + if (FULL(a)) \ + return qfalse; \ + a.b++; \ + a.l[a.b].type = EXPR_OPERATOR; \ + a.l[a.b].u.op = o; \ + } + +#define POP_STACK(a) \ + { \ + if (EMPTY(a)) \ + return qfalse; \ + value = a.l[a.b]; \ + a.b--; \ + } + +#define PEEK_STACK_OP(a) (a.l[a.b].u.op) +#define PEEK_STACK_VAL(a) (a.l[a.b].u.val) + +#define POP_FIFO(a) \ + { \ + if (EMPTY(a)) \ + return qfalse; \ + value = a.l[a.f]; \ + a.f++; \ + } + + stack.f = fifo.f = 0; + stack.b = fifo.b = -1; + + while (trap_Parse_ReadToken(handle, &token)) + { + if (!unmatchedParentheses && token.string[0] == ')') + break; + + // Special case to catch negative numbers + if (expectingNumber && token.string[0] == '-') + { + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + token.floatvalue = -token.floatvalue; + } + + if (token.type == TT_NUMBER) + { + if (!expectingNumber) + return qfalse; + + expectingNumber = !expectingNumber; + + PUSH_VAL(fifo, token.floatvalue); + } + else + { + switch (token.string[0]) + { + case '(': + unmatchedParentheses++; + PUSH_OP(stack, '('); + break; + + case ')': + unmatchedParentheses--; + + if (unmatchedParentheses < 0) + return qfalse; + + while (!EMPTY(stack) && PEEK_STACK_OP(stack) != '(') + { + POP_STACK(stack); + PUSH_OP(fifo, value.u.op); + } + + // Pop the '(' + POP_STACK(stack); + + break; + + case '*': + case '/': + case '+': + case '-': + if (expectingNumber) + return qfalse; + + expectingNumber = !expectingNumber; + + if (EMPTY(stack)) + { + PUSH_OP(stack, token.string[0]); + } + else + { + while (!EMPTY(stack) && OpPrec(token.string[0]) < OpPrec(PEEK_STACK_OP(stack))) + { + POP_STACK(stack); + PUSH_OP(fifo, value.u.op); + } + + PUSH_OP(stack, token.string[0]); + } + + break; + + default: + // Unknown token + return qfalse; + } + } + } + + while (!EMPTY(stack)) + { + POP_STACK(stack); + PUSH_OP(fifo, value.u.op); + } + + while (!EMPTY(fifo)) + { + POP_FIFO(fifo); + + if (value.type == EXPR_VALUE) + { + PUSH_VAL(stack, value.u.val); + } + else if (value.type == EXPR_OPERATOR) + { + char op = value.u.op; + float operand1, operand2, result; + + POP_STACK(stack); + operand2 = value.u.val; + POP_STACK(stack); + operand1 = value.u.val; + + switch (op) + { + case '*': + result = operand1 * operand2; + break; + + case '/': + result = operand1 / operand2; + break; + + case '+': + result = operand1 + operand2; + break; + + case '-': + result = operand1 - operand2; + break; + + default: + Com_Error(ERR_FATAL, "Unknown operator '%c' in postfix string", op); + return qfalse; + } + + PUSH_VAL(stack, result); + } + } + + POP_STACK(stack); + + *f = value.u.val; + return qtrue; - } else { - return qfalse; - } + +#undef FULL +#undef EMPTY +#undef PUSH_VAL +#undef PUSH_OP +#undef POP_STACK +#undef PEEK_STACK_OP +#undef PEEK_STACK_VAL +#undef POP_FIFO } /* @@ -339,26 +649,37 @@ qboolean Float_Parse(char **p, float *f) { PC_Float_Parse ================= */ -qboolean PC_Float_Parse(int handle, float *f) { - pc_token_t token; - int negative = qfalse; +qboolean PC_Float_Parse(int handle, float *f) +{ + pc_token_t token; + int negative = qfalse; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (token.string[0] == '-') { if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - negative = qtrue; - } - if (token.type != TT_NUMBER) { - PC_SourceError(handle, "expected float but found %s\n", token.string); - return qfalse; - } - if (negative) - *f = -token.floatvalue; - else - *f = token.floatvalue; - return qtrue; + return qfalse; + + if (token.string[0] == '(') + return PC_Expression_Parse(handle, f); + + if (token.string[0] == '-') + { + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + negative = qtrue; + } + + if (token.type != TT_NUMBER) + { + PC_SourceError(handle, "expected float but found %s\n", token.string); + return qfalse; + } + + if (negative) + *f = -token.floatvalue; + else + *f = token.floatvalue; + + return qtrue; } /* @@ -366,17 +687,20 @@ qboolean PC_Float_Parse(int handle, float *f) { Color_Parse ================= */ -qboolean Color_Parse(char **p, vec4_t *c) { - int i; - float f; +qboolean Color_Parse(char **p, vec4_t *c) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (!Float_Parse(p, &f)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!Float_Parse(p, &f)) { - return qfalse; + (*c)[i] = f; } - (*c)[i] = f; - } - return qtrue; + + return qtrue; } /* @@ -384,17 +708,20 @@ qboolean Color_Parse(char **p, vec4_t *c) { PC_Color_Parse ================= */ -qboolean PC_Color_Parse(int handle, vec4_t *c) { - int i; - float f; +qboolean PC_Color_Parse(int handle, vec4_t *c) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; + (*c)[i] = f; } - (*c)[i] = f; - } - return qtrue; + + return qtrue; } /* @@ -402,16 +729,18 @@ qboolean PC_Color_Parse(int handle, vec4_t *c) { Int_Parse ================= */ -qboolean Int_Parse(char **p, int *i) { - char *token; - token = COM_ParseExt(p, qfalse); +qboolean Int_Parse(char **p, int *i) +{ + char *token; + token = COM_ParseExt(p, qfalse); - if (token && token[0] != 0) { - *i = atoi(token); - return qtrue; - } else { - return qfalse; - } + if (token && token[0] != 0) + { + *i = atoi(token); + return qtrue; + } + else + return qfalse; } /* @@ -419,25 +748,47 @@ qboolean Int_Parse(char **p, int *i) { PC_Int_Parse ================= */ -qboolean PC_Int_Parse(int handle, int *i) { - pc_token_t token; - int negative = qfalse; +qboolean PC_Int_Parse(int handle, int *i) +{ + pc_token_t token; + int negative = qfalse; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (token.string[0] == '-') { if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - negative = qtrue; - } - if (token.type != TT_NUMBER) { - PC_SourceError(handle, "expected integer but found %s\n", token.string); - return qfalse; - } - *i = token.intvalue; - if (negative) - *i = - *i; - return qtrue; + return qfalse; + + if (token.string[0] == '(') + { + float f; + + if (PC_Expression_Parse(handle, &f)) + { + *i = (int)f; + return qtrue; + } + else + return qfalse; + } + + if (token.string[0] == '-') + { + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + negative = qtrue; + } + + if (token.type != TT_NUMBER) + { + PC_SourceError(handle, "expected integer but found %s\n", token.string); + return qfalse; + } + + *i = token.intvalue; + + if (negative) + *i = -*i; + + return qtrue; } /* @@ -445,17 +796,21 @@ qboolean PC_Int_Parse(int handle, int *i) { Rect_Parse ================= */ -qboolean Rect_Parse(char **p, rectDef_t *r) { - if (Float_Parse(p, &r->x)) { - if (Float_Parse(p, &r->y)) { - if (Float_Parse(p, &r->w)) { - if (Float_Parse(p, &r->h)) { - return qtrue; +qboolean Rect_Parse(char **p, rectDef_t *r) +{ + if (Float_Parse(p, &r->x)) + { + if (Float_Parse(p, &r->y)) + { + if (Float_Parse(p, &r->w)) + { + if (Float_Parse(p, &r->h)) + return qtrue; + } } - } } - } - return qfalse; + + return qfalse; } /* @@ -463,17 +818,21 @@ qboolean Rect_Parse(char **p, rectDef_t *r) { PC_Rect_Parse ================= */ -qboolean PC_Rect_Parse(int handle, rectDef_t *r) { - if (PC_Float_Parse(handle, &r->x)) { - if (PC_Float_Parse(handle, &r->y)) { - if (PC_Float_Parse(handle, &r->w)) { - if (PC_Float_Parse(handle, &r->h)) { - return qtrue; +qboolean PC_Rect_Parse(int handle, rectDef_t *r) +{ + if (PC_Float_Parse(handle, &r->x)) + { + if (PC_Float_Parse(handle, &r->y)) + { + if (PC_Float_Parse(handle, &r->w)) + { + if (PC_Float_Parse(handle, &r->h)) + return qtrue; + } } - } } - } - return qfalse; + + return qfalse; } /* @@ -481,15 +840,19 @@ qboolean PC_Rect_Parse(int handle, rectDef_t *r) { String_Parse ================= */ -qboolean String_Parse(char **p, const char **out) { - char *token; +qboolean String_Parse(char **p, const char **out) +{ + char *token; - token = COM_ParseExt(p, qfalse); - if (token && token[0] != 0) { - *(out) = String_Alloc(token); - return qtrue; - } - return qfalse; + token = COM_ParseExt(p, qfalse); + + if (token && token[0] != 0) + { + *(out) = String_Alloc(token); + return qtrue; + } + + return qfalse; } /* @@ -497,13 +860,15 @@ qboolean String_Parse(char **p, const char **out) { PC_String_Parse ================= */ -qboolean PC_String_Parse(int handle, const char **out) { - pc_token_t token; +qboolean PC_String_Parse(int handle, const char **out) +{ + pc_token_t token; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + *(out) = String_Alloc(token.string); - *(out) = String_Alloc(token.string); return qtrue; } @@ -512,37 +877,41 @@ qboolean PC_String_Parse(int handle, const char **out) { PC_Script_Parse ================= */ -qboolean PC_Script_Parse(int handle, const char **out) { - char script[1024]; - pc_token_t token; - - memset(script, 0, sizeof(script)); - // scripts start with { and have ; separated command lists.. commands are command, arg.. - // basically we want everything between the { } as it will be interpreted at run time +qboolean PC_Script_Parse(int handle, const char **out) +{ + char script[1024]; + pc_token_t token; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (Q_stricmp(token.string, "{") != 0) { - return qfalse; - } + memset(script, 0, sizeof(script)); + // scripts start with { and have ; separated command lists.. commands are command, arg.. + // basically we want everything between the { } as it will be interpreted at run time - while ( 1 ) { if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; + return qfalse; - if (Q_stricmp(token.string, "}") == 0) { - *out = String_Alloc(script); - return qtrue; - } + if (Q_stricmp(token.string, "{") != 0) + return qfalse; - if (token.string[1] != '\0') { - Q_strcat(script, 1024, va("\"%s\"", token.string)); - } else { - Q_strcat(script, 1024, token.string); + while (1) + { + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + if (Q_stricmp(token.string, "}") == 0) + { + *out = String_Alloc(script); + return qtrue; + } + + if (token.string[1] != '\0') + Q_strcat(script, 1024, va("\"%s\"", token.string)); + else + Q_strcat(script, 1024, token.string); + + Q_strcat(script, 1024, " "); } - Q_strcat(script, 1024, " "); - } - return qfalse; // bk001105 - LCC missing return value + + return qfalse; } // display, window, menu, item code @@ -555,5315 +924,6589 @@ Init_Display Initializes the display with a structure to all the drawing routines ================== */ -void Init_Display( displayContextDef_t *dc ) -{ - DC = dc; -} - - +void Init_Display(displayContextDef_t *dc) { DC = dc; } // type and style painting -void GradientBar_Paint( rectDef_t *rect, vec4_t color ) +void GradientBar_Paint(rectDef_t *rect, vec4_t color) { - // gradient bar takes two paints - DC->setColor( color ); - DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar ); - DC->setColor( NULL ); + // gradient bar takes two paints + DC->setColor(color); + DC->drawHandlePic(rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar); + DC->setColor(NULL); } - /* ================== Window_Init -Initializes a window structure ( windowDef_t ) with defaults - +Initializes a window structure ( Window ) with defaults ================== */ -void Window_Init(Window *w) { - memset(w, 0, sizeof(windowDef_t)); - w->borderSize = 1; - w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0; - w->cinematic = -1; -} - -void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount) { - if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN)) { - if (DC->realTime > *nextTime) { - *nextTime = DC->realTime + offsetTime; - if (*flags & WINDOW_FADINGOUT) { - *f -= fadeAmount; - if (bFlags && *f <= 0.0) { - *flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE); - } - } else { - *f += fadeAmount; - if (*f >= clamp) { - *f = clamp; - if (bFlags) { - *flags &= ~WINDOW_FADINGIN; - } - } - } - } - } -} - - - -void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) { - //float bordersize = 0; - vec4_t color; - rectDef_t fillRect = w->rect; - - - if (debugMode) { - color[0] = color[1] = color[2] = color[3] = 1; - DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color); - } - - if (w == NULL || (w->style == 0 && w->border == 0)) { - return; - } - - if (w->border != 0) { - fillRect.x += w->borderSize; - fillRect.y += w->borderSize; - fillRect.w -= w->borderSize + 1; - fillRect.h -= w->borderSize + 1; - } - - if (w->style == WINDOW_STYLE_FILLED) { - // box, but possible a shader that needs filled - if (w->background) { - Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount); - DC->setColor(w->backColor); - DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); - DC->setColor(NULL); - } else { - DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor); - } - } else if (w->style == WINDOW_STYLE_GRADIENT) { - GradientBar_Paint(&fillRect, w->backColor); - // gradient bar - } else if (w->style == WINDOW_STYLE_SHADER) { - if (w->flags & WINDOW_FORECOLORSET) { - DC->setColor(w->foreColor); - } - DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); - DC->setColor(NULL); - } else if (w->style == WINDOW_STYLE_TEAMCOLOR) { - if (DC->getTeamColor) { - DC->getTeamColor(&color); - DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, color); - } - } else if (w->style == WINDOW_STYLE_CINEMATIC) { - if (w->cinematic == -1) { - w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h); - if (w->cinematic == -1) { - w->cinematic = -2; - } - } - if (w->cinematic >= 0) { - DC->runCinematicFrame(w->cinematic); - DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h); - } - } - - if (w->border == WINDOW_BORDER_FULL) { - // full - // HACK HACK HACK - if (w->style == WINDOW_STYLE_TEAMCOLOR) { - if (color[0] > 0) { - // red - color[0] = 1; - color[1] = color[2] = .5; - - } else { - color[2] = 1; - color[0] = color[1] = .5; - } - color[3] = 1; - DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color); - } else { - DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor); - } - } else if (w->border == WINDOW_BORDER_HORZ) { - // top/bottom - DC->setColor(w->borderColor); - DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); - DC->setColor( NULL ); - } else if (w->border == WINDOW_BORDER_VERT) { - // left right - DC->setColor(w->borderColor); - DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); - DC->setColor( NULL ); - } else if (w->border == WINDOW_BORDER_KCGRADIENT) { - // this is just two gradient bars along each horz edge - rectDef_t r = w->rect; - r.h = w->borderSize; - GradientBar_Paint(&r, w->borderColor); - r.y = w->rect.y + w->rect.h - 1; - GradientBar_Paint(&r, w->borderColor); - } - -} - - -void Item_SetScreenCoords(itemDef_t *item, float x, float y) { - - if (item == NULL) { - return; - } - - if (item->window.border != 0) { - x += item->window.borderSize; - y += item->window.borderSize; - } - - item->window.rect.x = x + item->window.rectClient.x; - item->window.rect.y = y + item->window.rectClient.y; - item->window.rect.w = item->window.rectClient.w; - item->window.rect.h = item->window.rectClient.h; - - // force the text rects to recompute - item->textRect.w = 0; - item->textRect.h = 0; +void Window_Init(Window *w) +{ + memset(w, 0, sizeof(Window)); + w->borderSize = 1; + w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0; + w->cinematic = -1; } -// FIXME: consolidate this with nearby stuff -void Item_UpdatePosition(itemDef_t *item) { - float x, y; - menuDef_t *menu; +void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount) +{ + if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN)) + { + if (DC->realTime > *nextTime) + { + *nextTime = DC->realTime + offsetTime; - if (item == NULL || item->parent == NULL) { - return; - } + if (*flags & WINDOW_FADINGOUT) + { + *f -= fadeAmount; - menu = item->parent; + if (bFlags && *f <= 0.0) + *flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE); + } + else + { + *f += fadeAmount; - x = menu->window.rect.x; - y = menu->window.rect.y; + if (*f >= clamp) + { + *f = clamp; - if (menu->window.border != 0) { - x += menu->window.borderSize; - y += menu->window.borderSize; - } + if (bFlags) + *flags &= ~WINDOW_FADINGIN; + } + } + } + } +} - Item_SetScreenCoords(item, x, y); +static void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) +{ + vec4_t color; + rectDef_t fillRect = w->rect; -} + if (DC->getCVarValue("ui_developer")) + { + color[0] = color[1] = color[2] = color[3] = 1; + DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color); + } -// menus -void Menu_UpdatePosition(menuDef_t *menu) { - int i; - float x, y; - - if (menu == NULL) { - return; - } - - x = menu->window.rect.x; - y = menu->window.rect.y; - if (menu->window.border != 0) { - x += menu->window.borderSize; - y += menu->window.borderSize; - } - - for (i = 0; i < menu->itemCount; i++) { - Item_SetScreenCoords(menu->items[i], x, y); - } -} - -void Menu_PostParse(menuDef_t *menu) { - if (menu == NULL) { - return; - } - if (menu->fullScreen) { - menu->window.rect.x = 0; - menu->window.rect.y = 0; - menu->window.rect.w = 640; - menu->window.rect.h = 480; - } - Menu_UpdatePosition(menu); -} - -itemDef_t *Menu_ClearFocus(menuDef_t *menu) { - int i; - itemDef_t *ret = NULL; - - if (menu == NULL) { - return NULL; - } + if (w == NULL || (w->style == 0 && w->border == 0)) + return; + + if (w->border != 0) + { + fillRect.x += w->borderSize; + fillRect.y += w->borderSize; + fillRect.w -= w->borderSize + 1; + fillRect.h -= w->borderSize + 1; + } + + if (w->style == WINDOW_STYLE_FILLED) + { + // box, but possible a shader that needs filled - for (i = 0; i < menu->itemCount; i++) { - if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { - ret = menu->items[i]; + if (w->background) + { + Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount); + DC->setColor(w->backColor); + DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); + DC->setColor(NULL); + } + else + DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor); } - menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; - if (menu->items[i]->leaveFocus) { - Item_RunScript(menu->items[i], menu->items[i]->leaveFocus); + else if (w->style == WINDOW_STYLE_GRADIENT) + { + GradientBar_Paint(&fillRect, w->backColor); + // gradient bar } - } + else if (w->style == WINDOW_STYLE_SHADER) + { + if (w->flags & WINDOW_FORECOLORSET) + DC->setColor(w->foreColor); - return ret; -} + DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); + DC->setColor(NULL); + } + else if (w->style == WINDOW_STYLE_CINEMATIC) + { + if (w->cinematic == -1) + { + w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h); -qboolean IsVisible(int flags) { - return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT)); -} + if (w->cinematic == -1) + w->cinematic = -2; + } -qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) { - if (rect) { - if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h) { - return qtrue; + if (w->cinematic >= 0) + { + DC->runCinematicFrame(w->cinematic); + DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h); + } } - } - return qfalse; } -int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name) { - int i; - int count = 0; - for (i = 0; i < menu->itemCount; i++) { - if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) { - count++; +static void Border_Paint(Window *w) +{ + if (w == NULL || (w->style == 0 && w->border == 0)) + return; + + if (w->border == WINDOW_BORDER_FULL) + { + // full + DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor); + } + else if (w->border == WINDOW_BORDER_HORZ) + { + // top/bottom + DC->setColor(w->borderColor); + DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); + DC->setColor(NULL); + } + else if (w->border == WINDOW_BORDER_VERT) + { + // left right + DC->setColor(w->borderColor); + DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); + DC->setColor(NULL); + } + else if (w->border == WINDOW_BORDER_KCGRADIENT) + { + // this is just two gradient bars along each horz edge + rectDef_t r = w->rect; + r.h = w->borderSize; + GradientBar_Paint(&r, w->borderColor); + r.y = w->rect.y + w->rect.h - 1; + GradientBar_Paint(&r, w->borderColor); } - } - return count; } -itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name) { - int i; - int count = 0; - for (i = 0; i < menu->itemCount; i++) { - if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) { - if (count == index) { - return menu->items[i]; - } - count++; +void Item_SetScreenCoords(itemDef_t *item, float x, float y) +{ + if (item == NULL) + return; + + if (item->window.border != 0) + { + x += item->window.borderSize; + y += item->window.borderSize; } - } - return NULL; + + item->window.rect.x = x + item->window.rectClient.x; + item->window.rect.y = y + item->window.rectClient.y; + item->window.rect.w = item->window.rectClient.w; + item->window.rect.h = item->window.rectClient.h; + + // force the text rects to recompute + item->textRect.w = 0; + item->textRect.h = 0; } +// FIXME: consolidate this with nearby stuff +void Item_UpdatePosition(itemDef_t *item) +{ + float x, y; + menuDef_t *menu; + if (item == NULL || item->parent == NULL) + return; -void Script_SetColor(itemDef_t *item, char **args) { - const char *name; - int i; - float f; - vec4_t *out; - // expecting type of color to set and 4 args for the color - if (String_Parse(args, &name)) { - out = NULL; - if (Q_stricmp(name, "backcolor") == 0) { - out = &item->window.backColor; - item->window.flags |= WINDOW_BACKCOLORSET; - } else if (Q_stricmp(name, "forecolor") == 0) { - out = &item->window.foreColor; - item->window.flags |= WINDOW_FORECOLORSET; - } else if (Q_stricmp(name, "bordercolor") == 0) { - out = &item->window.borderColor; - } + menu = item->parent; - if (out) { - for (i = 0; i < 4; i++) { - if (!Float_Parse(args, &f)) { - return; - } - (*out)[i] = f; - } - } - } -} + x = menu->window.rect.x; + y = menu->window.rect.y; -void Script_SetAsset(itemDef_t *item, char **args) { - const char *name; - // expecting name to set asset to - if (String_Parse(args, &name)) { - // check for a model - if (item->type == ITEM_TYPE_MODEL) { + if (menu->window.border != 0) + { + x += menu->window.borderSize; + y += menu->window.borderSize; } - } -} -void Script_SetBackground(itemDef_t *item, char **args) { - const char *name; - // expecting name to set asset to - if (String_Parse(args, &name)) { - item->window.background = DC->registerShaderNoMip(name); - } + Item_SetScreenCoords(item, x, y); } +// menus +void Menu_UpdatePosition(menuDef_t *menu) +{ + int i; + float x, y; + if (menu == NULL) + return; + x = menu->window.rect.x; + y = menu->window.rect.y; -itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p) { - int i; - if (menu == NULL || p == NULL) { - return NULL; - } - - for (i = 0; i < menu->itemCount; i++) { - if (Q_stricmp(p, menu->items[i]->window.name) == 0) { - return menu->items[i]; + if (menu->window.border != 0) + { + x += menu->window.borderSize; + y += menu->window.borderSize; } - } - return NULL; + for (i = 0; i < menu->itemCount; i++) + Item_SetScreenCoords(menu->items[i], x, y); } -void Script_SetTeamColor(itemDef_t *item, char **args) { - if (DC->getTeamColor) { - int i; - vec4_t color; - DC->getTeamColor(&color); - for (i = 0; i < 4; i++) { - item->window.backColor[i] = color[i]; - } - } -} - -void Script_SetItemColor(itemDef_t *item, char **args) { - const char *itemname; - const char *name; - vec4_t color; - int i; - vec4_t *out; - // expecting type of color to set and 4 args for the color - if (String_Parse(args, &itemname) && String_Parse(args, &name)) { - itemDef_t *item2; - int j; - int count = Menu_ItemsMatchingGroup(item->parent, itemname); +static void Menu_AspectiseRect(int bias, Rectangle *rect) +{ + switch (bias) + { + case ALIGN_LEFT: + rect->x *= DC->aspectScale; + rect->w *= DC->aspectScale; + break; - if (!Color_Parse(args, &color)) { - return; - } + case ALIGN_CENTER: + rect->x = (rect->x * DC->aspectScale) + (320.0f - (320.0f * DC->aspectScale)); + rect->w *= DC->aspectScale; + break; - for (j = 0; j < count; j++) { - item2 = Menu_GetMatchingItemByNumber(item->parent, j, itemname); - if (item2 != NULL) { - out = NULL; - if (Q_stricmp(name, "backcolor") == 0) { - out = &item2->window.backColor; - } else if (Q_stricmp(name, "forecolor") == 0) { - out = &item2->window.foreColor; - item2->window.flags |= WINDOW_FORECOLORSET; - } else if (Q_stricmp(name, "bordercolor") == 0) { - out = &item2->window.borderColor; - } + case ALIGN_RIGHT: + rect->x = 640.0f - ((640.0f - rect->x) * DC->aspectScale); + rect->w *= DC->aspectScale; + break; - if (out) { - for (i = 0; i < 4; i++) { - (*out)[i] = color[i]; - } - } - } + default: + + case ASPECT_NONE: + break; } - } } +void Menu_AspectCompensate(menuDef_t *menu) +{ + int i; -void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) { - itemDef_t *item; - int i; - int count = Menu_ItemsMatchingGroup(menu, p); - for (i = 0; i < count; i++) { - item = Menu_GetMatchingItemByNumber(menu, i, p); - if (item != NULL) { - if (bShow) { - item->window.flags |= WINDOW_VISIBLE; - } else { - item->window.flags &= ~WINDOW_VISIBLE; - // stop cinematics playing in the window - if (item->window.cinematic >= 0) { - DC->stopCinematic(item->window.cinematic); - item->window.cinematic = -1; - } - } - } - } -} + if (menu->window.aspectBias != ASPECT_NONE) + { + Menu_AspectiseRect(menu->window.aspectBias, &menu->window.rect); -void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut) { - itemDef_t *item; - int i; - int count = Menu_ItemsMatchingGroup(menu, p); - for (i = 0; i < count; i++) { - item = Menu_GetMatchingItemByNumber(menu, i, p); - if (item != NULL) { - if (fadeOut) { - item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE); - item->window.flags &= ~WINDOW_FADINGIN; - } else { - item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN); - item->window.flags &= ~WINDOW_FADINGOUT; - } + for (i = 0; i < menu->itemCount; i++) + { + menu->items[i]->window.rectClient.x *= DC->aspectScale; + menu->items[i]->window.rectClient.w *= DC->aspectScale; + menu->items[i]->textalignx *= DC->aspectScale; + } } - } -} + else + { + for (i = 0; i < menu->itemCount; i++) + { + Menu_AspectiseRect(menu->items[i]->window.aspectBias, &menu->items[i]->window.rectClient); -menuDef_t *Menus_FindByName(const char *p) { - int i; - for (i = 0; i < menuCount; i++) { - if (Q_stricmp(Menus[i].window.name, p) == 0) { - return &Menus[i]; + if (menu->items[i]->window.aspectBias != ASPECT_NONE) + menu->items[i]->textalignx *= DC->aspectScale; + } } - } - return NULL; -} - -void Menus_ShowByName(const char *p) { - menuDef_t *menu = Menus_FindByName(p); - if (menu) { - Menus_Activate(menu); - } } -void Menus_OpenByName(const char *p) { - Menus_ActivateByName(p); -} +void Menu_PostParse(menuDef_t *menu) +{ + int i, j; -static void Menu_RunCloseScript(menuDef_t *menu) { - if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose) { - itemDef_t item; - item.parent = menu; - Item_RunScript(&item, menu->onClose); - } -} + if (menu == NULL) + return; -void Menus_CloseByName(const char *p) { - menuDef_t *menu = Menus_FindByName(p); - if (menu != NULL) { - Menu_RunCloseScript(menu); - menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS); - } -} + if (menu->fullScreen) + { + menu->window.rect.x = 0; + menu->window.rect.y = 0; + menu->window.rect.w = 640; + menu->window.rect.h = 480; + } -void Menus_CloseAll( void ) { - int i; - for (i = 0; i < menuCount; i++) { - Menu_RunCloseScript(&Menus[i]); - Menus[i].window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); - } + Menu_AspectCompensate(menu); + Menu_UpdatePosition(menu); - g_editingField = qfalse; - g_waitingForKey = qfalse; -} + // Push lists to the end of the array as they can potentially be drawn on top + // of other elements + for (i = 0; i < menu->itemCount; i++) + { + itemDef_t *item = menu->items[i]; + if (Item_IsListBox(item)) + { + for (j = i; j < menu->itemCount - 1; j++) + menu->items[j] = menu->items[j + 1]; -void Script_Show(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - Menu_ShowItemByName(item->parent, name, qtrue); - } + menu->items[j] = item; + } + } } -void Script_Hide(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - Menu_ShowItemByName(item->parent, name, qfalse); - } -} +itemDef_t *Menu_ClearFocus(menuDef_t *menu) +{ + int i; + itemDef_t *ret = NULL; -void Script_FadeIn(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - Menu_FadeItemByName(item->parent, name, qfalse); - } -} + if (menu == NULL) + return NULL; -void Script_FadeOut(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - Menu_FadeItemByName(item->parent, name, qtrue); - } -} + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) + ret = menu->items[i]; + menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; + if (menu->items[i]->leaveFocus) + Item_RunScript(menu->items[i], menu->items[i]->leaveFocus); + } -void Script_Open(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - Menus_OpenByName(name); - } + return ret; } -void Script_ConditionalOpen(itemDef_t *item, char **args) { - const char *cvar; - const char *name1; - const char *name2; - float val; +qboolean IsVisible(int flags) { return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT)); } - if ( String_Parse(args, &cvar) && String_Parse(args, &name1) && String_Parse(args, &name2) ) { - val = DC->getCVarValue( cvar ); - if ( val == 0.f ) { - Menus_OpenByName(name2); - } else { - Menus_OpenByName(name1); +qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) +{ + if (rect) + { + if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h) + return qtrue; } - } -} -void Script_Close(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - Menus_CloseByName(name); - } + return qfalse; } -void Menu_TransitionItemByName(menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt) { - itemDef_t *item; - int i; - int count = Menu_ItemsMatchingGroup(menu, p); - for (i = 0; i < count; i++) { - item = Menu_GetMatchingItemByNumber(menu, i, p); - if (item != NULL) { - item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE); - item->window.offsetTime = time; - memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t)); - memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t)); - item->window.rectEffects2.x = fabs(rectTo.x - rectFrom.x) / amt; - item->window.rectEffects2.y = fabs(rectTo.y - rectFrom.y) / amt; - item->window.rectEffects2.w = fabs(rectTo.w - rectFrom.w) / amt; - item->window.rectEffects2.h = fabs(rectTo.h - rectFrom.h) / amt; - Item_UpdatePosition(item); +int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name) +{ + int i; + int count = 0; + + for (i = 0; i < menu->itemCount; i++) + { + if (Q_stricmp(menu->items[i]->window.name, name) == 0 || + (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) + { + count++; + } } - } + + return count; } +itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name) +{ + int i; + int count = 0; -void Script_Transition(itemDef_t *item, char **args) { - const char *name; - rectDef_t rectFrom, rectTo; - int time; - float amt; + for (i = 0; i < menu->itemCount; i++) + { + if (Q_stricmp(menu->items[i]->window.name, name) == 0 || + (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) + { + if (count == index) + return menu->items[i]; - if (String_Parse(args, &name)) { - if ( Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) && Float_Parse(args, &amt)) { - Menu_TransitionItemByName(item->parent, name, rectFrom, rectTo, time, amt); + count++; + } } - } + + return NULL; } +void Script_SetColor(itemDef_t *item, char **args) +{ + const char *name; + int i; + float f; + vec4_t *out; + // expecting type of color to set and 4 args for the color -void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time) { - itemDef_t *item; - int i; - int count = Menu_ItemsMatchingGroup(menu, p); - for (i = 0; i < count; i++) { - item = Menu_GetMatchingItemByNumber(menu, i, p); - if (item != NULL) { - item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE); - item->window.offsetTime = time; - item->window.rectEffects.x = cx; - item->window.rectEffects.y = cy; - item->window.rectClient.x = x; - item->window.rectClient.y = y; - Item_UpdatePosition(item); - } - } -} + if (String_Parse(args, &name)) + { + out = NULL; + if (Q_stricmp(name, "backcolor") == 0) + { + out = &item->window.backColor; + item->window.flags |= WINDOW_BACKCOLORSET; + } + else if (Q_stricmp(name, "forecolor") == 0) + { + out = &item->window.foreColor; + item->window.flags |= WINDOW_FORECOLORSET; + } + else if (Q_stricmp(name, "bordercolor") == 0) + out = &item->window.borderColor; -void Script_Orbit(itemDef_t *item, char **args) { - const char *name; - float cx, cy, x, y; - int time; + if (out) + { + for (i = 0; i < 4; i++) + { + if (!Float_Parse(args, &f)) + return; - if (String_Parse(args, &name)) { - if ( Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) && Int_Parse(args, &time) ) { - Menu_OrbitItemByName(item->parent, name, x, y, cx, cy, time); + (*out)[i] = f; + } + } } - } } +void Script_SetAsset(itemDef_t *item, char **args) +{ + const char *name; + // expecting name to set asset to - -void Script_SetFocus(itemDef_t *item, char **args) { - const char *name; - itemDef_t *focusItem; - - if (String_Parse(args, &name)) { - focusItem = Menu_FindItemByName(item->parent, name); - if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION)) { - Menu_ClearFocus(item->parent); - focusItem->window.flags |= WINDOW_HASFOCUS; - if (focusItem->onFocus) { - Item_RunScript(focusItem, focusItem->onFocus); - } - if (focusItem->type == ITEM_TYPE_EDITFIELD || focusItem->type == ITEM_TYPE_SAYFIELD || focusItem->type == ITEM_TYPE_NUMERICFIELD) { - focusItem->cursorPos = 0; - g_editingField = qtrue; - g_editItem = focusItem; - if (focusItem->type == ITEM_TYPE_SAYFIELD) { - DC->setOverstrikeMode(qfalse); + if (String_Parse(args, &name)) + { + // check for a model + if (item->type == ITEM_TYPE_MODEL) + { } - } - if (DC->Assets.itemFocusSound) { - DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND ); - } } - } -} - -void Script_SetPlayerModel(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - DC->setCVar("team_model", name); - } -} - -void Script_SetPlayerHead(itemDef_t *item, char **args) { - const char *name; - if (String_Parse(args, &name)) { - DC->setCVar("team_headmodel", name); - } } -void Script_SetCvar(itemDef_t *item, char **args) { - const char *cvar, *val; - if (String_Parse(args, &cvar) && String_Parse(args, &val)) { - DC->setCVar(cvar, val); - } +void Script_SetBackground(itemDef_t *item, char **args) +{ + const char *name; + // expecting name to set asset to + if (String_Parse(args, &name)) + item->window.background = DC->registerShaderNoMip(name); } -void Script_Exec(itemDef_t *item, char **args) { - const char *val; - if (String_Parse(args, &val)) { - DC->executeText(EXEC_APPEND, va("%s ; ", val)); - } -} +itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p) +{ + int i; + + if (menu == NULL || p == NULL) + return NULL; + + for (i = 0; i < menu->itemCount; i++) + { + if (Q_stricmp(p, menu->items[i]->window.name) == 0) + return menu->items[i]; + } -void Script_Play(itemDef_t *item, char **args) { - const char *val; - if (String_Parse(args, &val)) { - DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_LOCAL_SOUND); - } + return NULL; } -void Script_playLooped(itemDef_t *item, char **args) { - const char *val; - if (String_Parse(args, &val)) { - DC->stopBackgroundTrack(); - DC->startBackgroundTrack(val, val); - } -} - - -commandDef_t commandList[] = +void Script_SetItemColor(itemDef_t *item, char **args) { - {"fadein", &Script_FadeIn}, // group/name - {"fadeout", &Script_FadeOut}, // group/name - {"show", &Script_Show}, // group/name - {"hide", &Script_Hide}, // group/name - {"setcolor", &Script_SetColor}, // works on this - {"open", &Script_Open}, // menu - {"conditionalopen", &Script_ConditionalOpen}, // menu - {"close", &Script_Close}, // menu - {"setasset", &Script_SetAsset}, // works on this - {"setbackground", &Script_SetBackground}, // works on this - {"setitemcolor", &Script_SetItemColor}, // group/name - {"setteamcolor", &Script_SetTeamColor}, // sets this background color to team color - {"setfocus", &Script_SetFocus}, // sets this background color to team color - {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color - {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color - {"transition", &Script_Transition}, // group/name - {"setcvar", &Script_SetCvar}, // group/name - {"exec", &Script_Exec}, // group/name - {"play", &Script_Play}, // group/name - {"playlooped", &Script_playLooped}, // group/name - {"orbit", &Script_Orbit} // group/name -}; + const char *itemname; + const char *name; + vec4_t color; + int i; + vec4_t *out; + // expecting type of color to set and 4 args for the color -int scriptCommandCount = sizeof(commandList) / sizeof(commandDef_t); - - -void Item_RunScript(itemDef_t *item, const char *s) { - char script[1024], *p; - int i; - qboolean bRan; - memset(script, 0, sizeof(script)); - if (item && s && s[0]) { - Q_strcat(script, 1024, s); - p = script; - while (1) { - const char *command; - // expect command then arguments, ; ends command, NULL ends script - if (!String_Parse(&p, &command)) { - return; - } + if (String_Parse(args, &itemname) && String_Parse(args, &name)) + { + itemDef_t *item2; + int j; + int count = Menu_ItemsMatchingGroup(item->parent, itemname); - if (command[0] == ';' && command[1] == '\0') { - continue; - } + if (!Color_Parse(args, &color)) + return; - bRan = qfalse; - for (i = 0; i < scriptCommandCount; i++) { - if (Q_stricmp(command, commandList[i].name) == 0) { - (commandList[i].handler(item, &p)); - bRan = qtrue; - break; + for (j = 0; j < count; j++) + { + item2 = Menu_GetMatchingItemByNumber(item->parent, j, itemname); + + if (item2 != NULL) + { + out = NULL; + + if (Q_stricmp(name, "backcolor") == 0) + out = &item2->window.backColor; + else if (Q_stricmp(name, "forecolor") == 0) + { + out = &item2->window.foreColor; + item2->window.flags |= WINDOW_FORECOLORSET; + } + else if (Q_stricmp(name, "bordercolor") == 0) + out = &item2->window.borderColor; + + if (out) + { + for (i = 0; i < 4; i++) + (*out)[i] = color[i]; + } + } } - } - // not in our auto list, pass to handler - if (!bRan) { - DC->runScript(&p); - } } - } } +void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); -qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) { - char script[1024], *p; - memset(script, 0, sizeof(script)); - if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) { - char buff[1024]; - DC->getCVarString(item->cvarTest, buff, sizeof(buff)); - - Q_strcat(script, 1024, item->enableCvar); - p = script; - while (1) { - const char *val; - // expect value then ; or NULL, NULL ends list - if (!String_Parse(&p, &val)) { - return (item->cvarFlags & flag) ? qfalse : qtrue; - } - - if (val[0] == ';' && val[1] == '\0') { - continue; - } + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); - // enable it if any of the values are true - if (item->cvarFlags & flag) { - if (Q_stricmp(buff, val) == 0) { - return qtrue; - } - } else { - // disable it if any of the values are true - if (Q_stricmp(buff, val) == 0) { - return qfalse; + if (item != NULL) + { + if (bShow) + item->window.flags |= WINDOW_VISIBLE; + else + { + item->window.flags &= ~WINDOW_VISIBLE; + // stop cinematics playing in the window + + if (item->window.cinematic >= 0) + { + DC->stopCinematic(item->window.cinematic); + item->window.cinematic = -1; + } + } } - } - } - return (item->cvarFlags & flag) ? qfalse : qtrue; - } - return qtrue; } +void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); -// will optionaly set focus to this item -qboolean Item_SetFocus(itemDef_t *item, float x, float y) { - int i; - itemDef_t *oldFocus; - sfxHandle_t *sfx = &DC->Assets.itemFocusSound; - qboolean playSound = qfalse; - menuDef_t *parent; // bk001206: = (menuDef_t*)item->parent; - // sanity check, non-null, not a decoration and does not already have the focus - if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || !(item->window.flags & WINDOW_VISIBLE)) { - return qfalse; - } + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); - // bk001206 - this can be NULL. - parent = (menuDef_t*)item->parent; + if (item != NULL) + { + if (fadeOut) + { + item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE); + item->window.flags &= ~WINDOW_FADINGIN; + } + else + { + item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN); + item->window.flags &= ~WINDOW_FADINGOUT; + } + } + } +} - // items can be enabled and disabled based on cvars - if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { - return qfalse; - } +menuDef_t *Menus_FindByName(const char *p) +{ + int i; - if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) { - return qfalse; - } + for (i = 0; i < menuCount; i++) + { + if (Q_stricmp(Menus[i].window.name, p) == 0) + return &Menus[i]; + } - oldFocus = Menu_ClearFocus(item->parent); + return NULL; +} - if (item->type == ITEM_TYPE_TEXT) { - rectDef_t r; - r = item->textRect; - r.y -= r.h; - if (Rect_ContainsPoint(&r, x, y)) { - item->window.flags |= WINDOW_HASFOCUS; - if (item->focusSound) { - sfx = &item->focusSound; - } - playSound = qtrue; - } else { - if (oldFocus) { - oldFocus->window.flags |= WINDOW_HASFOCUS; - if (oldFocus->onFocus) { - Item_RunScript(oldFocus, oldFocus->onFocus); - } - } - } - } else { - item->window.flags |= WINDOW_HASFOCUS; - if (item->onFocus) { - Item_RunScript(item, item->onFocus); - } - if (item->focusSound) { - sfx = &item->focusSound; - } - playSound = qtrue; - } - - if (playSound && sfx) { - DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND ); - } - - for (i = 0; i < parent->itemCount; i++) { - if (parent->items[i] == item) { - parent->cursorItem = i; - break; - } - } - - return qtrue; -} - -int Item_ListBox_MaxScroll(itemDef_t *item) { - listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - int count = DC->feederCount(item->special); - int max; - - if (item->window.flags & WINDOW_HORIZONTAL) { - max = count - (item->window.rect.w / listPtr->elementWidth) + 1; - } - else { - max = count - (item->window.rect.h / listPtr->elementHeight) + 1; - } - if (max < 0) { - return 0; - } - return max; -} - -int Item_ListBox_ThumbPosition(itemDef_t *item) { - float max, pos, size; - listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - - max = Item_ListBox_MaxScroll(item); - if (item->window.flags & WINDOW_HORIZONTAL) { - size = item->window.rect.w - (SCROLLBAR_SIZE * 2) - 2; - if (max > 0) { - pos = (size-SCROLLBAR_SIZE) / (float) max; - } else { - pos = 0; - } - pos *= listPtr->startPos; - return item->window.rect.x + 1 + SCROLLBAR_SIZE + pos; - } - else { - size = item->window.rect.h - (SCROLLBAR_SIZE * 2) - 2; - if (max > 0) { - pos = (size-SCROLLBAR_SIZE) / (float) max; - } else { - pos = 0; - } - pos *= listPtr->startPos; - return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos; - } -} - -int Item_ListBox_ThumbDrawPosition(itemDef_t *item) { - int min, max; - - if (itemCapture == item) { - if (item->window.flags & WINDOW_HORIZONTAL) { - min = item->window.rect.x + SCROLLBAR_SIZE + 1; - max = item->window.rect.x + item->window.rect.w - 2*SCROLLBAR_SIZE - 1; - if (DC->cursorx >= min + SCROLLBAR_SIZE/2 && DC->cursorx <= max + SCROLLBAR_SIZE/2) { - return DC->cursorx - SCROLLBAR_SIZE/2; - } - else { - return Item_ListBox_ThumbPosition(item); - } - } - else { - min = item->window.rect.y + SCROLLBAR_SIZE + 1; - max = item->window.rect.y + item->window.rect.h - 2*SCROLLBAR_SIZE - 1; - if (DC->cursory >= min + SCROLLBAR_SIZE/2 && DC->cursory <= max + SCROLLBAR_SIZE/2) { - return DC->cursory - SCROLLBAR_SIZE/2; - } - else { - return Item_ListBox_ThumbPosition(item); - } - } - } - else { - return Item_ListBox_ThumbPosition(item); - } +static void Menu_RunCloseScript(menuDef_t *menu) +{ + if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose) + { + itemDef_t item; + item.parent = menu; + Item_RunScript(&item, menu->onClose); + } } -float Item_Slider_ThumbPosition(itemDef_t *item) { - float value, range, x; - editFieldDef_t *editDef = item->typeData; +static void Menus_Close(menuDef_t *menu) +{ + if (menu != NULL) + { + Menu_RunCloseScript(menu); + menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS); - if (item->text) { - x = item->textRect.x + item->textRect.w + 8; - } else { - x = item->window.rect.x; - } + if (openMenuCount > 0) + openMenuCount--; - if (editDef == NULL && item->cvar) { - return x; - } - - value = DC->getCVarValue(item->cvar); - - if (value < editDef->minVal) { - value = editDef->minVal; - } else if (value > editDef->maxVal) { - value = editDef->maxVal; - } - - range = editDef->maxVal - editDef->minVal; - value -= editDef->minVal; - value /= range; - //value /= (editDef->maxVal - editDef->minVal); - value *= SLIDER_WIDTH; - x += value; - // vm fuckage - //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH); - return x; -} - -int Item_Slider_OverSlider(itemDef_t *item, float x, float y) { - rectDef_t r; - - r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2); - r.y = item->window.rect.y - 2; - r.w = SLIDER_THUMB_WIDTH; - r.h = SLIDER_THUMB_HEIGHT; - - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_THUMB; - } - return 0; -} - -int Item_ListBox_OverLB(itemDef_t *item, float x, float y) { - rectDef_t r; - listBoxDef_t *listPtr; - int thumbstart; - int count; - - count = DC->feederCount(item->special); - listPtr = (listBoxDef_t*)item->typeData; - if (item->window.flags & WINDOW_HORIZONTAL) { - // check if on left arrow - r.x = item->window.rect.x; - r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; - r.h = r.w = SCROLLBAR_SIZE; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_LEFTARROW; - } - // check if on right arrow - r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_RIGHTARROW; - } - // check if on thumb - thumbstart = Item_ListBox_ThumbPosition(item); - r.x = thumbstart; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_THUMB; - } - r.x = item->window.rect.x + SCROLLBAR_SIZE; - r.w = thumbstart - r.x; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_PGUP; - } - r.x = thumbstart + SCROLLBAR_SIZE; - r.w = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_PGDN; - } - } else { - r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; - r.y = item->window.rect.y; - r.h = r.w = SCROLLBAR_SIZE; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_LEFTARROW; - } - r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_RIGHTARROW; - } - thumbstart = Item_ListBox_ThumbPosition(item); - r.y = thumbstart; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_THUMB; + if (openMenuCount > 0) + Menus_Activate(menuStack[openMenuCount - 1]); } - r.y = item->window.rect.y + SCROLLBAR_SIZE; - r.h = thumbstart - r.y; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_PGUP; - } - r.y = thumbstart + SCROLLBAR_SIZE; - r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; - if (Rect_ContainsPoint(&r, x, y)) { - return WINDOW_LB_PGDN; - } - } - return 0; } +void Menus_CloseByName(const char *p) { Menus_Close(Menus_FindByName(p)); } -void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y) +void Menus_CloseAll(void) { - rectDef_t r; - listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - - item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN); - item->window.flags |= Item_ListBox_OverLB(item, x, y); - - if (item->window.flags & WINDOW_HORIZONTAL) { - if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) { - // check for selection hit as we have exausted buttons and thumb - if (listPtr->elementStyle == LISTBOX_IMAGE) { - r.x = item->window.rect.x; - r.y = item->window.rect.y; - r.h = item->window.rect.h - SCROLLBAR_SIZE; - r.w = item->window.rect.w - listPtr->drawPadding; - if (Rect_ContainsPoint(&r, x, y)) { - listPtr->cursorPos = (int)((x - r.x) / listPtr->elementWidth) + listPtr->startPos; - if (listPtr->cursorPos >= listPtr->endPos) { - listPtr->cursorPos = listPtr->endPos; - } - } - } else { - // text hit.. - } - } - } else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) { - r.x = item->window.rect.x; - r.y = item->window.rect.y; - r.w = item->window.rect.w - SCROLLBAR_SIZE; - r.h = item->window.rect.h - listPtr->drawPadding; - if (Rect_ContainsPoint(&r, x, y)) { - listPtr->cursorPos = (int)((y - 2 - r.y) / listPtr->elementHeight) + listPtr->startPos; - if (listPtr->cursorPos > listPtr->endPos) { - listPtr->cursorPos = listPtr->endPos; - } - } - } -} - -void Item_MouseEnter(itemDef_t *item, float x, float y) { - rectDef_t r; - if (item) { - r = item->textRect; - r.y -= r.h; - // in the text rect? + int i; - // items can be enabled and disabled based on cvars - if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { - return; - } + // Close any menus on the stack first + if (openMenuCount > 0) + { + for (i = openMenuCount; i > 0; i--) + Menus_Close(menuStack[i - 1]); - if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) { - return; + openMenuCount = 0; } - if (Rect_ContainsPoint(&r, x, y)) { - if (!(item->window.flags & WINDOW_MOUSEOVERTEXT)) { - Item_RunScript(item, item->mouseEnterText); - item->window.flags |= WINDOW_MOUSEOVERTEXT; - } - if (!(item->window.flags & WINDOW_MOUSEOVER)) { - Item_RunScript(item, item->mouseEnter); - item->window.flags |= WINDOW_MOUSEOVER; - } + // Close all other menus + for (i = 0; i < menuCount; i++) + Menus_Close(&Menus[i]); - } else { - // not in the text rect - if (item->window.flags & WINDOW_MOUSEOVERTEXT) { - // if we were - Item_RunScript(item, item->mouseExitText); - item->window.flags &= ~WINDOW_MOUSEOVERTEXT; - } - if (!(item->window.flags & WINDOW_MOUSEOVER)) { - Item_RunScript(item, item->mouseEnter); - item->window.flags |= WINDOW_MOUSEOVER; - } + g_editingField = qfalse; + g_waitingForKey = qfalse; + g_comboBoxItem = NULL; +} - if (item->type == ITEM_TYPE_LISTBOX) { - Item_ListBox_MouseEnter(item, x, y); - } - } - } +void Script_Show(itemDef_t *item, char **args) +{ + const char *name; + + if (String_Parse(args, &name)) + Menu_ShowItemByName(item->parent, name, qtrue); } -void Item_MouseLeave(itemDef_t *item) { - if (item) { - if (item->window.flags & WINDOW_MOUSEOVERTEXT) { - Item_RunScript(item, item->mouseExitText); - item->window.flags &= ~WINDOW_MOUSEOVERTEXT; - } - Item_RunScript(item, item->mouseExit); - item->window.flags &= ~(WINDOW_LB_RIGHTARROW | WINDOW_LB_LEFTARROW); - } +void Script_Hide(itemDef_t *item, char **args) +{ + const char *name; + + if (String_Parse(args, &name)) + Menu_ShowItemByName(item->parent, name, qfalse); } -itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y) { - int i; - for (i = 0; i < menu->itemCount; i++) { - if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { - return menu->items[i]; - } - } - return NULL; +void Script_FadeIn(itemDef_t *item, char **args) +{ + const char *name; + + if (String_Parse(args, &name)) + Menu_FadeItemByName(item->parent, name, qfalse); } -void Item_SetMouseOver(itemDef_t *item, qboolean focus) { - if (item) { - if (focus) { - item->window.flags |= WINDOW_MOUSEOVER; - } else { - item->window.flags &= ~WINDOW_MOUSEOVER; - } - } +void Script_FadeOut(itemDef_t *item, char **args) +{ + const char *name; + + if (String_Parse(args, &name)) + Menu_FadeItemByName(item->parent, name, qtrue); } +void Script_Open(itemDef_t *item, char **args) +{ + const char *name; -qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) { - if (item && DC->ownerDrawHandleKey) { - return DC->ownerDrawHandleKey(item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key); - } - return qfalse; + if (String_Parse(args, &name)) + Menus_ActivateByName(name); } -qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) { - listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - int count = DC->feederCount(item->special); - int max, viewmax; +void Script_ConditionalOpen(itemDef_t *item, char **args) +{ + const char *cvar; + const char *name1; + const char *name2; + float val; - if (force || (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) { - max = Item_ListBox_MaxScroll(item); - if (item->window.flags & WINDOW_HORIZONTAL) { - viewmax = (item->window.rect.w / listPtr->elementWidth); - if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) - { - if (!listPtr->notselectable) { - listPtr->cursorPos--; - if (listPtr->cursorPos < 0) { - listPtr->cursorPos = 0; - } - if (listPtr->cursorPos < listPtr->startPos) { - listPtr->startPos = listPtr->cursorPos; - } - if (listPtr->cursorPos >= listPtr->startPos + viewmax) { - listPtr->startPos = listPtr->cursorPos - viewmax + 1; - } - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - else { - listPtr->startPos--; - if (listPtr->startPos < 0) - listPtr->startPos = 0; - } - return qtrue; - } - if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) - { - if (!listPtr->notselectable) { - listPtr->cursorPos++; - if (listPtr->cursorPos < listPtr->startPos) { - listPtr->startPos = listPtr->cursorPos; - } - if (listPtr->cursorPos >= count) { - listPtr->cursorPos = count-1; - } - if (listPtr->cursorPos >= listPtr->startPos + viewmax) { - listPtr->startPos = listPtr->cursorPos - viewmax + 1; - } - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - else { - listPtr->startPos++; - if (listPtr->startPos >= count) - listPtr->startPos = count-1; - } - return qtrue; - } - } - else { - viewmax = (item->window.rect.h / listPtr->elementHeight); - if ( key == K_UPARROW || key == K_KP_UPARROW ) - { - if (!listPtr->notselectable) { - listPtr->cursorPos--; - if (listPtr->cursorPos < 0) { - listPtr->cursorPos = 0; - } - if (listPtr->cursorPos < listPtr->startPos) { - listPtr->startPos = listPtr->cursorPos; - } - if (listPtr->cursorPos >= listPtr->startPos + viewmax) { - listPtr->startPos = listPtr->cursorPos - viewmax + 1; - } - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - else { - listPtr->startPos--; - if (listPtr->startPos < 0) - listPtr->startPos = 0; - } - return qtrue; - } - if ( key == K_DOWNARROW || key == K_KP_DOWNARROW ) - { - if (!listPtr->notselectable) { - listPtr->cursorPos++; - if (listPtr->cursorPos < listPtr->startPos) { - listPtr->startPos = listPtr->cursorPos; - } - if (listPtr->cursorPos >= count) { - listPtr->cursorPos = count-1; - } - if (listPtr->cursorPos >= listPtr->startPos + viewmax) { - listPtr->startPos = listPtr->cursorPos - viewmax + 1; - } - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - else { - listPtr->startPos++; - if (listPtr->startPos > max) - listPtr->startPos = max; - } - return qtrue; - } - } - // mouse hit - if (key == K_MOUSE1 || key == K_MOUSE2) { - if (item->window.flags & WINDOW_LB_LEFTARROW) { - listPtr->startPos--; - if (listPtr->startPos < 0) { - listPtr->startPos = 0; - } - } else if (item->window.flags & WINDOW_LB_RIGHTARROW) { - // one down - listPtr->startPos++; - if (listPtr->startPos > max) { - listPtr->startPos = max; - } - } else if (item->window.flags & WINDOW_LB_PGUP) { - // page up - listPtr->startPos -= viewmax; - if (listPtr->startPos < 0) { - listPtr->startPos = 0; - } - } else if (item->window.flags & WINDOW_LB_PGDN) { - // page down - listPtr->startPos += viewmax; - if (listPtr->startPos > max) { - listPtr->startPos = max; - } - } else if (item->window.flags & WINDOW_LB_THUMB) { - // Display_SetCaptureItem(item); - } else { - // select an item - if (DC->realTime < lastListBoxClickTime && listPtr->doubleClick) { - Item_RunScript(item, listPtr->doubleClick); - } - lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; - if (item->cursorPos != listPtr->cursorPos) { - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - } - return qtrue; - } - - // Scroll wheel - if (key == K_MWHEELUP) { - listPtr->startPos--; - if (listPtr->startPos < 0) { - listPtr->startPos = 0; - } - return qtrue; - } - if (key == K_MWHEELDOWN) { - listPtr->startPos++; - if (listPtr->startPos > max) { - listPtr->startPos = max; - } - return qtrue; - } - - // Invoke the doubleClick handler when enter is pressed - if( key == K_ENTER ) + if (String_Parse(args, &cvar) && String_Parse(args, &name1) && String_Parse(args, &name2)) { - if( listPtr->doubleClick ) - Item_RunScript( item, listPtr->doubleClick ); - - return qtrue; - } + val = DC->getCVarValue(cvar); - if ( key == K_HOME || key == K_KP_HOME) { - // home - listPtr->startPos = 0; - return qtrue; - } - if ( key == K_END || key == K_KP_END) { - // end - listPtr->startPos = max; - return qtrue; - } - if (key == K_PGUP || key == K_KP_PGUP ) { - // page up - if (!listPtr->notselectable) { - listPtr->cursorPos -= viewmax; - if (listPtr->cursorPos < 0) { - listPtr->cursorPos = 0; - } - if (listPtr->cursorPos < listPtr->startPos) { - listPtr->startPos = listPtr->cursorPos; - } - if (listPtr->cursorPos >= listPtr->startPos + viewmax) { - listPtr->startPos = listPtr->cursorPos - viewmax + 1; - } - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - else { - listPtr->startPos -= viewmax; - if (listPtr->startPos < 0) { - listPtr->startPos = 0; - } - } - return qtrue; - } - if ( key == K_PGDN || key == K_KP_PGDN ) { - // page down - if (!listPtr->notselectable) { - listPtr->cursorPos += viewmax; - if (listPtr->cursorPos < listPtr->startPos) { - listPtr->startPos = listPtr->cursorPos; - } - if (listPtr->cursorPos >= count) { - listPtr->cursorPos = count-1; - } - if (listPtr->cursorPos >= listPtr->startPos + viewmax) { - listPtr->startPos = listPtr->cursorPos - viewmax + 1; - } - item->cursorPos = listPtr->cursorPos; - DC->feederSelection(item->special, item->cursorPos); - } - else { - listPtr->startPos += viewmax; - if (listPtr->startPos > max) { - listPtr->startPos = max; - } - } - return qtrue; + if (val == 0.0f) + Menus_ActivateByName(name2); + else + Menus_ActivateByName(name1); } - } - return qfalse; } -qboolean Item_YesNo_HandleKey(itemDef_t *item, int key) { - - if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) { - if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) { - DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar))); - return qtrue; - } - } +void Script_Close(itemDef_t *item, char **args) +{ + const char *name; - return qfalse; + (void)item; + if (String_Parse(args, &name)) + Menus_CloseByName(name); } -int Item_Multi_CountSettings(itemDef_t *item) { - multiDef_t *multiPtr = (multiDef_t*)item->typeData; - if (multiPtr == NULL) { - return 0; - } - return multiPtr->count; -} - -int Item_Multi_FindCvarByValue(itemDef_t *item) { - char buff[1024]; - float value = 0; - int i; - multiDef_t *multiPtr = (multiDef_t*)item->typeData; - if (multiPtr) { - if (multiPtr->strDef) { - DC->getCVarString(item->cvar, buff, sizeof(buff)); - } else { - value = DC->getCVarValue(item->cvar); - } - for (i = 0; i < multiPtr->count; i++) { - if (multiPtr->strDef) { - if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) { - return i; - } - } else { - if (multiPtr->cvarValue[i] == value) { - return i; - } - } - } - } - return 0; -} - -const char *Item_Multi_Setting(itemDef_t *item) { - char buff[1024]; - float value = 0; - int i; - multiDef_t *multiPtr = (multiDef_t*)item->typeData; - if (multiPtr) { - if (multiPtr->strDef) { - DC->getCVarString(item->cvar, buff, sizeof(buff)); - } else { - value = DC->getCVarValue(item->cvar); - } - for (i = 0; i < multiPtr->count; i++) { - if (multiPtr->strDef) { - if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) { - return multiPtr->cvarList[i]; - } - } else { - if (multiPtr->cvarValue[i] == value) { - return multiPtr->cvarList[i]; - } - } - } - } - return ""; -} - -qboolean Item_Multi_HandleKey(itemDef_t *item, int key) { - multiDef_t *multiPtr = (multiDef_t*)item->typeData; - if (multiPtr) { - if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) { - if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) { - int current = Item_Multi_FindCvarByValue(item) + 1; - int max = Item_Multi_CountSettings(item); - if ( current < 0 || current >= max ) { - current = 0; - } - if (multiPtr->strDef) { - DC->setCVar(item->cvar, multiPtr->cvarStr[current]); - } else { - float value = multiPtr->cvarValue[current]; - if (((float)((int) value)) == value) { - DC->setCVar(item->cvar, va("%i", (int) value )); - } - else { - DC->setCVar(item->cvar, va("%f", value )); - } +void Menu_TransitionItemByName( + menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); + + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + + if (item != NULL) + { + item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE); + item->window.offsetTime = time; + memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t)); + memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t)); + item->window.rectEffects2.x = fabs(rectTo.x - rectFrom.x) / amt; + item->window.rectEffects2.y = fabs(rectTo.y - rectFrom.y) / amt; + item->window.rectEffects2.w = fabs(rectTo.w - rectFrom.w) / amt; + item->window.rectEffects2.h = fabs(rectTo.h - rectFrom.h) / amt; + Item_UpdatePosition(item); } - return qtrue; - } } - } - return qfalse; } -qboolean Item_TextField_HandleKey(itemDef_t *item, int key) { - char buff[1024]; - int len; - itemDef_t *newItem = NULL; - editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; - - if (item->cvar) { +void Script_Transition(itemDef_t *item, char **args) +{ + const char *name; + rectDef_t rectFrom, rectTo; + int time; + float amt; - memset(buff, 0, sizeof(buff)); - DC->getCVarString(item->cvar, buff, sizeof(buff)); - len = strlen(buff); - if (editPtr->maxChars && len > editPtr->maxChars) { - len = editPtr->maxChars; + if (String_Parse(args, &name)) + { + if (Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) && + Float_Parse(args, &amt)) + { + Menu_TransitionItemByName(item->parent, name, rectFrom, rectTo, time, amt); + } } - if ( key & K_CHAR_FLAG ) { - key &= ~K_CHAR_FLAG; - +} - if (key == 'h' - 'a' + 1 ) { // ctrl-h is backspace - if ( item->cursorPos > 0 ) { - memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos); - item->cursorPos--; - if (item->cursorPos < editPtr->paintOffset) { - editPtr->paintOffset--; - } - } - DC->setCVar(item->cvar, buff); - return qtrue; - } +void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); - // - // ignore any non printable chars - // - if ( key < 32 || !item->cvar) { - return qtrue; + if (item != NULL) + { + item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE); + item->window.offsetTime = time; + item->window.rectEffects.x = cx; + item->window.rectEffects.y = cy; + item->window.rectClient.x = x; + item->window.rectClient.y = y; + Item_UpdatePosition(item); } + } +} - if (item->type == ITEM_TYPE_NUMERICFIELD) { - if (key < '0' || key > '9') { - return qfalse; - } - } +void Script_Orbit(itemDef_t *item, char **args) +{ + const char *name; + float cx, cy, x, y; + int time; - if (!DC->getOverstrikeMode()) { - if (( len == MAX_EDITFIELD - 1 ) || (editPtr->maxChars && len >= editPtr->maxChars)) { - return qtrue; - } - memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); - } else { - if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars) { - return qtrue; + if (String_Parse(args, &name)) + { + if (Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) && + Int_Parse(args, &time)) + { + Menu_OrbitItemByName(item->parent, name, x, y, cx, cy, time); } - } + } +} - buff[item->cursorPos] = key; +void Script_SetFocus(itemDef_t *item, char **args) +{ + const char *name; + itemDef_t *focusItem; - DC->setCVar(item->cvar, buff); + if (String_Parse(args, &name)) + { + focusItem = Menu_FindItemByName(item->parent, name); - if (item->cursorPos < len + 1) { - item->cursorPos++; - if (editPtr->maxPaintChars && item->cursorPos > editPtr->maxPaintChars) { - editPtr->paintOffset++; - } - } + if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION)) + { + Menu_ClearFocus(item->parent); + focusItem->window.flags |= WINDOW_HASFOCUS; - } else { + if (focusItem->onFocus) + Item_RunScript(focusItem, focusItem->onFocus); - if ( key == K_DEL || key == K_KP_DEL ) { - if ( item->cursorPos < len ) { - memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos); - DC->setCVar(item->cvar, buff); - } - return qtrue; - } + // Edit fields get activated too + if (Item_IsEditField(focusItem)) + { + g_editingField = qtrue; + g_editItem = focusItem; + } - if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) - { - if (editPtr->maxPaintChars && item->cursorPos >= editPtr->maxPaintChars && item->cursorPos < len) { - item->cursorPos++; - editPtr->paintOffset++; - return qtrue; + if (DC->Assets.itemFocusSound) + DC->startLocalSound(DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND); } - if (item->cursorPos < len) { - item->cursorPos++; - } - return qtrue; - } + } +} - if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) - { - if ( item->cursorPos > 0 ) { - item->cursorPos--; - } - if (item->cursorPos < editPtr->paintOffset) { - editPtr->paintOffset--; - } - return qtrue; - } +void Script_Reset(itemDef_t *item, char **args) +{ + const char *name; + itemDef_t *resetItem; - if ( key == K_HOME || key == K_KP_HOME) {// || ( tolower(key) == 'a' && trap_Key_IsDown( K_CTRL ) ) ) { - item->cursorPos = 0; - editPtr->paintOffset = 0; - return qtrue; - } + if (String_Parse(args, &name)) + { + resetItem = Menu_FindItemByName(item->parent, name); - if ( key == K_END || key == K_KP_END) {// ( tolower(key) == 'e' && trap_Key_IsDown( K_CTRL ) ) ) { - item->cursorPos = len; - if(item->cursorPos > editPtr->maxPaintChars) { - editPtr->paintOffset = len - editPtr->maxPaintChars; + if (resetItem) + { + if (Item_IsListBox(resetItem)) + { + resetItem->cursorPos = DC->feederInitialise(resetItem->feederID); + Item_ListBox_SetStartPos(resetItem, 0); + DC->feederSelection(resetItem->feederID, resetItem->cursorPos); + } } - return qtrue; - } - - if ( key == K_INS || key == K_KP_INS ) { - DC->setOverstrikeMode(!DC->getOverstrikeMode()); - return qtrue; - } - } - - if (key == K_TAB || key == K_DOWNARROW || key == K_KP_DOWNARROW) { - if (item->type == ITEM_TYPE_SAYFIELD) { - return qtrue; - } - - newItem = Menu_SetNextCursorItem(item->parent); - if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_SAYFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) { - g_editItem = newItem; - } } +} - if (key == K_UPARROW || key == K_KP_UPARROW) { - if (item->type == ITEM_TYPE_SAYFIELD) { - return qtrue; - } +void Script_SetPlayerModel(itemDef_t *item, char **args) +{ + const char *name; - newItem = Menu_SetPrevCursorItem(item->parent); - if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_SAYFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) { - g_editItem = newItem; - } - } + (void)item; - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 || key == K_MOUSE4) { - if (item->type == ITEM_TYPE_SAYFIELD) { - return qtrue; - } - return qfalse; - } + if (String_Parse(args, &name)) + DC->setCVar("model", name); +} - if ( key == K_ENTER || key == K_KP_ENTER || key == K_ESCAPE) { - return qfalse; - } +void Script_SetPlayerHead(itemDef_t *item, char **args) +{ + const char *name; - return qtrue; - } - return qfalse; - -} - -static void Scroll_ListBox_AutoFunc(void *p) { - scrollInfo_t *si = (scrollInfo_t*)p; - if (DC->realTime > si->nextScrollTime) { - // need to scroll which is done by simulating a click to the item - // this is done a bit sideways as the autoscroll "knows" that the item is a listbox - // so it calls it directly - Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); - si->nextScrollTime = DC->realTime + si->adjustValue; - } - - if (DC->realTime > si->nextAdjustTime) { - si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; - if (si->adjustValue > SCROLL_TIME_FLOOR) { - si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; - } - } -} - -static void Scroll_ListBox_ThumbFunc(void *p) { - scrollInfo_t *si = (scrollInfo_t*)p; - rectDef_t r; - int pos, max; - - listBoxDef_t *listPtr = (listBoxDef_t*)si->item->typeData; - if (si->item->window.flags & WINDOW_HORIZONTAL) { - if (DC->cursorx == si->xStart) { - return; - } - r.x = si->item->window.rect.x + SCROLLBAR_SIZE + 1; - r.y = si->item->window.rect.y + si->item->window.rect.h - SCROLLBAR_SIZE - 1; - r.h = SCROLLBAR_SIZE; - r.w = si->item->window.rect.w - (SCROLLBAR_SIZE*2) - 2; - max = Item_ListBox_MaxScroll(si->item); - // - pos = (DC->cursorx - r.x - SCROLLBAR_SIZE/2) * max / (r.w - SCROLLBAR_SIZE); - if (pos < 0) { - pos = 0; - } - else if (pos > max) { - pos = max; - } - listPtr->startPos = pos; - si->xStart = DC->cursorx; - } - else if (DC->cursory != si->yStart) { - - r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1; - r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1; - r.h = si->item->window.rect.h - (SCROLLBAR_SIZE*2) - 2; - r.w = SCROLLBAR_SIZE; - max = Item_ListBox_MaxScroll(si->item); - // - pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * max / (r.h - SCROLLBAR_SIZE); - if (pos < 0) { - pos = 0; - } - else if (pos > max) { - pos = max; - } - listPtr->startPos = pos; - si->yStart = DC->cursory; - } - - if (DC->realTime > si->nextScrollTime) { - // need to scroll which is done by simulating a click to the item - // this is done a bit sideways as the autoscroll "knows" that the item is a listbox - // so it calls it directly - Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); - si->nextScrollTime = DC->realTime + si->adjustValue; - } - - if (DC->realTime > si->nextAdjustTime) { - si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; - if (si->adjustValue > SCROLL_TIME_FLOOR) { - si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; - } - } -} - -static void Scroll_Slider_ThumbFunc(void *p) { - float x, value, cursorx; - scrollInfo_t *si = (scrollInfo_t*)p; - editFieldDef_t *editDef = si->item->typeData; - - if (si->item->text) { - x = si->item->textRect.x + si->item->textRect.w + 8; - } else { - x = si->item->window.rect.x; - } - - cursorx = DC->cursorx; - - if (cursorx < x) { - cursorx = x; - } else if (cursorx > x + SLIDER_WIDTH) { - cursorx = x + SLIDER_WIDTH; - } - value = cursorx - x; - value /= SLIDER_WIDTH; - value *= (editDef->maxVal - editDef->minVal); - value += editDef->minVal; - DC->setCVar(si->item->cvar, va("%f", value)); -} - -void Item_StartCapture(itemDef_t *item, int key) { - int flags; - switch (item->type) { - case ITEM_TYPE_EDITFIELD: - case ITEM_TYPE_SAYFIELD: - case ITEM_TYPE_NUMERICFIELD: - - case ITEM_TYPE_LISTBOX: - { - flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory); - if (flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW)) { - scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; - scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; - scrollInfo.adjustValue = SCROLL_TIME_START; - scrollInfo.scrollKey = key; - scrollInfo.scrollDir = (flags & WINDOW_LB_LEFTARROW) ? qtrue : qfalse; - scrollInfo.item = item; - captureData = &scrollInfo; - captureFunc = &Scroll_ListBox_AutoFunc; - itemCapture = item; - } else if (flags & WINDOW_LB_THUMB) { - scrollInfo.scrollKey = key; - scrollInfo.item = item; - scrollInfo.xStart = DC->cursorx; - scrollInfo.yStart = DC->cursory; - captureData = &scrollInfo; - captureFunc = &Scroll_ListBox_ThumbFunc; - itemCapture = item; - } - break; - } - case ITEM_TYPE_SLIDER: - { - flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory); - if (flags & WINDOW_LB_THUMB) { - scrollInfo.scrollKey = key; - scrollInfo.item = item; - scrollInfo.xStart = DC->cursorx; - scrollInfo.yStart = DC->cursory; - captureData = &scrollInfo; - captureFunc = &Scroll_Slider_ThumbFunc; - itemCapture = item; - } - break; - } - } -} - -void Item_StopCapture(itemDef_t *item) { - -} - -qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) { - float x, value, width, work; - - if (item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { - if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) { - editFieldDef_t *editDef = item->typeData; - if (editDef) { - rectDef_t testRect; - width = SLIDER_WIDTH; - if (item->text) { - x = item->textRect.x + item->textRect.w + 8; - } else { - x = item->window.rect.x; - } - - testRect = item->window.rect; - testRect.x = x; - value = (float)SLIDER_THUMB_WIDTH / 2; - testRect.x -= value; - testRect.w = (SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2); - if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) { - work = DC->cursorx - x; - value = work / width; - value *= (editDef->maxVal - editDef->minVal); - // vm fuckage - // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal)); - value += editDef->minVal; - DC->setCVar(item->cvar, va("%f", value)); - return qtrue; - } - } - } - } - return qfalse; -} - - -qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) { - - if (itemCapture) { - Item_StopCapture(itemCapture); - itemCapture = NULL; - captureFunc = voidFunction; - captureData = NULL; - } else { - // bk001206 - parentheses - if ( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { - Item_StartCapture(item, key); - } - } + (void)item; - if (!down) { - return qfalse; - } - - switch (item->type) { - case ITEM_TYPE_BUTTON: - return qfalse; - break; - case ITEM_TYPE_RADIOBUTTON: - return qfalse; - break; - case ITEM_TYPE_CHECKBOX: - return qfalse; - break; - case ITEM_TYPE_EDITFIELD: - case ITEM_TYPE_SAYFIELD: - case ITEM_TYPE_NUMERICFIELD: - //return Item_TextField_HandleKey(item, key); - return qfalse; - break; - case ITEM_TYPE_COMBO: - return qfalse; - break; - case ITEM_TYPE_LISTBOX: - return Item_ListBox_HandleKey(item, key, down, qfalse); - break; - case ITEM_TYPE_YESNO: - return Item_YesNo_HandleKey(item, key); - break; - case ITEM_TYPE_MULTI: - return Item_Multi_HandleKey(item, key); - break; - case ITEM_TYPE_OWNERDRAW: - return Item_OwnerDraw_HandleKey(item, key); - break; - case ITEM_TYPE_BIND: - return Item_Bind_HandleKey(item, key, down); - break; - case ITEM_TYPE_SLIDER: - return Item_Slider_HandleKey(item, key, down); - break; - //case ITEM_TYPE_IMAGE: - // Item_Image_Paint(item); - // break; - default: - return qfalse; - break; - } - - //return qfalse; -} - -void Item_Action(itemDef_t *item) { - if (item) { - Item_RunScript(item, item->action); - } -} - -itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) { - qboolean wrapped = qfalse; - int oldCursor = menu->cursorItem; - - if (menu->cursorItem < 0) { - menu->cursorItem = menu->itemCount-1; - wrapped = qtrue; - } - - while (menu->cursorItem > -1) { - - menu->cursorItem--; - if (menu->cursorItem < 0 && !wrapped) { - wrapped = qtrue; - menu->cursorItem = menu->itemCount -1; - } - - if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) { - Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); - return menu->items[menu->cursorItem]; - } - } - menu->cursorItem = oldCursor; - return NULL; - -} - -itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu) { - - qboolean wrapped = qfalse; - int oldCursor = menu->cursorItem; - - - if (menu->cursorItem == -1) { - menu->cursorItem = 0; - wrapped = qtrue; - } - - while (menu->cursorItem < menu->itemCount) { - - menu->cursorItem++; - if (menu->cursorItem >= menu->itemCount && !wrapped) { - wrapped = qtrue; - menu->cursorItem = 0; - } - if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) { - Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); - return menu->items[menu->cursorItem]; - } - - } - - menu->cursorItem = oldCursor; - return NULL; + if (String_Parse(args, &name)) + DC->setCVar("headmodel", name); } -static void Window_CloseCinematic(windowDef_t *window) { - if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) { - DC->stopCinematic(window->cinematic); - window->cinematic = -1; - } -} +void Script_SetCvar(itemDef_t *item, char **args) +{ + const char *cvar, *val; -static void Menu_CloseCinematics(menuDef_t *menu) { - if (menu) { - int i; - Window_CloseCinematic(&menu->window); - for (i = 0; i < menu->itemCount; i++) { - Window_CloseCinematic(&menu->items[i]->window); - if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW) { - DC->stopCinematic(0-menu->items[i]->window.ownerDraw); - } - } - } -} + (void)item; -static void Display_CloseCinematics( void ) { - int i; - for (i = 0; i < menuCount; i++) { - Menu_CloseCinematics(&Menus[i]); - } + if (String_Parse(args, &cvar) && String_Parse(args, &val)) + DC->setCVar(cvar, val); } -void Menus_Activate(menuDef_t *menu) { - menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); - if (menu->onOpen) { - itemDef_t item; - item.parent = menu; - Item_RunScript(&item, menu->onOpen); - } - - if (menu->soundName && *menu->soundName) { -// DC->stopBackgroundTrack(); // you don't want to do this since it will reset s_rawend - DC->startBackgroundTrack(menu->soundName, menu->soundName); - } +void Script_Exec(itemDef_t *item, char **args) +{ + const char *val; - Display_CloseCinematics(); + (void)item; + if (String_Parse(args, &val)) + DC->executeText(EXEC_APPEND, va("%s\n", val)); } -int Display_VisibleMenuCount( void ) { - int i, count; - count = 0; - for (i = 0; i < menuCount; i++) { - if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) { - count++; - } - } - return count; +void Script_Play(itemDef_t *item, char **args) +{ + const char *val; + + (void)item; + + if (String_Parse(args, &val)) + DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_LOCAL_SOUND); } -void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) { - if (menu) { - int i; - // basically the behaviour we are looking for is if there are windows in the stack.. see if - // the cursor is within any of them.. if not close them otherwise activate them and pass the - // key on.. force a mouse move to activate focus and script stuff - if (down && menu->window.flags & WINDOW_OOB_CLICK) { - Menu_RunCloseScript(menu); - menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); - } +void Script_playLooped(itemDef_t *item, char **args) +{ + const char *val; - for (i = 0; i < menuCount; i++) { - if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) { - Menu_RunCloseScript(menu); - menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); - Menus_Activate(&Menus[i]); - Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory); - Menu_HandleKey(&Menus[i], key, down); - } - } + (void)item; - if (Display_VisibleMenuCount() == 0) { - if (DC->Pause) { - DC->Pause(qfalse); - } + if (String_Parse(args, &val)) + { + DC->stopBackgroundTrack(); + DC->startBackgroundTrack(val, val); } - Display_CloseCinematics(); - } } -static rectDef_t *Item_CorrectedTextRect(itemDef_t *item) { - static rectDef_t rect; - memset(&rect, 0, sizeof(rectDef_t)); - if (item) { - rect = item->textRect; - if (rect.w) { - rect.y -= rect.h; - } - } - return ▭ +static ID_INLINE float UI_EmoticonHeight(fontInfo_t *font, float scale) +{ + return font->glyphs[(int)'['].height * scale * font->glyphScale; } -void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) { - int i; - itemDef_t *item = NULL; - qboolean inHandler = qfalse; - - if (inHandler) { - return; - } - - inHandler = qtrue; - if (g_waitingForKey && down) { - Item_Bind_HandleKey(g_bindItem, key, down); - inHandler = qfalse; - return; - } - - if (g_editingField && down) { - if (!Item_TextField_HandleKey(g_editItem, key)) { - g_editingField = qfalse; - Item_RunScript(g_editItem, g_editItem->onTextEntry); - g_editItem = NULL; - inHandler = qfalse; - return; - } - } +static ID_INLINE float UI_EmoticonWidth(fontInfo_t *font, float scale) +{ + return UI_EmoticonHeight(font, scale) * DC->aspectScale; +} - if (menu == NULL) { - inHandler = qfalse; - return; - } +void UI_EscapeEmoticons(char *dest, const char *src, int destsize) +{ + int len; + qboolean escaped; - // see if the mouse is within the window bounds and if so is this a mouse click - if (down && !(menu->window.flags & WINDOW_POPUP) && !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) { - static qboolean inHandleKey = qfalse; - // bk001206 - parentheses - if (!inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { - inHandleKey = qtrue; - Menus_HandleOOBClick(menu, key, down); - inHandleKey = qfalse; - inHandler = qfalse; - return; - } - } - - // get the item with focus - for (i = 0; i < menu->itemCount; i++) { - if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { - item = menu->items[i]; - } - } - - if (item != NULL) { - if (Item_HandleKey(item, key, down)) { - Item_Action(item); - inHandler = qfalse; - return; - } - } - - if (!down) { - inHandler = qfalse; - return; - } - - // default handling - switch ( key ) { - - case K_F11: - if (DC->getCVarValue("developer")) { - debugMode ^= 1; - } - break; - - case K_F12: - if (DC->getCVarValue("developer")) { - DC->executeText(EXEC_APPEND, "screenshot\n"); - } - break; - case K_KP_UPARROW: - case K_UPARROW: - Menu_SetPrevCursorItem(menu); - break; - - case K_ESCAPE: - if (!g_waitingForKey && menu->onESC) { - itemDef_t it; - it.parent = menu; - Item_RunScript(&it, menu->onESC); - } - break; - case K_TAB: - case K_KP_DOWNARROW: - case K_DOWNARROW: - Menu_SetNextCursorItem(menu); - break; - - case K_MOUSE1: - case K_MOUSE2: - if (item) { - if (item->type == ITEM_TYPE_TEXT) { - if (Rect_ContainsPoint(Item_CorrectedTextRect(item), DC->cursorx, DC->cursory)) { - Item_Action(item); - } - } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) { - if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { - item->cursorPos = 0; - g_editingField = qtrue; - g_editItem = item; - DC->setOverstrikeMode(qtrue); - } - } else if (item->type == ITEM_TYPE_SAYFIELD) { - // do nothing - } else { - if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { - Item_Action(item); - } - } - } - break; - - 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: - break; - case K_KP_ENTER: - case K_ENTER: - if (item) { - if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD || item->type == ITEM_TYPE_NUMERICFIELD) { - item->cursorPos = 0; - g_editingField = qtrue; - g_editItem = item; - DC->setOverstrikeMode(qtrue); - } else { - Item_Action(item); + for (; *src && destsize > 1; src++, destsize--) + { + if (UI_Text_IsEmoticon(src, &escaped, &len, NULL, NULL) && !escaped) + { + *dest++ = '['; + destsize--; } - } - break; - } - inHandler = qfalse; -} - -void ToWindowCoords(float *x, float *y, windowDef_t *window) { - if (window->border != 0) { - *x += window->borderSize; - *y += window->borderSize; - } - *x += window->rect.x; - *y += window->rect.y; -} - -void Rect_ToWindowCoords(rectDef_t *rect, windowDef_t *window) { - ToWindowCoords(&rect->x, &rect->y, window); -} - -void Item_SetTextExtents(itemDef_t *item, int *width, int *height, const char *text) { - const char *textPtr = (text) ? text : item->text; - - if (textPtr == NULL ) { - return; - } - - *width = item->textRect.w; - *height = item->textRect.h; - - // keeps us from computing the widths and heights more than once - if (*width == 0 || (item->type == ITEM_TYPE_OWNERDRAW && item->textalignment == ITEM_ALIGN_CENTER)) { - int originalWidth = DC->textWidth(item->text, item->textscale, 0); - - if (item->type == ITEM_TYPE_OWNERDRAW && (item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_RIGHT)) { - originalWidth += DC->ownerDrawWidth(item->window.ownerDraw, item->textscale); - } else if (item->type == ITEM_TYPE_EDITFIELD && item->textalignment == ITEM_ALIGN_CENTER && item->cvar) { - char buff[256]; - DC->getCVarString(item->cvar, buff, 256); - originalWidth += DC->textWidth(buff, item->textscale, 0); - } - - *width = DC->textWidth(textPtr, item->textscale, 0); - *height = DC->textHeight(textPtr, item->textscale, 0); - item->textRect.w = *width; - item->textRect.h = *height; - item->textRect.x = item->textalignx; - item->textRect.y = item->textaligny; - if (item->textalignment == ITEM_ALIGN_RIGHT) { - item->textRect.x = item->textalignx - originalWidth; - } else if (item->textalignment == ITEM_ALIGN_CENTER) { - item->textRect.x = item->textalignx - originalWidth / 2; - } - - ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window); - } -} - -void Item_TextColor(itemDef_t *item, vec4_t *newColor) { - vec4_t lowLight; - menuDef_t *parent = (menuDef_t*)item->parent; - - Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); - - if (item->window.flags & WINDOW_HASFOCUS) { -/* lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; - LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); - } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) { - lowLight[0] = 0.8 * item->window.foreColor[0]; - lowLight[1] = 0.8 * item->window.foreColor[1]; - lowLight[2] = 0.8 * item->window.foreColor[2]; - lowLight[3] = 0.8 * item->window.foreColor[3]; - LerpColor(item->window.foreColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); - } else { - memcpy(newColor, &item->window.foreColor, sizeof(vec4_t)); - // items can be enabled and disabled based on cvars - } - if (item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest) { - if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { - memcpy(newColor, &parent->disableColor, sizeof(vec4_t)); + *dest++ = *src; } - } + + *dest++ = '\0'; } -int Item_Text_AutoWrapped_Lines( itemDef_t *item ) +qboolean UI_Text_IsEmoticon(const char *s, qboolean *escaped, int *length, qhandle_t *h, int *width) { - char text[ 1024 ]; - const char *p, *textPtr, *newLinePtr; - char buff[ 1024 ]; - int len, textWidth, newLine; - int lines = 0; + const char *p = s; + char emoticon[MAX_EMOTICON_NAME_LEN]; + int i; - textWidth = 0; - newLinePtr = NULL; + if (*p != '[') + return qfalse; + p++; - if( item->text == NULL ) - { - if( item->cvar == NULL ) - return 0; - else + if (*p == '[') { - DC->getCVarString( item->cvar, text, sizeof( text ) ); - textPtr = text; + *escaped = qtrue; + p++; } - } - else - textPtr = item->text; - - if( *textPtr == '\0' ) - return 0; - - len = 0; - buff[ 0 ] = '\0'; - newLine = 0; - p = textPtr; - - while( p ) - { - textWidth = DC->textWidth( buff, item->textscale, 0 ); + else + *escaped = qfalse; - if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' ) + for (*length = 0; p[*length] != ']'; (*length)++) { - newLine = len; - newLinePtr = p + 1; - } + if (!p[*length] || *length == MAX_EMOTICON_NAME_LEN - 1) + return qfalse; - //TA: forceably split lines that are too long (where normal splitage has failed) - if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' ) - { - newLine = len; - newLinePtr = p; + emoticon[*length] = p[*length]; } + emoticon[*length] = '\0'; - if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' ) - { - if( len ) - buff[ newLine ] = '\0'; - - if( !( *p == '\n' && !*( p + 1 ) ) ) - lines++; + for (i = 0; i < DC->Assets.emoticonCount; i++) + if (!Q_stricmp(DC->Assets.emoticons[i].name, emoticon)) + break; - if( *p == '\0' ) - break; + if (i == DC->Assets.emoticonCount) + return qfalse; - // - p = newLinePtr; - len = 0; - newLine = 0; + if (h) + *h = DC->Assets.emoticons[i].shader; + if (width) + *width = DC->Assets.emoticons[i].width; - continue; - } + (*length) += 2; - buff[ len++ ] = *p++; - buff[ len ] = '\0'; - } + if (*escaped) + (*length)++; - return lines; + return qtrue; } -#define MAX_AUTOWRAP_CACHE 16 -#define MAX_AUTOWRAP_LINES 32 -#define MAX_AUTOWRAP_TEXT 512 - -typedef struct +static float UI_Parse_Indent(const char **text) { - //this is used purely for checking for cache hits - char text[ MAX_AUTOWRAP_TEXT * MAX_AUTOWRAP_LINES ]; - rectDef_t rect; - int textWidth, textHeight; - char lines[ MAX_AUTOWRAP_LINES ][ MAX_AUTOWRAP_TEXT ]; - int lineOffsets[ MAX_AUTOWRAP_LINES ][ 2 ]; - int numLines; -} autoWrapCache_t; + char indentWidth[32]; + char *indentWidthPtr; + const char *p = *text; + int numDigits; + float pixels; -static int cacheIndex = 0; -static autoWrapCache_t awc[ MAX_AUTOWRAP_CACHE ]; + while (isdigit(*p) || *p == '.') + p++; -static int checkCache( const char *text, rectDef_t *rect, int width, int height ) -{ - int i; + if (*p != INDENT_MARKER) + return 0.0f; - for( i = 0; i < MAX_AUTOWRAP_CACHE; i++ ) - { - if( Q_stricmp( text, awc[ i ].text ) ) - continue; + numDigits = (p - *text); - if( rect->x != awc[ i ].rect.x || - rect->y != awc[ i ].rect.y || - rect->w != awc[ i ].rect.w || - rect->h != awc[ i ].rect.h ) - continue; + if (numDigits > sizeof(indentWidth) - 1) + return 0.0f; - if( awc[ i ].textWidth != width || awc[ i ].textHeight != height ) - continue; + strncpy(indentWidth, *text, numDigits); - //this is a match - return i; - } + indentWidth[numDigits] = '\0'; + indentWidthPtr = indentWidth; - //no match - autowrap isn't cached - return -1; -} + if (!Float_Parse(&indentWidthPtr, &pixels)) + return 0.0f; -void Item_Text_AutoWrapped_Paint( itemDef_t *item ) -{ - char text[ 1024 ]; - const char *p, *textPtr, *newLinePtr; - char buff[ 1024 ]; - char lastCMod[ 2 ] = { 0, 0 }; - qboolean forwardColor = qfalse; - int width, height, len, textWidth, newLine, newLineWidth; - int skipLines, totalLines, lineNum = 0; - float y, totalY, diffY; - vec4_t color; - int cache, i; + (*text) += (numDigits + 1); - textWidth = 0; - newLinePtr = NULL; + return pixels; +} - if( item->text == NULL ) - { - if( item->cvar == NULL ) - return; +static ID_INLINE fontInfo_t *UI_FontForScale(float scale) +{ + if (scale <= DC->smallFontScale) + return &DC->Assets.smallFont; + else if (scale >= DC->bigFontScale) + return &DC->Assets.bigFont; else - { - DC->getCVarString( item->cvar, text, sizeof( text ) ); - textPtr = text; - } - } - else - textPtr = item->text; - - if( *textPtr == '\0' ) - return; - - Item_TextColor( item, &color ); - Item_SetTextExtents( item, &width, &height, textPtr ); + return &DC->Assets.textFont; +} - //check if this block is cached - cache = checkCache( textPtr, &item->window.rect, width, height ); - if( cache >= 0 ) - { - lineNum = awc[ cache ].numLines; +float UI_Char_Width(const char **text, float scale) +{ + glyphInfo_t *glyph; + fontInfo_t *font; + int emoticonLen; + qboolean emoticonEscaped; + int emoticonWidth; - for( i = 0; i < lineNum; i++ ) + if (text && *text) { - item->textRect.x = awc[ cache ].lineOffsets[ i ][ 0 ]; - item->textRect.y = awc[ cache ].lineOffsets[ i ][ 1 ]; - - DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, - awc[ cache ].lines[ i ], 0, 0, item->textStyle ); - } - } - else - { - y = item->textaligny; - len = 0; - buff[ 0 ] = '\0'; - newLine = 0; - newLineWidth = 0; - p = textPtr; - - totalLines = Item_Text_AutoWrapped_Lines( item ); - - totalY = totalLines * ( height + 5 ); - diffY = totalY - item->window.rect.h; - - if( diffY > 0.0f ) - skipLines = (int)( diffY / ( (float)height + 5.0f ) ); - else - skipLines = 0; - - //set up a cache entry - strcpy( awc[ cacheIndex ].text, textPtr ); - awc[ cacheIndex ].rect.x = item->window.rect.x; - awc[ cacheIndex ].rect.y = item->window.rect.y; - awc[ cacheIndex ].rect.w = item->window.rect.w; - awc[ cacheIndex ].rect.h = item->window.rect.h; - awc[ cacheIndex ].textWidth = width; - awc[ cacheIndex ].textHeight = height; - - while( p ) - { - textWidth = DC->textWidth( buff, item->textscale, 0 ); - - if( *p == '^' ) - { - lastCMod[ 0 ] = p[ 0 ]; - lastCMod[ 1 ] = p[ 1 ]; - } - - if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' ) - { - newLine = len; - newLinePtr = p+1; - newLineWidth = textWidth; - - if( *p == '\n' ) //don't forward colours past deilberate \n's - lastCMod[ 0 ] = lastCMod[ 1 ] = 0; - else - forwardColor = qtrue; - } - - //TA: forceably split lines that are too long (where normal splitage has failed) - if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' ) - { - newLine = len; - newLinePtr = p; - newLineWidth = textWidth; - - forwardColor = qtrue; - } - - if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' ) - { - if( len ) + if (Q_IsColorString(*text)) { - if( item->textalignment == ITEM_ALIGN_LEFT ) - item->textRect.x = item->textalignx; - else if( item->textalignment == ITEM_ALIGN_RIGHT ) - item->textRect.x = item->textalignx - newLineWidth; - else if( item->textalignment == ITEM_ALIGN_CENTER ) - item->textRect.x = item->textalignx - newLineWidth / 2; - - item->textRect.y = y; - ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window ); - // - buff[ newLine ] = '\0'; - - if( !skipLines ) - { - DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, 0, item->textStyle ); - - strcpy( awc[ cacheIndex ].lines[ lineNum ], buff ); - awc[ cacheIndex ].lineOffsets[ lineNum ][ 0 ] = item->textRect.x; - awc[ cacheIndex ].lineOffsets[ lineNum ][ 1 ] = item->textRect.y; - - lineNum++; - } + *text += 2; + return 0.0f; } - if( *p == '\0' ) - break; - - // - if( !skipLines ) - y += height + 5; - if( skipLines ) - skipLines--; - - p = newLinePtr; - len = 0; - newLine = 0; - newLineWidth = 0; + if (**text == INDENT_MARKER) + { + (*text)++; + return 0.0f; + } - if( forwardColor && lastCMod[ 0 ] != 0 ) - { - buff[ len++ ] = lastCMod[ 0 ]; - buff[ len++ ] = lastCMod[ 1 ]; - buff[ len ] = '\0'; + font = UI_FontForScale(scale); - forwardColor = qfalse; + if (UI_Text_IsEmoticon(*text, &emoticonEscaped, &emoticonLen, NULL, &emoticonWidth)) + { + if (emoticonEscaped) + (*text)++; + else + { + *text += emoticonLen; + return emoticonWidth * UI_EmoticonWidth(font, scale); + } } - continue; - } + (*text)++; - buff[ len++ ] = *p++; - buff[ len ] = '\0'; + glyph = &font->glyphs[(int)**text]; + return glyph->xSkip * DC->aspectScale * scale * font->glyphScale; } - //mark the end of the lines list - awc[ cacheIndex ].numLines = lineNum; - - //increment cacheIndex - cacheIndex = ( cacheIndex + 1 ) % MAX_AUTOWRAP_CACHE; - } + return 0.0f; } -void Item_Text_Wrapped_Paint(itemDef_t *item) { - char text[1024]; - const char *p, *start, *textPtr; - char buff[1024]; - int width, height; - float x, y; - vec4_t color; - - // now paint the text and/or any optional images - // default to left - - if (item->text == NULL) { - if (item->cvar == NULL) { - return; - } - else { - DC->getCVarString(item->cvar, text, sizeof(text)); - textPtr = text; - } - } - else { - textPtr = item->text; - } - if (*textPtr == '\0') { - return; - } - - Item_TextColor(item, &color); - Item_SetTextExtents(item, &width, &height, textPtr); - - x = item->textRect.x; - y = item->textRect.y; - start = textPtr; - p = strchr(textPtr, '\r'); - while (p && *p) { - strncpy(buff, start, p-start+1); - buff[p-start] = '\0'; - DC->drawText(x, y, item->textscale, color, buff, 0, 0, item->textStyle); - y += height + 5; - start += p - start + 1; - p = strchr(p+1, '\r'); - } - DC->drawText(x, y, item->textscale, color, start, 0, 0, item->textStyle); -} +float UI_Text_Width(const char *text, float scale) +{ + float out; + const char *s = text; + float indentWidth = 0.0f; -void Item_Text_Paint(itemDef_t *item) { - char text[1024]; - const char *textPtr; - int height, width; - vec4_t color; + out = 0.0f; - if (item->window.flags & WINDOW_WRAPPED) { - Item_Text_Wrapped_Paint(item); - return; - } - if (item->window.flags & WINDOW_AUTOWRAPPED) { - Item_Text_AutoWrapped_Paint(item); - return; - } + if (text) + { + indentWidth = UI_Parse_Indent(&s); - if (item->text == NULL) { - if (item->cvar == NULL) { - return; - } - else { - DC->getCVarString(item->cvar, text, sizeof(text)); - textPtr = text; + while (*s) + out += UI_Char_Width(&s, scale); } - } - else { - textPtr = item->text; - } - // this needs to go here as it sets extents for cvar types as well - Item_SetTextExtents(item, &width, &height, textPtr); + return out + indentWidth; +} - if (*textPtr == '\0') { - return; - } +float UI_Text_Height(const char *text, float scale) +{ + float max; + glyphInfo_t *glyph; + float useScale; + const char *s = text; + fontInfo_t *font = UI_FontForScale(scale); + useScale = scale * font->glyphScale; + max = 0; - Item_TextColor(item, &color); + if (text) + { + while (s && *s) + { + if (Q_IsColorString(s)) + { + s += 2; + continue; + } + else + { + glyph = &font->glyphs[(int)*s]; - //FIXME: this is a fucking mess -/* - adjust = 0; - if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { - adjust = 0.5; - } - - if (item->textStyle == ITEM_TEXTSTYLE_SHADOWED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { - Fade(&item->window.flags, &DC->Assets.shadowColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse); - DC->drawText(item->textRect.x + DC->Assets.shadowX, item->textRect.y + DC->Assets.shadowY, item->textscale, DC->Assets.shadowColor, textPtr, adjust); - } -*/ + if (max < glyph->height) + max = glyph->height; + s++; + } + } + } -// if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { -// Fade(&item->window.flags, &item->window.outlineColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse); -// /* -// Text_Paint(item->textRect.x-1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x+1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x-1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x+1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x-1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); -// Text_Paint(item->textRect.x+1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); -// */ -// DC->drawText(item->textRect.x - 1, item->textRect.y + 1, item->textscale * 1.02, item->window.outlineColor, textPtr, adjust); -// } - - DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle); -} - - - -//float trap_Cvar_VariableValue( const char *var_name ); -//void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); - -void Item_TextField_Paint(itemDef_t *item) { - char buff[1024]; - vec4_t newColor; - int offset; - menuDef_t *parent = (menuDef_t*)item->parent; - editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; - - Item_Text_Paint(item); - - buff[0] = '\0'; - - if (item->cvar) { - DC->getCVarString(item->cvar, buff, sizeof(buff)); - } - - parent = (menuDef_t*)item->parent; - - if (item->window.flags & WINDOW_HASFOCUS) { -/* lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; - LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); - } else { - memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); - } - - offset = (item->text && *item->text) ? 8 : 0; - if (item->window.flags & WINDOW_HASFOCUS && g_editingField) { - char cursor = DC->getOverstrikeMode() ? '_' : '|'; - DC->drawTextWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset , cursor, editPtr->maxPaintChars, item->textStyle); - } else { - DC->drawText(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, 0, editPtr->maxPaintChars, item->textStyle); - } - -} - -void Item_YesNo_Paint(itemDef_t *item) { - vec4_t newColor; - float value; - menuDef_t *parent = (menuDef_t*)item->parent; - - value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; - - if (item->window.flags & WINDOW_HASFOCUS) { -/* lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; - LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); - } else { - memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); - } - - if (item->text) { - Item_Text_Paint(item); - DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle); - } else { - DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle); - } -} - -void Item_Multi_Paint(itemDef_t *item) { - vec4_t newColor; - const char *text = ""; - menuDef_t *parent = (menuDef_t*)item->parent; - - if (item->window.flags & WINDOW_HASFOCUS) { -/* lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; - LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); - } else { - memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); - } - - text = Item_Multi_Setting(item); - - if (item->text) { - Item_Text_Paint(item); - DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle); - } else { - DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle); - } + return max * useScale; } +float UI_Text_EmWidth(float scale) { return UI_Text_Width("M", scale); } -typedef struct { - char *command; - int id; - int defaultbind1; - int defaultbind2; - int bind1; - int bind2; -} bind_t; - -typedef struct -{ - char* name; - float defaultvalue; - float value; -} configcvar_t; - - -static bind_t g_bindings[] = -{ - { "+scores", K_TAB, -1, -1, -1 }, - { "+button2", K_ENTER, -1, -1, -1 }, - { "+speed", K_SHIFT, -1, -1, -1 }, - { "boost", 'x', -1, -1, -1 }, //TA: human sprinting - { "+forward", K_UPARROW, -1, -1, -1 }, - { "+back", K_DOWNARROW, -1, -1, -1 }, - { "+moveleft", ',', -1, -1, -1 }, - { "+moveright", '.', -1, -1, -1 }, - { "+moveup", K_SPACE, -1, -1, -1 }, - { "+movedown", 'c', -1, -1, -1 }, - { "+left", K_LEFTARROW, -1, -1, -1 }, - { "+right", K_RIGHTARROW, -1, -1, -1 }, - { "+strafe", K_ALT, -1, -1, -1 }, - { "+lookup", K_PGDN, -1, -1, -1 }, - { "+lookdown", K_DEL, -1, -1, -1 }, - { "+mlook", '/', -1, -1, -1 }, - { "centerview", K_END, -1, -1, -1 }, - { "+zoom", -1, -1, -1, -1 }, - { "weapon 1", '1', -1, -1, -1 }, - { "weapon 2", '2', -1, -1, -1 }, - { "weapon 3", '3', -1, -1, -1 }, - { "weapon 4", '4', -1, -1, -1 }, - { "weapon 5", '5', -1, -1, -1 }, - { "weapon 6", '6', -1, -1, -1 }, - { "weapon 7", '7', -1, -1, -1 }, - { "weapon 8", '8', -1, -1, -1 }, - { "weapon 9", '9', -1, -1, -1 }, - { "weapon 10", '0', -1, -1, -1 }, - { "weapon 11", -1, -1, -1, -1 }, - { "weapon 12", -1, -1, -1, -1 }, - { "weapon 13", -1, -1, -1, -1 }, - { "+attack", K_MOUSE1, -1, -1, -1 }, - { "+button5", K_MOUSE2, -1, -1, -1 }, //TA: secondary attack - { "reload", 'r', -1, -1, -1 }, //TA: reload - { "buy ammo", 'b', -1, -1, -1 }, //TA: buy ammo - { "itemact medkit", 'm', -1, -1, -1 }, //TA: use medkit - { "+button7", 'q', -1, -1, -1 }, //TA: buildable use - { "deconstruct", 'e', -1, -1, -1 }, //TA: buildable destroy - { "weapprev", '[', -1, -1, -1 }, - { "weapnext", ']', -1, -1, -1 }, - { "+button3", K_MOUSE3, -1, -1, -1 }, - { "+button4", K_MOUSE4, -1, -1, -1 }, - { "vote yes", K_F1, -1, -1, -1 }, - { "vote no", K_F2, -1, -1, -1 }, - { "teamvote yes", K_F3, -1, -1, -1 }, - { "teamvote no", K_F4, -1, -1, -1 }, - { "scoresUp", K_KP_PGUP, -1, -1, -1 }, - { "scoresDown", K_KP_PGDN, -1, -1, -1 }, - // bk001205 - this one below was: '-1' - { "messagemode", -1, -1, -1, -1 }, - { "messagemode2", -1, -1, -1, -1 }, - { "messagemode3", -1, -1, -1, -1 }, - { "messagemode4", -1, -1, -1, -1 } -}; - - -static const int g_bindCount = sizeof(g_bindings) / sizeof(bind_t); +float UI_Text_EmHeight(float scale) { return UI_Text_Height("M", scale); } /* -================= -Controls_GetKeyAssignment -================= +================ +UI_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ */ -static void Controls_GetKeyAssignment (char *command, int *twokeys) +void UI_AdjustFrom640(float *x, float *y, float *w, float *h) { - int count; - int j; - char b[256]; - - twokeys[0] = twokeys[1] = -1; - count = 0; - - for ( j = 0; j < 256; j++ ) - { - DC->getBindingBuf( j, b, 256 ); - if ( *b == 0 ) { - continue; - } - if ( !Q_stricmp( b, command ) ) { - twokeys[count] = j; - count++; - if (count == 2) { - break; - } - } - } + *x *= DC->xscale; + *y *= DC->yscale; + *w *= DC->xscale; + *h *= DC->yscale; } /* -================= -Controls_GetConfig +================ +UI_SetClipRegion ================= */ -void Controls_GetConfig( void ) +void UI_SetClipRegion(float x, float y, float w, float h) { - int i; - int twokeys[ 2 ]; + vec4_t clip; - // iterate each command, get its numeric binding - for( i = 0; i < g_bindCount; i++ ) - { - Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys ); + UI_AdjustFrom640(&x, &y, &w, &h); - g_bindings[ i ].bind1 = twokeys[ 0 ]; - g_bindings[ i ].bind2 = twokeys[ 1 ]; - } + clip[0] = x; + clip[1] = y; + clip[2] = x + w; + clip[3] = y + h; - //s_controls.invertmouse.curvalue = DC->getCVarValue( "m_pitch" ) < 0; - //s_controls.smoothmouse.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "m_filter" ) ); - //s_controls.alwaysrun.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_run" ) ); - //s_controls.autoswitch.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cg_autoswitch" ) ); - //s_controls.sensitivity.curvalue = UI_ClampCvar( 2, 30, Controls_GetCvarValue( "sensitivity" ) ); - //s_controls.joyenable.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "in_joystick" ) ); - //s_controls.joythreshold.curvalue = UI_ClampCvar( 0.05, 0.75, Controls_GetCvarValue( "joy_threshold" ) ); - //s_controls.freelook.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_freelook" ) ); + trap_R_SetClipRegion(clip); } /* -================= -Controls_SetConfig +================ +UI_ClearClipRegion ================= */ -void Controls_SetConfig(qboolean restart) +void UI_ClearClipRegion(void) { trap_R_SetClipRegion(NULL); } + +static void UI_Text_PaintChar(float x, float y, float scale, glyphInfo_t *glyph, float size) { - int i; + float w, h; - // iterate each command, get its numeric binding - for (i=0; i < g_bindCount; i++) - { + w = glyph->imageWidth; + h = glyph->imageHeight; - if (g_bindings[i].bind1 != -1) + if (size > 0.0f) { - DC->setBinding( g_bindings[i].bind1, g_bindings[i].command ); - - if (g_bindings[i].bind2 != -1) - DC->setBinding( g_bindings[i].bind2, g_bindings[i].command ); + float half = size * 0.5f * scale; + x -= half; + y -= half; + w += size; + h += size; } - } - //if ( s_controls.invertmouse.curvalue ) - // DC->setCVar("m_pitch", va("%f),-fabs( DC->getCVarValue( "m_pitch" ) ) ); - //else - // trap_Cvar_SetValue( "m_pitch", fabs( trap_Cvar_VariableValue( "m_pitch" ) ) ); + w *= (DC->aspectScale * scale); + h *= scale; + y -= (glyph->top * scale); + UI_AdjustFrom640(&x, &y, &w, &h); - //trap_Cvar_SetValue( "m_filter", s_controls.smoothmouse.curvalue ); - //trap_Cvar_SetValue( "cl_run", s_controls.alwaysrun.curvalue ); - //trap_Cvar_SetValue( "cg_autoswitch", s_controls.autoswitch.curvalue ); - //trap_Cvar_SetValue( "sensitivity", s_controls.sensitivity.curvalue ); - //trap_Cvar_SetValue( "in_joystick", s_controls.joyenable.curvalue ); - //trap_Cvar_SetValue( "joy_threshold", s_controls.joythreshold.curvalue ); - //trap_Cvar_SetValue( "cl_freelook", s_controls.freelook.curvalue ); - DC->executeText(EXEC_APPEND, "in_restart\n"); - //trap_Cmd_ExecuteText( EXEC_APPEND, "in_restart\n" ); + DC->drawStretchPic(x, y, w, h, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph); } -/* -================= -Controls_SetDefaults -================= -*/ -void Controls_SetDefaults( void ) +static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust, const char *text, vec4_t color, + int style, int limit, float *maxX, int cursorPos, char cursor) { - int i; - - // iterate each command, set its default binding - for (i=0; i < g_bindCount; i++) - { - g_bindings[i].bind1 = g_bindings[i].defaultbind1; - g_bindings[i].bind2 = g_bindings[i].defaultbind2; - } - - //s_controls.invertmouse.curvalue = Controls_GetCvarDefault( "m_pitch" ) < 0; - //s_controls.smoothmouse.curvalue = Controls_GetCvarDefault( "m_filter" ); - //s_controls.alwaysrun.curvalue = Controls_GetCvarDefault( "cl_run" ); - //s_controls.autoswitch.curvalue = Controls_GetCvarDefault( "cg_autoswitch" ); - //s_controls.sensitivity.curvalue = Controls_GetCvarDefault( "sensitivity" ); - //s_controls.joyenable.curvalue = Controls_GetCvarDefault( "in_joystick" ); - //s_controls.joythreshold.curvalue = Controls_GetCvarDefault( "joy_threshold" ); - //s_controls.freelook.curvalue = Controls_GetCvarDefault( "cl_freelook" ); -} - -int BindingIDFromName(const char *name) { - int i; - for (i=0; i < g_bindCount; i++) - { - if (Q_stricmp(name, g_bindings[i].command) == 0) { - return i; - } - } - return -1; -} - -char g_nameBind1[32]; -char g_nameBind2[32]; + const char *s = text; + int len; + int count = 0; + vec4_t newColor; + fontInfo_t *font = UI_FontForScale(scale); + glyphInfo_t *glyph; + float useScale; + qhandle_t emoticonHandle = 0; + float emoticonH, emoticonW; + qboolean emoticonEscaped; + int emoticonLen = 0; + int emoticonWidth; + int cursorX = -1; + + if (!text) + return; -void BindingFromName(const char *cvar) { - int i, b1, b2; - - // iterate each command, set its default binding - for (i=0; i < g_bindCount; i++) - { - if (Q_stricmp(cvar, g_bindings[i].command) == 0) { - b1 = g_bindings[i].bind1; - if (b1 == -1) { - break; - } - DC->keynumToStringBuf( b1, g_nameBind1, 32 ); - Q_strupr(g_nameBind1); - - b2 = g_bindings[i].bind2; - if (b2 != -1) - { - DC->keynumToStringBuf( b2, g_nameBind2, 32 ); - Q_strupr(g_nameBind2); - strcat( g_nameBind1, " or " ); - strcat( g_nameBind1, g_nameBind2 ); - } - return; - } - } - strcpy(g_nameBind1, "???"); -} - -void Item_Slider_Paint(itemDef_t *item) { - vec4_t newColor; - float x, y, value; - menuDef_t *parent = (menuDef_t*)item->parent; - - value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; - - if (item->window.flags & WINDOW_HASFOCUS) { -/* lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; - LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); - } else { - memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); - } - - y = item->window.rect.y; - if (item->text) { - Item_Text_Paint(item); - x = item->textRect.x + item->textRect.w + 8; - } else { - x = item->window.rect.x; - } - DC->setColor(newColor); - DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar ); - - x = Item_Slider_ThumbPosition(item); - DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb ); - -} - -void Item_Bind_Paint(itemDef_t *item) { - vec4_t newColor, lowLight; - float value; - int maxChars = 0; - menuDef_t *parent = (menuDef_t*)item->parent; - editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; - if (editPtr) { - maxChars = editPtr->maxPaintChars; - } - - value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; - - if (item->window.flags & WINDOW_HASFOCUS) { - if (g_bindItem == item) { - lowLight[0] = 0.8f * 1.0f; - lowLight[1] = 0.8f * 0.0f; - lowLight[2] = 0.8f * 0.0f; - lowLight[3] = 0.8f * 1.0f; - } else { - lowLight[0] = 0.8f * parent->focusColor[0]; - lowLight[1] = 0.8f * parent->focusColor[1]; - lowLight[2] = 0.8f * parent->focusColor[2]; - lowLight[3] = 0.8f * parent->focusColor[3]; - } - /*LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); - } else { - memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); - } - - if (item->text) { - Item_Text_Paint(item); - BindingFromName(item->cvar); - DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle); - } else { - DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle); - } -} + useScale = scale * font->glyphScale; -qboolean Display_KeyBindPending( void ) { - return g_waitingForKey; -} + emoticonH = UI_EmoticonHeight(font, scale); + emoticonW = UI_EmoticonWidth(font, scale); -qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) { - int id; - int i; + len = strlen(text); + if (limit > 0 && len > limit) + len = limit; - if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey) - { - if (down && (key == K_MOUSE1 || key == K_ENTER)) { - g_waitingForKey = qtrue; - g_bindItem = item; - } - return qtrue; - } - else - { - if (!g_waitingForKey || g_bindItem == NULL) { - return qtrue; - } + DC->setColor(color); + memcpy(&newColor[0], &color[0], sizeof(vec4_t)); - if (key & K_CHAR_FLAG) { - return qtrue; - } + x += UI_Parse_Indent(&s); - switch (key) + while (s && *s && count < len) { - case K_ESCAPE: - g_waitingForKey = qfalse; - return qtrue; + const char *t = s; + float charWidth = UI_Char_Width(&t, scale); + glyph = &font->glyphs[(int)*s]; - case K_BACKSPACE: - id = BindingIDFromName(item->cvar); - if (id != -1) { - g_bindings[id].bind1 = -1; - g_bindings[id].bind2 = -1; + if (maxX && charWidth + x > *maxX) + { + *maxX = 0; + break; } - Controls_SetConfig(qtrue); - g_waitingForKey = qfalse; - g_bindItem = NULL; - return qtrue; - case '`': - return qtrue; - } - } - - if (key != -1) - { - - for (i=0; i < g_bindCount; i++) - { - - if (g_bindings[i].bind2 == key) { - g_bindings[i].bind2 = -1; - } + if (cursorPos < 0) + { + if (Q_IsColorString(s)) + { + memcpy(newColor, g_color_table[ColorIndex(*(s + 1))], sizeof(newColor)); + newColor[3] = color[3]; + DC->setColor(newColor); + s += 2; + continue; + } - if (g_bindings[i].bind1 == key) - { - g_bindings[i].bind1 = g_bindings[i].bind2; - g_bindings[i].bind2 = -1; - } - } - } + if (*s == INDENT_MARKER) + { + s++; + continue; + } + if (UI_Text_IsEmoticon(s, &emoticonEscaped, &emoticonLen, &emoticonHandle, &emoticonWidth)) + { + if (emoticonEscaped) + s++; + else + { + float yadj = useScale * glyph->top; + + DC->setColor(NULL); + DC->drawHandlePic(x, y - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle); + DC->setColor(newColor); + x += (emoticonW * emoticonWidth) + gapAdjust; + s += emoticonLen; + count += emoticonWidth; + continue; + } + } + } - id = BindingIDFromName(item->cvar); + if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) + { + int ofs; + + if (style == ITEM_TEXTSTYLE_SHADOWED) + ofs = 1; + else + ofs = 2; + colorBlack[3] = newColor[3]; + DC->setColor(colorBlack); + UI_Text_PaintChar(x + ofs, y + ofs, useScale, glyph, 0.0f); + DC->setColor(newColor); + colorBlack[3] = 1.0f; + } + else if (style == ITEM_TEXTSTYLE_NEON) + { + vec4_t glow; - if (id != -1) { - if (key == -1) { - if( g_bindings[id].bind1 != -1 ) { - DC->setBinding( g_bindings[id].bind1, "" ); - g_bindings[id].bind1 = -1; - } - if( g_bindings[id].bind2 != -1 ) { - DC->setBinding( g_bindings[id].bind2, "" ); - g_bindings[id].bind2 = -1; - } - } - else if (g_bindings[id].bind1 == -1) { - g_bindings[id].bind1 = key; - } - else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1) { - g_bindings[id].bind2 = key; - } - else { - DC->setBinding( g_bindings[id].bind1, "" ); - DC->setBinding( g_bindings[id].bind2, "" ); - g_bindings[id].bind1 = key; - g_bindings[id].bind2 = -1; - } - } + memcpy(&glow[0], &newColor[0], sizeof(vec4_t)); + glow[3] *= 0.2f; - Controls_SetConfig(qtrue); - g_waitingForKey = qfalse; + DC->setColor(glow); + UI_Text_PaintChar(x, y, useScale, glyph, 6.0f); + UI_Text_PaintChar(x, y, useScale, glyph, 4.0f); + DC->setColor(newColor); + UI_Text_PaintChar(x, y, useScale, glyph, 2.0f); - return qtrue; -} + DC->setColor(colorWhite); + } + UI_Text_PaintChar(x, y, useScale, glyph, 0.0f); + if (count == cursorPos) + cursorX = x; -void AdjustFrom640(float *x, float *y, float *w, float *h) { - //*x = *x * DC->scale + DC->bias; - *x *= DC->xscale; - *y *= DC->yscale; - *w *= DC->xscale; - *h *= DC->yscale; -} + x += (glyph->xSkip * DC->aspectScale * useScale) + gapAdjust; + s++; + count++; + } -void Item_Model_Paint(itemDef_t *item) { - float x, y, w, h; - refdef_t refdef; - refEntity_t ent; - vec3_t mins, maxs, origin; - vec3_t angles; - modelDef_t *modelPtr = (modelDef_t*)item->typeData; + if (maxX) + *maxX = x; - if (modelPtr == NULL) { - return; - } + // paint cursor + if (cursorPos >= 0) + { + if (cursorPos == len) + cursorX = x; - // setup the refdef - memset( &refdef, 0, sizeof( refdef ) ); - refdef.rdflags = RDF_NOWORLDMODEL; - AxisClear( refdef.viewaxis ); - x = item->window.rect.x+1; - y = item->window.rect.y+1; - w = item->window.rect.w-2; - h = item->window.rect.h-2; + if (cursorX >= 0 && !((DC->realTime / BLINK_DIVISOR) & 1)) + { + glyph = &font->glyphs[(int)cursor]; + UI_Text_PaintChar(cursorX, y, useScale, glyph, 0.0f); + } + } - AdjustFrom640( &x, &y, &w, &h ); + DC->setColor(NULL); +} - refdef.x = x; - refdef.y = y; - refdef.width = w; - refdef.height = h; +void UI_Text_Paint_Limit( + float *maxX, float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit) +{ + UI_Text_Paint_Generic(x, y, scale, adjust, text, color, ITEM_TEXTSTYLE_NORMAL, limit, maxX, -1, 0); +} - DC->modelBounds( item->asset, mins, maxs ); +void UI_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) +{ + UI_Text_Paint_Generic(x, y, scale, adjust, text, color, style, limit, NULL, -1, 0); +} - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); +void UI_Text_PaintWithCursor( + float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) +{ + UI_Text_Paint_Generic(x, y, scale, 0.0, text, color, style, limit, NULL, cursorPos, cursor); +} + +commandDef_t commandList[] = { + {"close", &Script_Close}, // menu + {"conditionalopen", &Script_ConditionalOpen}, // menu + {"exec", &Script_Exec}, // group/name + {"fadein", &Script_FadeIn}, // group/name + {"fadeout", &Script_FadeOut}, // group/name + {"hide", &Script_Hide}, // group/name + {"open", &Script_Open}, // menu + {"orbit", &Script_Orbit}, // group/name + {"play", &Script_Play}, // group/name + {"playlooped", &Script_playLooped}, // group/name + {"reset", &Script_Reset}, // resets the state of the item argument + {"setasset", &Script_SetAsset}, // works on this + {"setbackground", &Script_SetBackground}, // works on this + {"setcolor", &Script_SetColor}, // works on this + {"setcvar", &Script_SetCvar}, // group/name + {"setfocus", &Script_SetFocus}, // sets this background color to team color + {"setitemcolor", &Script_SetItemColor}, // group/name + {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color + {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color + {"show", &Script_Show}, // group/name + {"transition", &Script_Transition}, // group/name +}; - // calculate distance so the model nearly fills the box - if (qtrue) { - float len = 0.5 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - //origin[0] = len / tan(w/2); - } else { - origin[0] = item->textscale; - } - refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : w; - refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : h; +static size_t scriptCommandCount = ARRAY_LEN(commandList); - //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); - //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); - //refdef.fov_y = atan2( refdef.height, xx ); - //refdef.fov_y *= ( 360 / M_PI ); +// despite what lcc thinks, we do not get cmdcmp here +static int commandComp(const void *a, const void *b) { return Q_stricmp((const char *)a, ((commandDef_t *)b)->name); } - DC->clearScene(); +void Item_RunScript(itemDef_t *item, const char *s) +{ + char script[1024], *p; + commandDef_t *cmd; + memset(script, 0, sizeof(script)); - refdef.time = DC->realTime; + if (item && s && s[0]) + { + Q_strcat(script, 1024, s); + p = script; - // add the model + while (1) + { + const char *command; + // expect command then arguments, ; ends command, NULL ends script - memset( &ent, 0, sizeof(ent) ); + if (!String_Parse(&p, &command)) + return; - //adjust = 5.0 * sin( (float)uis.realtime / 500 ); - //adjust = 360 % (int)((float)uis.realtime / 1000); - //VectorSet( angles, 0, 0, 1 ); + if (command[0] == ';' && command[1] == '\0') + continue; - // use item storage to track - if (modelPtr->rotationSpeed) { - if (DC->realTime > item->window.nextTime) { - item->window.nextTime = DC->realTime + modelPtr->rotationSpeed; - modelPtr->angle = (int)(modelPtr->angle + 1) % 360; + cmd = bsearch(command, commandList, scriptCommandCount, sizeof(commandDef_t), commandComp); + if (cmd) + cmd->handler(item, &p); + else + // not in our auto list, pass to handler + DC->runScript(&p); + } } - } - VectorSet( angles, 0, modelPtr->angle, 0 ); - AnglesToAxis( angles, ent.axis ); +} - ent.hModel = item->asset; - VectorCopy( origin, ent.origin ); - VectorCopy( origin, ent.lightingOrigin ); - ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; - VectorCopy( ent.origin, ent.oldorigin ); +qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) +{ + char script[1024], *p; + memset(script, 0, sizeof(script)); - DC->addRefEntityToScene( &ent ); - DC->renderScene( &refdef ); + if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) + { + char buff[1024]; + DC->getCVarString(item->cvarTest, buff, sizeof(buff)); -} + Q_strcat(script, 1024, item->enableCvar); + p = script; + while (1) + { + const char *val; + // expect value then ; or NULL, NULL ends list -void Item_Image_Paint(itemDef_t *item) { - if (item == NULL) { - return; - } - DC->drawHandlePic(item->window.rect.x+1, item->window.rect.y+1, item->window.rect.w-2, item->window.rect.h-2, item->asset); -} + if (!String_Parse(&p, &val)) + return (item->cvarFlags & flag) ? qfalse : qtrue; -void Item_ListBox_Paint(itemDef_t *item) { - float x, y, size, thumb; - int i, count; - qhandle_t image; - qhandle_t optionalImage; - listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - - // the listbox is horizontal or vertical and has a fixed size scroll bar going either direction - // elements are enumerated from the DC and either text or image handles are acquired from the DC as well - // textscale is used to size the text, textalignx and textaligny are used to size image elements - // there is no clipping available so only the last completely visible item is painted - count = DC->feederCount(item->special); - // default is vertical if horizontal flag is not here - if (item->window.flags & WINDOW_HORIZONTAL) { - // draw scrollbar in bottom of the window - // bar - x = item->window.rect.x + 1; - y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE - 1; - DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowLeft); - x += SCROLLBAR_SIZE - 1; - size = item->window.rect.w - (SCROLLBAR_SIZE * 2); - DC->drawHandlePic(x, y, size+1, SCROLLBAR_SIZE, DC->Assets.scrollBar); - x += size - 1; - DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowRight); - // thumb - thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item); - if (thumb > x - SCROLLBAR_SIZE - 1) { - thumb = x - SCROLLBAR_SIZE - 1; - } - DC->drawHandlePic(thumb, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); - // - listPtr->endPos = listPtr->startPos; - size = item->window.rect.w - 2; - // items - // size contains max available space - if (listPtr->elementStyle == LISTBOX_IMAGE) { - // fit = 0; - x = item->window.rect.x + 1; - y = item->window.rect.y + 1; - for (i = listPtr->startPos; i < count; i++) { - // always draw at least one - // which may overdraw the box if it is too small for the element - image = DC->feederItemImage(item->special, i); - if (image) { - DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); - } - - if (i == item->cursorPos) { - DC->drawRect(x, y, listPtr->elementWidth-1, listPtr->elementHeight-1, item->window.borderSize, item->window.borderColor); - } - - listPtr->endPos++; - size -= listPtr->elementWidth; - if (size < listPtr->elementWidth) { - listPtr->drawPadding = size; //listPtr->elementWidth - size; - break; - } - x += listPtr->elementWidth; - // fit++; - } - } else { - // - } - } else { - // draw scrollbar to right side of the window - x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE - 1; - y = item->window.rect.y + 1; - DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowUp); - y += SCROLLBAR_SIZE - 1; - - listPtr->endPos = listPtr->startPos; - size = item->window.rect.h - (SCROLLBAR_SIZE * 2); - DC->drawHandlePic(x, y, SCROLLBAR_SIZE, size+1, DC->Assets.scrollBar); - y += size - 1; - DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowDown); - // thumb - thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item); - if (thumb > y - SCROLLBAR_SIZE - 1) { - thumb = y - SCROLLBAR_SIZE - 1; - } - DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); - - // adjust size for item painting - size = item->window.rect.h - 2; - if (listPtr->elementStyle == LISTBOX_IMAGE) { - // fit = 0; - x = item->window.rect.x + 1; - y = item->window.rect.y + 1; - for (i = listPtr->startPos; i < count; i++) { - // always draw at least one - // which may overdraw the box if it is too small for the element - image = DC->feederItemImage(item->special, i); - if (image) { - DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); - } - - if (i == item->cursorPos) { - DC->drawRect(x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor); - } - - listPtr->endPos++; - size -= listPtr->elementWidth; - if (size < listPtr->elementHeight) { - listPtr->drawPadding = listPtr->elementHeight - size; - break; - } - y += listPtr->elementHeight; - // fit++; - } - } else { - x = item->window.rect.x + 1; - y = item->window.rect.y + 1; - for (i = listPtr->startPos; i < count; i++) { - const char *text; - // always draw at least one - // which may overdraw the box if it is too small for the element - - if (listPtr->numColumns > 0) { - int j; - for (j = 0; j < listPtr->numColumns; j++) { - text = DC->feederItemText(item->special, i, j, &optionalImage); - if (optionalImage >= 0) { - DC->drawHandlePic(x + 4 + listPtr->columnInfo[j].pos, y - 1 + listPtr->elementHeight / 2, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); - } else if (text) { - //TA: - int alignOffset = 0.0f, tw; - - tw = DC->textWidth( text, item->textscale, 0 ); - - switch( listPtr->columnInfo[ j ].align ) - { - case ITEM_ALIGN_LEFT: - alignOffset = 0.0f; - break; - - case ITEM_ALIGN_RIGHT: - alignOffset = listPtr->columnInfo[ j ].width - tw; - break; - - case ITEM_ALIGN_CENTER: - alignOffset = ( listPtr->columnInfo[ j ].width / 2.0f ) - ( tw / 2.0f ); - break; + if (val[0] == ';' && val[1] == '\0') + continue; - default: - alignOffset = 0.0f; - } + // enable it if any of the values are true + if (item->cvarFlags & flag) + { + if (Q_stricmp(buff, val) == 0) + return qtrue; + } + else + { + // disable it if any of the values are true - DC->drawText( x + 4 + listPtr->columnInfo[j].pos + alignOffset, y + listPtr->elementHeight, - item->textscale, item->window.foreColor, text, 0, - listPtr->columnInfo[j].maxChars, item->textStyle ); + if (Q_stricmp(buff, val) == 0) + return qfalse; } - } - } else { - text = DC->feederItemText(item->special, i, 0, &optionalImage); - if (optionalImage >= 0) { - //DC->drawHandlePic(x + 4 + listPtr->elementHeight, y, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); - } else if (text) { - DC->drawText(x + 4, y + listPtr->elementHeight, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle); - } } - if (i == item->cursorPos) { - DC->fillRect(x + 2, y + 2, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight, item->window.outlineColor); - } - - listPtr->endPos++; - size -= listPtr->elementHeight; - if (size < listPtr->elementHeight) { - listPtr->drawPadding = listPtr->elementHeight - size; - break; - } - y += listPtr->elementHeight; - // fit++; - } + return (item->cvarFlags & flag) ? qfalse : qtrue; } - } - //TA: FIXME: hacky fix to off-by-one bug - listPtr->endPos--; -} - - -void Item_OwnerDraw_Paint(itemDef_t *item) { - menuDef_t *parent; - - if (item == NULL) { - return; - } - parent = (menuDef_t*)item->parent; - - if (DC->ownerDrawItem) { - vec4_t color, lowLight; - menuDef_t *parent = (menuDef_t*)item->parent; - Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); - memcpy(&color, &item->window.foreColor, sizeof(color)); - if (item->numColors > 0 && DC->getValue) { - // if the value is within one of the ranges then set color to that, otherwise leave at default - int i; - float f = DC->getValue(item->window.ownerDraw); - for (i = 0; i < item->numColors; i++) { - if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high) { - memcpy(&color, &item->colorRanges[i].color, sizeof(color)); - break; - } - } - } - - if (item->window.flags & WINDOW_HASFOCUS) { -/* lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; - LerpColor(parent->focusColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/ - //TA: - memcpy(color, &parent->focusColor, sizeof(vec4_t)); - } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) { - lowLight[0] = 0.8 * item->window.foreColor[0]; - lowLight[1] = 0.8 * item->window.foreColor[1]; - lowLight[2] = 0.8 * item->window.foreColor[2]; - lowLight[3] = 0.8 * item->window.foreColor[3]; - LerpColor(item->window.foreColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); - } - - if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { - memcpy(color, parent->disableColor, sizeof(vec4_t)); // bk001207 - FIXME: Com_Memcpy - } - - if (item->text) { - Item_Text_Paint(item); - if (item->text[0]) { - // +8 is an offset kludge to properly align owner draw items that have text combined with them - DC->ownerDrawItem(item->textRect.x + item->textRect.w + 8, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); - } else { - DC->ownerDrawItem(item->textRect.x + item->textRect.w, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); - } - } else { - DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); - } - } -} - - -void Item_Paint(itemDef_t *item) { - vec4_t red; - menuDef_t *parent = (menuDef_t*)item->parent; - red[0] = red[3] = 1; - red[1] = red[2] = 0; - - if (item == NULL) { - return; - } - - if (item->window.flags & WINDOW_ORBITING) { - if (DC->realTime > item->window.nextTime) { - float rx, ry, a, c, s, w, h; - - item->window.nextTime = DC->realTime + item->window.offsetTime; - // translate - w = item->window.rectClient.w / 2; - h = item->window.rectClient.h / 2; - rx = item->window.rectClient.x + w - item->window.rectEffects.x; - ry = item->window.rectClient.y + h - item->window.rectEffects.y; - a = 3 * M_PI / 180; - c = cos(a); - s = sin(a); - item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w; - item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h; - Item_UpdatePosition(item); - - } - } - - - if (item->window.flags & WINDOW_INTRANSITION) { - if (DC->realTime > item->window.nextTime) { - int done = 0; - item->window.nextTime = DC->realTime + item->window.offsetTime; - // transition the x,y - if (item->window.rectClient.x == item->window.rectEffects.x) { - done++; - } else { - if (item->window.rectClient.x < item->window.rectEffects.x) { - item->window.rectClient.x += item->window.rectEffects2.x; - if (item->window.rectClient.x > item->window.rectEffects.x) { - item->window.rectClient.x = item->window.rectEffects.x; - done++; - } - } else { - item->window.rectClient.x -= item->window.rectEffects2.x; - if (item->window.rectClient.x < item->window.rectEffects.x) { - item->window.rectClient.x = item->window.rectEffects.x; - done++; - } - } - } - if (item->window.rectClient.y == item->window.rectEffects.y) { - done++; - } else { - if (item->window.rectClient.y < item->window.rectEffects.y) { - item->window.rectClient.y += item->window.rectEffects2.y; - if (item->window.rectClient.y > item->window.rectEffects.y) { - item->window.rectClient.y = item->window.rectEffects.y; - done++; - } - } else { - item->window.rectClient.y -= item->window.rectEffects2.y; - if (item->window.rectClient.y < item->window.rectEffects.y) { - item->window.rectClient.y = item->window.rectEffects.y; - done++; - } - } - } - if (item->window.rectClient.w == item->window.rectEffects.w) { - done++; - } else { - if (item->window.rectClient.w < item->window.rectEffects.w) { - item->window.rectClient.w += item->window.rectEffects2.w; - if (item->window.rectClient.w > item->window.rectEffects.w) { - item->window.rectClient.w = item->window.rectEffects.w; - done++; - } - } else { - item->window.rectClient.w -= item->window.rectEffects2.w; - if (item->window.rectClient.w < item->window.rectEffects.w) { - item->window.rectClient.w = item->window.rectEffects.w; - done++; - } - } - } - if (item->window.rectClient.h == item->window.rectEffects.h) { - done++; - } else { - if (item->window.rectClient.h < item->window.rectEffects.h) { - item->window.rectClient.h += item->window.rectEffects2.h; - if (item->window.rectClient.h > item->window.rectEffects.h) { - item->window.rectClient.h = item->window.rectEffects.h; - done++; - } - } else { - item->window.rectClient.h -= item->window.rectEffects2.h; - if (item->window.rectClient.h < item->window.rectEffects.h) { - item->window.rectClient.h = item->window.rectEffects.h; - done++; - } - } - } - - Item_UpdatePosition(item); - - if (done == 4) { - item->window.flags &= ~WINDOW_INTRANSITION; - } - - } - } - - if (item->window.ownerDrawFlags && DC->ownerDrawVisible) { - if (!DC->ownerDrawVisible(item->window.ownerDrawFlags)) { - item->window.flags &= ~WINDOW_VISIBLE; - } else { - item->window.flags |= WINDOW_VISIBLE; - } - } - - if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE)) { - if (!Item_EnableShowViaCvar(item, CVAR_SHOW)) { - return; - } - } - - if (item->window.flags & WINDOW_TIMEDVISIBLE) { - - } - - if (!(item->window.flags & WINDOW_VISIBLE)) { - return; - } - - // paint the rect first.. - Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle); - - if (debugMode) { - vec4_t color; - rectDef_t *r = Item_CorrectedTextRect(item); - color[1] = color[3] = 1; - color[0] = color[2] = 0; - DC->drawRect(r->x, r->y, r->w, r->h, 1, color); - } - - //DC->drawRect(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, 1, red); - - switch (item->type) { - case ITEM_TYPE_OWNERDRAW: - Item_OwnerDraw_Paint(item); - break; - case ITEM_TYPE_TEXT: - case ITEM_TYPE_BUTTON: - Item_Text_Paint(item); - break; - case ITEM_TYPE_RADIOBUTTON: - break; - case ITEM_TYPE_CHECKBOX: - break; - case ITEM_TYPE_EDITFIELD: - case ITEM_TYPE_SAYFIELD: - case ITEM_TYPE_NUMERICFIELD: - Item_TextField_Paint(item); - break; - case ITEM_TYPE_COMBO: - break; - case ITEM_TYPE_LISTBOX: - Item_ListBox_Paint(item); - break; - //case ITEM_TYPE_IMAGE: - // Item_Image_Paint(item); - // break; - case ITEM_TYPE_MODEL: - Item_Model_Paint(item); - break; - case ITEM_TYPE_YESNO: - Item_YesNo_Paint(item); - break; - case ITEM_TYPE_MULTI: - Item_Multi_Paint(item); - break; - case ITEM_TYPE_BIND: - Item_Bind_Paint(item); - break; - case ITEM_TYPE_SLIDER: - Item_Slider_Paint(item); - break; - default: - break; - } - -} - -void Menu_Init(menuDef_t *menu) { - memset(menu, 0, sizeof(menuDef_t)); - menu->cursorItem = -1; - menu->fadeAmount = DC->Assets.fadeAmount; - menu->fadeClamp = DC->Assets.fadeClamp; - menu->fadeCycle = DC->Assets.fadeCycle; - Window_Init(&menu->window); -} - -itemDef_t *Menu_GetFocusedItem(menuDef_t *menu) { - int i; - if (menu) { - for (i = 0; i < menu->itemCount; i++) { - if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { - return menu->items[i]; - } - } - } - return NULL; -} - -menuDef_t *Menu_GetFocused( void ) { - int i; - for (i = 0; i < menuCount; i++) { - if (Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE) { - return &Menus[i]; - } - } - return NULL; -} - -void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down) { - if (menu) { + return qtrue; +} + +// will optionaly set focus to this item +qboolean Item_SetFocus(itemDef_t *item, float x, float y) +{ int i; - for (i = 0; i < menu->itemCount; i++) { - if (menu->items[i]->special == feeder) { - Item_ListBox_HandleKey(menu->items[i], (down) ? K_DOWNARROW : K_UPARROW, qtrue, qtrue); - return; - } + itemDef_t *oldFocus; + sfxHandle_t *sfx = &DC->Assets.itemFocusSound; + qboolean playSound = qfalse; + menuDef_t *parent; + // sanity check, non-null, not a decoration and does not already have the focus + + if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || + !(item->window.flags & WINDOW_VISIBLE)) + { + return qfalse; } - } -} + parent = (menuDef_t *)item->parent; + // items can be enabled and disabled based on cvars -void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name) { - if (menu == NULL) { - if (name == NULL) { - menu = Menu_GetFocused(); - } else { - menu = Menus_FindByName(name); - } - } + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + return qfalse; - if (menu) { - int i; - for (i = 0; i < menu->itemCount; i++) { - if (menu->items[i]->special == feeder) { - if (index == 0) { - listBoxDef_t *listPtr = (listBoxDef_t*)menu->items[i]->typeData; - listPtr->cursorPos = 0; - listPtr->startPos = 0; - } - menu->items[i]->cursorPos = index; - DC->feederSelection(menu->items[i]->special, menu->items[i]->cursorPos); - return; - } - } - } -} + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) + return qfalse; -qboolean Menus_AnyFullScreenVisible( void ) { - int i; - for (i = 0; i < menuCount; i++) { - if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) { - return qtrue; - } - } - return qfalse; -} + oldFocus = Menu_ClearFocus(item->parent); -menuDef_t *Menus_ActivateByName(const char *p) { - int i, j; - menuDef_t *m = NULL; - menuDef_t *focus = Menu_GetFocused(); + if (item->type == ITEM_TYPE_TEXT) + { + rectDef_t r; + r = item->textRect; + r.y -= r.h; + + if (Rect_ContainsPoint(&r, x, y)) + { + item->window.flags |= WINDOW_HASFOCUS; - for (i = 0; i < menuCount; i++) { - if (Q_stricmp(Menus[i].window.name, p) == 0) { - m = &Menus[i]; - Menus_Activate(m); - Menu_HandleMouseMove( m, DC->cursorx, DC->cursory ); //TA: force the item under the cursor to focus + if (item->focusSound) + sfx = &item->focusSound; - for( j = 0; j < m->itemCount; j++ ) //TA: reset selection in listboxes when opened - { - if( m->items[ j ]->type == ITEM_TYPE_LISTBOX ) + playSound = qtrue; + } + else { - listBoxDef_t *listPtr = (listBoxDef_t*)m->items[ j ]->typeData; - m->items[ j ]->cursorPos = 0; - listPtr->startPos = 0; - DC->feederSelection( m->items[ j ]->special, 0 ); + if (oldFocus) + { + oldFocus->window.flags |= WINDOW_HASFOCUS; + + if (oldFocus->onFocus) + Item_RunScript(oldFocus, oldFocus->onFocus); + } } - } + } + else + { + item->window.flags |= WINDOW_HASFOCUS; + + if (item->onFocus) + Item_RunScript(item, item->onFocus); + + if (item->focusSound) + sfx = &item->focusSound; - if (openMenuCount < MAX_OPEN_MENUS && focus != NULL) { - menuStack[openMenuCount++] = focus; - } - } else { - Menus[i].window.flags &= ~WINDOW_HASFOCUS; + playSound = qtrue; } - } - Display_CloseCinematics(); - return m; -} + if (playSound && sfx) + DC->startLocalSound(*sfx, CHAN_LOCAL_SOUND); -void Item_Init(itemDef_t *item) { - memset(item, 0, sizeof(itemDef_t)); - item->textscale = 0.55f; - Window_Init(&item->window); -} + for (i = 0; i < parent->itemCount; i++) + { + if (parent->items[i] == item) + { + parent->cursorItem = i; + break; + } + } -void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) { - int i, pass; - qboolean focusSet = qfalse; + return qtrue; +} - itemDef_t *overItem; - if (menu == NULL) { - return; - } +static float Item_ListBox_HeightForNumItems(itemDef_t *item, int numItems) +{ + listBoxDef_t *listPtr = item->typeData.list; - if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { - return; - } + return (listPtr->elementHeight * numItems) + 2.0f; +} - if (itemCapture) { - //Item_MouseMove(itemCapture, x, y); - return; - } +static int Item_ListBox_NumItemsForItemHeight(itemDef_t *item) +{ + listBoxDef_t *listPtr = item->typeData.list; - if (g_waitingForKey || g_editingField) { - return; - } + if (item->type == ITEM_TYPE_COMBOBOX) + return listPtr->dropItems; + else + return ((item->window.rect.h - 2.0f) / listPtr->elementHeight); +} - // FIXME: this is the whole issue of focus vs. mouse over.. - // need a better overall solution as i don't like going through everything twice - for (pass = 0; pass < 2; pass++) { - for (i = 0; i < menu->itemCount; i++) { - // turn off focus each item - // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; +int Item_ListBox_MaxScroll(itemDef_t *item) +{ + int total = DC->feederCount(item->feederID); + int max = total - Item_ListBox_NumItemsForItemHeight(item); - if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { - continue; - } + if (max < 0) + return 0; - // items can be enabled and disabled based on cvars - if (menu->items[i]->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_ENABLE)) { - continue; - } + return max; +} - if (menu->items[i]->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_SHOW)) { - continue; - } +static float oldComboBoxY; +static float oldComboBoxH; +static qboolean Item_ComboBox_MaybeCastToListBox(itemDef_t *item) +{ + listBoxDef_t *listPtr = item->typeData.list; + qboolean cast = g_comboBoxItem != NULL && (item->type == ITEM_TYPE_COMBOBOX); + if (cast) + { + oldComboBoxY = item->window.rect.y; + oldComboBoxH = item->window.rect.h; - if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { - if (pass == 1) { - overItem = menu->items[i]; - if (overItem->type == ITEM_TYPE_TEXT && overItem->text) { - if (!Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) { - continue; - } - } - // if we are over an item - if (IsVisible(overItem->window.flags)) { - // different one - Item_MouseEnter(overItem, x, y); - // Item_SetMouseOver(overItem, qtrue); - - // if item is not a decoration see if it can take focus - if (!focusSet) { - focusSet = Item_SetFocus(overItem, x, y); - } - } - } - } else if (menu->items[i]->window.flags & WINDOW_MOUSEOVER) { - Item_MouseLeave(menu->items[i]); - Item_SetMouseOver(menu->items[i], qfalse); - } + item->window.rect.y += item->window.rect.h; + item->window.rect.h = Item_ListBox_HeightForNumItems(item, listPtr->dropItems); + item->type = ITEM_TYPE_LISTBOX; } - } + return cast; } -void Menu_Paint(menuDef_t *menu, qboolean forcePaint) { - int i; +static void Item_ComboBox_MaybeUnCastFromListBox(itemDef_t *item, qboolean unCast) +{ + if (unCast) + { + item->window.rect.y = oldComboBoxY; + item->window.rect.h = oldComboBoxH; + item->type = ITEM_TYPE_COMBOBOX; + } +} - if (menu == NULL) { - return; - } +static void Item_ListBox_SetStartPos(itemDef_t *item, int startPos) +{ + listBoxDef_t *listPtr = item->typeData.list; + int total = DC->feederCount(item->feederID); + int max = Item_ListBox_MaxScroll(item); - if (!(menu->window.flags & WINDOW_VISIBLE) && !forcePaint) { - return; - } + if (startPos < 0) + listPtr->startPos = 0; + else if (startPos > max) + listPtr->startPos = max; + else + listPtr->startPos = startPos; - if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) { - return; - } + listPtr->endPos = listPtr->startPos + MIN((total - listPtr->startPos), Item_ListBox_NumItemsForItemHeight(item)); +} - if (forcePaint) { - menu->window.flags |= WINDOW_FORCED; - } +float Item_ListBox_ThumbPosition(itemDef_t *item) +{ + float max, pos, size; + float startPos = (float)item->typeData.list->startPos; - // draw the background if necessary - if (menu->fullScreen) { - // implies a background shader - // FIXME: make sure we have a default shader if fullscreen is set with no background - DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background ); - } else if (menu->window.background) { - // this allows a background shader without being full screen - //UI_DrawHandlePic(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, menu->backgroundShader); - } + max = Item_ListBox_MaxScroll(item); + size = SCROLLBAR_SLIDER_HEIGHT(item); - // paint the background and or border - Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle ); + if (max > 0.0f) + pos = (size - SCROLLBAR_ARROW_HEIGHT) / max; + else + pos = 0.0f; - for (i = 0; i < menu->itemCount; i++) { - Item_Paint(menu->items[i]); - } + pos *= startPos; - if (debugMode) { - vec4_t color; - color[0] = color[2] = color[3] = 1; - color[1] = 0; - DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color); - } + return SCROLLBAR_SLIDER_Y(item) + pos; } -/* -=============== -Item_ValidateTypeData -=============== -*/ -void Item_ValidateTypeData(itemDef_t *item) { - if (item->typeData) { - return; - } - - if (item->type == ITEM_TYPE_LISTBOX) { - item->typeData = UI_Alloc(sizeof(listBoxDef_t)); - memset(item->typeData, 0, sizeof(listBoxDef_t)); - } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD || item->type == ITEM_TYPE_NUMERICFIELD || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_BIND || item->type == ITEM_TYPE_SLIDER || item->type == ITEM_TYPE_TEXT) { - item->typeData = UI_Alloc(sizeof(editFieldDef_t)); - memset(item->typeData, 0, sizeof(editFieldDef_t)); - if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD) { - if (!((editFieldDef_t *) item->typeData)->maxPaintChars) { - ((editFieldDef_t *) item->typeData)->maxPaintChars = MAX_EDITFIELD; - } - } - } else if (item->type == ITEM_TYPE_MULTI) { - item->typeData = UI_Alloc(sizeof(multiDef_t)); - } else if (item->type == ITEM_TYPE_MODEL) { - item->typeData = UI_Alloc(sizeof(modelDef_t)); - } -} +float Item_ListBox_ThumbDrawPosition(itemDef_t *item) +{ + if (itemCapture == item) + { + float min = SCROLLBAR_SLIDER_Y(item); + float max = min + SCROLLBAR_SLIDER_HEIGHT(item) - SCROLLBAR_ARROW_HEIGHT; + float halfThumbSize = SCROLLBAR_ARROW_HEIGHT / 2.0f; -/* -=============== -Keyword Hash -=============== -*/ + if (DC->cursory >= min + halfThumbSize && DC->cursory <= max + halfThumbSize) + return DC->cursory - halfThumbSize; + } -#define KEYWORDHASH_SIZE 512 + return Item_ListBox_ThumbPosition(item); +} -typedef struct keywordHash_s +float Item_Slider_ThumbPosition(itemDef_t *item) { - char *keyword; - qboolean (*func)(itemDef_t *item, int handle); - struct keywordHash_s *next; -} keywordHash_t; + float value, range, x; + editFieldDef_t *editDef = item->typeData.edit; -int KeywordHash_Key(char *keyword) { - int register hash, i; - - hash = 0; - for (i = 0; keyword[i] != '\0'; i++) { - if (keyword[i] >= 'A' && keyword[i] <= 'Z') - hash += (keyword[i] + ('a' - 'A')) * (119 + i); + if (item->text) + x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET; else - hash += keyword[i] * (119 + i); - } - hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE-1); - return hash; -} + x = item->window.rect.x; -void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) { - int hash; + if (editDef == NULL && item->cvar) + return x; - hash = KeywordHash_Key(key->keyword); -/* - if (table[hash]) { - int collision = qtrue; - } -*/ - key->next = table[hash]; - table[hash] = key; + value = DC->getCVarValue(item->cvar); + + if (value < editDef->minVal) + value = editDef->minVal; + else if (value > editDef->maxVal) + value = editDef->maxVal; + + range = editDef->maxVal - editDef->minVal; + value -= editDef->minVal; + value /= range; + value *= SLIDER_WIDTH; + x += value; + + return x; } -keywordHash_t *KeywordHash_Find(keywordHash_t *table[], char *keyword) +static float Item_Slider_VScale(itemDef_t *item) { - keywordHash_t *key; - int hash; - - hash = KeywordHash_Key(keyword); - for (key = table[hash]; key; key = key->next) { - if (!Q_stricmp(key->keyword, keyword)) - return key; - } - return NULL; + if (SLIDER_THUMB_HEIGHT > item->window.rect.h) + return item->window.rect.h / SLIDER_THUMB_HEIGHT; + else + return 1.0f; } -/* -=============== -Item Keyword Parse functions -=============== -*/ +int Item_Slider_OverSlider(itemDef_t *item, float x, float y) +{ + rectDef_t r; + float vScale = Item_Slider_VScale(item); -// name -qboolean ItemParse_name( itemDef_t *item, int handle ) { - if (!PC_String_Parse(handle, &item->window.name)) { - return qfalse; - } - return qtrue; -} + r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2); + r.y = item->textRect.y - item->textRect.h + ((item->textRect.h - (SLIDER_THUMB_HEIGHT * vScale)) / 2.0f); + r.w = SLIDER_THUMB_WIDTH; + r.h = SLIDER_THUMB_HEIGHT * vScale; -// name -qboolean ItemParse_focusSound( itemDef_t *item, int handle ) { - const char *temp; - if (!PC_String_Parse(handle, &temp)) { - return qfalse; - } - item->focusSound = DC->registerSound(temp, qfalse); - return qtrue; + if (Rect_ContainsPoint(&r, x, y)) + return WINDOW_LB_THUMB; + + return 0; } +int Item_ListBox_OverLB(itemDef_t *item, float x, float y) +{ + rectDef_t r; + int thumbstart; -// text -qboolean ItemParse_text( itemDef_t *item, int handle ) { - if (!PC_String_Parse(handle, &item->text)) { - return qfalse; - } - return qtrue; -} + r.x = SCROLLBAR_SLIDER_X(item); + r.y = SCROLLBAR_Y(item); + r.w = SCROLLBAR_ARROW_WIDTH; + r.h = SCROLLBAR_ARROW_HEIGHT; -// group -qboolean ItemParse_group( itemDef_t *item, int handle ) { - if (!PC_String_Parse(handle, &item->window.group)) { - return qfalse; - } - return qtrue; -} + if (Rect_ContainsPoint(&r, x, y)) + return WINDOW_LB_UPARROW; -// asset_model -qboolean ItemParse_asset_model( itemDef_t *item, int handle ) { - const char *temp; - modelDef_t *modelPtr; - Item_ValidateTypeData(item); - modelPtr = (modelDef_t*)item->typeData; + r.y = SCROLLBAR_SLIDER_Y(item) + SCROLLBAR_SLIDER_HEIGHT(item); - if (!PC_String_Parse(handle, &temp)) { - return qfalse; - } - item->asset = DC->registerModel(temp); - modelPtr->angle = rand() % 360; - return qtrue; -} + if (Rect_ContainsPoint(&r, x, y)) + return WINDOW_LB_DOWNARROW; -// asset_shader -qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) { - const char *temp; + thumbstart = Item_ListBox_ThumbPosition(item); + r.y = thumbstart; - if (!PC_String_Parse(handle, &temp)) { - return qfalse; - } - item->asset = DC->registerShaderNoMip(temp); - return qtrue; -} + if (Rect_ContainsPoint(&r, x, y)) + return WINDOW_LB_THUMB; -// model_origin -qboolean ItemParse_model_origin( itemDef_t *item, int handle ) { - modelDef_t *modelPtr; - Item_ValidateTypeData(item); - modelPtr = (modelDef_t*)item->typeData; - - if (PC_Float_Parse(handle, &modelPtr->origin[0])) { - if (PC_Float_Parse(handle, &modelPtr->origin[1])) { - if (PC_Float_Parse(handle, &modelPtr->origin[2])) { - return qtrue; - } - } - } - return qfalse; -} + r.y = SCROLLBAR_SLIDER_Y(item); + r.h = thumbstart - r.y; -// model_fovx -qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) { - modelDef_t *modelPtr; - Item_ValidateTypeData(item); - modelPtr = (modelDef_t*)item->typeData; + if (Rect_ContainsPoint(&r, x, y)) + return WINDOW_LB_PGUP; - if (!PC_Float_Parse(handle, &modelPtr->fov_x)) { - return qfalse; - } - return qtrue; -} + r.y = thumbstart + SCROLLBAR_ARROW_HEIGHT; + r.h = (SCROLLBAR_SLIDER_Y(item) + SCROLLBAR_SLIDER_HEIGHT(item)) - r.y; -// model_fovy -qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) { - modelDef_t *modelPtr; - Item_ValidateTypeData(item); - modelPtr = (modelDef_t*)item->typeData; + if (Rect_ContainsPoint(&r, x, y)) + return WINDOW_LB_PGDN; - if (!PC_Float_Parse(handle, &modelPtr->fov_y)) { - return qfalse; - } - return qtrue; + return 0; } -// model_rotation -qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) { - modelDef_t *modelPtr; - Item_ValidateTypeData(item); - modelPtr = (modelDef_t*)item->typeData; +void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y) +{ + rectDef_t r; + listBoxDef_t *listPtr = item->typeData.list; + int listBoxFlags = (WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN); + int total = DC->feederCount(item->feederID); - if (!PC_Int_Parse(handle, &modelPtr->rotationSpeed)) { - return qfalse; - } - return qtrue; -} + item->window.flags &= ~listBoxFlags; + item->window.flags |= Item_ListBox_OverLB(item, x, y); -// model_angle -qboolean ItemParse_model_angle( itemDef_t *item, int handle ) { - modelDef_t *modelPtr; - Item_ValidateTypeData(item); - modelPtr = (modelDef_t*)item->typeData; + if (!(item->window.flags & listBoxFlags)) + { + r.x = SCROLLBAR_X(item); + r.y = SCROLLBAR_Y(item); + r.w = SCROLLBAR_W(item); + r.h = listPtr->elementHeight * MIN(Item_ListBox_NumItemsForItemHeight(item), total); - if (!PC_Int_Parse(handle, &modelPtr->angle)) { - return qfalse; - } - return qtrue; -} + if (Rect_ContainsPoint(&r, x, y)) + { + listPtr->cursorPos = (int)((y - r.y) / listPtr->elementHeight) + listPtr->startPos; -// rect -qboolean ItemParse_rect( itemDef_t *item, int handle ) { - if (!PC_Rect_Parse(handle, &item->window.rectClient)) { - return qfalse; - } - return qtrue; + if (listPtr->cursorPos >= listPtr->endPos) + listPtr->cursorPos = listPtr->endPos - 1; + } + else + listPtr->cursorPos = -1; + } } -// style -qboolean ItemParse_style( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->window.style)) { - return qfalse; - } - return qtrue; -} +void Item_MouseEnter(itemDef_t *item, float x, float y) +{ + rectDef_t r; -// decoration -qboolean ItemParse_decoration( itemDef_t *item, int handle ) { - item->window.flags |= WINDOW_DECORATION; - return qtrue; -} + if (item) + { + r = item->textRect; + r.y -= r.h; + // in the text rect? -// notselectable -qboolean ItemParse_notselectable( itemDef_t *item, int handle ) { - listBoxDef_t *listPtr; - Item_ValidateTypeData(item); - listPtr = (listBoxDef_t*)item->typeData; - if (item->type == ITEM_TYPE_LISTBOX && listPtr) { - listPtr->notselectable = qtrue; - } - return qtrue; -} + // items can be enabled and disabled based on cvars -// manually wrapped -qboolean ItemParse_wrapped( itemDef_t *item, int handle ) { - item->window.flags |= WINDOW_WRAPPED; - return qtrue; -} + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + return; -// auto wrapped -qboolean ItemParse_autowrapped( itemDef_t *item, int handle ) { - item->window.flags |= WINDOW_AUTOWRAPPED; - return qtrue; -} + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) + return; + if (Rect_ContainsPoint(&r, x, y)) + { + if (!(item->window.flags & WINDOW_MOUSEOVERTEXT)) + { + Item_RunScript(item, item->mouseEnterText); + item->window.flags |= WINDOW_MOUSEOVERTEXT; + } -// horizontalscroll -qboolean ItemParse_horizontalscroll( itemDef_t *item, int handle ) { - item->window.flags |= WINDOW_HORIZONTAL; - return qtrue; -} + if (!(item->window.flags & WINDOW_MOUSEOVER)) + { + Item_RunScript(item, item->mouseEnter); + item->window.flags |= WINDOW_MOUSEOVER; + } + } + else + { + // not in the text rect -// type -qboolean ItemParse_type( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->type)) { - return qfalse; - } - Item_ValidateTypeData(item); - return qtrue; -} + if (item->window.flags & WINDOW_MOUSEOVERTEXT) + { + // if we were + Item_RunScript(item, item->mouseExitText); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } -// elementwidth, used for listbox image elements -// uses textalignx for storage -qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) { - listBoxDef_t *listPtr; + if (!(item->window.flags & WINDOW_MOUSEOVER)) + { + Item_RunScript(item, item->mouseEnter); + item->window.flags |= WINDOW_MOUSEOVER; + } - Item_ValidateTypeData(item); - listPtr = (listBoxDef_t*)item->typeData; - if (!PC_Float_Parse(handle, &listPtr->elementWidth)) { - return qfalse; - } - return qtrue; + if (item->type == ITEM_TYPE_LISTBOX) + Item_ListBox_MouseEnter(item, x, y); + } + } } -// elementheight, used for listbox image elements -// uses textaligny for storage -qboolean ItemParse_elementheight( itemDef_t *item, int handle ) { - listBoxDef_t *listPtr; +void Item_MouseLeave(itemDef_t *item) +{ + if (item) + { + if (item->window.flags & WINDOW_MOUSEOVERTEXT) + { + Item_RunScript(item, item->mouseExitText); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } - Item_ValidateTypeData(item); - listPtr = (listBoxDef_t*)item->typeData; - if (!PC_Float_Parse(handle, &listPtr->elementHeight)) { - return qfalse; - } - return qtrue; + Item_RunScript(item, item->mouseExit); + item->window.flags &= ~(WINDOW_LB_DOWNARROW | WINDOW_LB_UPARROW); + } } -// feeder -qboolean ItemParse_feeder( itemDef_t *item, int handle ) { - if (!PC_Float_Parse(handle, &item->special)) { - return qfalse; - } - return qtrue; -} +itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y) +{ + int i; -// elementtype, used to specify what type of elements a listbox contains -// uses textstyle for storage -qboolean ItemParse_elementtype( itemDef_t *item, int handle ) { - listBoxDef_t *listPtr; + for (i = 0; i < menu->itemCount; i++) + { + if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) + return menu->items[i]; + } - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; - listPtr = (listBoxDef_t*)item->typeData; - if (!PC_Int_Parse(handle, &listPtr->elementStyle)) { - return qfalse; - } - return qtrue; + return NULL; } -// columns sets a number of columns and an x pos and width per.. -qboolean ItemParse_columns( itemDef_t *item, int handle ) { - int num, i; - listBoxDef_t *listPtr; - - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; - listPtr = (listBoxDef_t*)item->typeData; - if (PC_Int_Parse(handle, &num)) { - if (num > MAX_LB_COLUMNS) { - num = MAX_LB_COLUMNS; - } - listPtr->numColumns = num; - for (i = 0; i < num; i++) { - int pos, width, maxChars, align; - - if( PC_Int_Parse( handle, &pos ) && - PC_Int_Parse( handle, &width ) && - PC_Int_Parse( handle, &maxChars ) && - PC_Int_Parse( handle, &align ) ) - { - listPtr->columnInfo[i].pos = pos; - listPtr->columnInfo[i].width = width; - listPtr->columnInfo[i].maxChars = maxChars; - listPtr->columnInfo[i].align = align; - } else { - return qfalse; - } +void Item_SetMouseOver(itemDef_t *item, qboolean focus) +{ + if (item) + { + if (focus) + item->window.flags |= WINDOW_MOUSEOVER; + else + item->window.flags &= ~WINDOW_MOUSEOVER; } - } else { - return qfalse; - } - return qtrue; } -qboolean ItemParse_border( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->window.border)) { - return qfalse; - } - return qtrue; -} +qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) +{ + if (item && DC->ownerDrawHandleKey) + return DC->ownerDrawHandleKey(item->window.ownerDraw, key); -qboolean ItemParse_bordersize( itemDef_t *item, int handle ) { - if (!PC_Float_Parse(handle, &item->window.borderSize)) { return qfalse; - } - return qtrue; } -qboolean ItemParse_visible( itemDef_t *item, int handle ) { - int i; +qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) +{ + listBoxDef_t *listPtr = item->typeData.list; + int count = DC->feederCount(item->feederID); + int viewmax; + + if (force || + (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) + { + viewmax = Item_ListBox_NumItemsForItemHeight(item); + + switch (key) + { + case K_MOUSE1: + case K_MOUSE2: + if (item->window.flags & WINDOW_LB_UPARROW) + Item_ListBox_SetStartPos(item, listPtr->startPos - 1); + else if (item->window.flags & WINDOW_LB_DOWNARROW) + Item_ListBox_SetStartPos(item, listPtr->startPos + 1); + else if (item->window.flags & WINDOW_LB_PGUP) + Item_ListBox_SetStartPos(item, listPtr->startPos - viewmax); + else if (item->window.flags & WINDOW_LB_PGDN) + Item_ListBox_SetStartPos(item, listPtr->startPos + viewmax); + else if (item->window.flags & WINDOW_LB_THUMB) + break; // Handled by capture function + else + { + // Select an item + qboolean runDoubleClick = qfalse; + + // Mouse isn't over an item + if (listPtr->cursorPos < 0) + break; + + if (item->cursorPos != listPtr->cursorPos) + { + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->feederID, item->cursorPos); + } + + runDoubleClick = DC->realTime < lastListBoxClickTime && listPtr->doubleClick; + lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; + + // Made a selection, so close combobox + if (g_comboBoxItem != NULL) + { + if (listPtr->doubleClick) + runDoubleClick = qtrue; + + g_comboBoxItem = NULL; + } + + if (runDoubleClick) + Item_RunScript(item, listPtr->doubleClick); + } + + break; + + case K_MWHEELUP: + Item_ListBox_SetStartPos(item, listPtr->startPos - 1); + break; + + case K_MWHEELDOWN: + Item_ListBox_SetStartPos(item, listPtr->startPos + 1); + break; + + case K_ENTER: + // Invoke the doubleClick handler when enter is pressed + if (listPtr->doubleClick) + Item_RunScript(item, listPtr->doubleClick); + + break; + + case K_PGUP: + case K_KP_PGUP: + if (!listPtr->notselectable) + { + listPtr->cursorPos -= viewmax; + + if (listPtr->cursorPos < 0) + listPtr->cursorPos = 0; + + if (listPtr->cursorPos < listPtr->startPos) + Item_ListBox_SetStartPos(item, listPtr->cursorPos); + + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + Item_ListBox_SetStartPos(item, listPtr->cursorPos - viewmax + 1); + + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->feederID, item->cursorPos); + } + else + Item_ListBox_SetStartPos(item, listPtr->startPos - viewmax); + + break; + + case K_PGDN: + case K_KP_PGDN: + if (!listPtr->notselectable) + { + listPtr->cursorPos += viewmax; + + if (listPtr->cursorPos < listPtr->startPos) + Item_ListBox_SetStartPos(item, listPtr->cursorPos); + + if (listPtr->cursorPos >= count) + listPtr->cursorPos = count - 1; + + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + Item_ListBox_SetStartPos(item, listPtr->cursorPos - viewmax + 1); + + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->feederID, item->cursorPos); + } + else + Item_ListBox_SetStartPos(item, listPtr->startPos + viewmax); + + break; + + default: + // Not handled + return qfalse; + } + + return qtrue; + } + + return qfalse; +} + +qboolean Item_ComboBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) +{ + if (g_comboBoxItem != NULL) + { + qboolean result; + + qboolean cast = Item_ComboBox_MaybeCastToListBox(item); + result = Item_ListBox_HandleKey(item, key, down, force); + Item_ComboBox_MaybeUnCastFromListBox(item, cast); + + if (!result) + g_comboBoxItem = NULL; + + return result; + } + else + { + if (force || + (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) + { + if (key == K_MOUSE1 || key == K_MOUSE2) + { + g_comboBoxItem = item; + + return qtrue; + } + } + } + + return qfalse; +} + +qboolean Item_YesNo_HandleKey(itemDef_t *item, int key) +{ + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && + item->cvar) + { + if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) + { + DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar))); + return qtrue; + } + } + + return qfalse; +} + +int Item_Multi_CountSettings(itemDef_t *item) +{ + if (item->typeData.multi == NULL) + return 0; + + return item->typeData.multi->count; +} + +int Item_Multi_FindCvarByValue(itemDef_t *item) +{ + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = item->typeData.multi; + + if (multiPtr) + { + if (multiPtr->strDef) + DC->getCVarString(item->cvar, buff, sizeof(buff)); + else + value = DC->getCVarValue(item->cvar); + + for (i = 0; i < multiPtr->count; i++) + { + if (multiPtr->strDef) + { + if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) + return i; + } + else + { + if (multiPtr->cvarValue[i] == value) + return i; + } + } + } + + return 0; +} + +const char *Item_Multi_Setting(itemDef_t *item) +{ + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = item->typeData.multi; + + if (multiPtr) + { + if (multiPtr->strDef) + DC->getCVarString(item->cvar, buff, sizeof(buff)); + else + value = DC->getCVarValue(item->cvar); + + for (i = 0; i < multiPtr->count; i++) + { + if (multiPtr->strDef) + { + if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) + return multiPtr->cvarList[i]; + } + else + { + if (multiPtr->cvarValue[i] == value) + return multiPtr->cvarList[i]; + } + } + } + + return ""; +} + +qboolean Item_Cycle_HandleKey(itemDef_t *item, int key) +{ + cycleDef_t *cyclePtr = item->typeData.cycle; + qboolean mouseOver = Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory); + int count = DC->feederCount(item->feederID); + + if (cyclePtr) + { + if (item->window.flags & WINDOW_HASFOCUS) + { + if ((mouseOver && key == K_MOUSE1) || key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW) + { + if (count > 0) + cyclePtr->cursorPos = (cyclePtr->cursorPos + 1) % count; + + DC->feederSelection(item->feederID, cyclePtr->cursorPos); + + return qtrue; + } + else if ((mouseOver && key == K_MOUSE2) || key == K_LEFTARROW || key == K_UPARROW) + { + if (count > 0) + cyclePtr->cursorPos = (count + cyclePtr->cursorPos - 1) % count; + + DC->feederSelection(item->feederID, cyclePtr->cursorPos); + + return qtrue; + } + } + } + + return qfalse; +} + +qboolean Item_Multi_HandleKey(itemDef_t *item, int key) +{ + qboolean mouseOver = Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory); + int max = Item_Multi_CountSettings(item); + qboolean changed = qfalse; + + if (item->typeData.multi) + { + if (item->window.flags & WINDOW_HASFOCUS && item->cvar && max > 0) + { + int current; + + if ((mouseOver && key == K_MOUSE1) || key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW) + { + current = (Item_Multi_FindCvarByValue(item) + 1) % max; + changed = qtrue; + } + else if ((mouseOver && key == K_MOUSE2) || key == K_LEFTARROW || key == K_UPARROW) + { + current = (Item_Multi_FindCvarByValue(item) + max - 1) % max; + changed = qtrue; + } + + if (changed) + { + if (item->typeData.multi->strDef) + DC->setCVar(item->cvar, item->typeData.multi->cvarStr[current]); + else + { + float value = item->typeData.multi->cvarValue[current]; + + if (((float)((int)value)) == value) + DC->setCVar(item->cvar, va("%i", (int)value)); + else + DC->setCVar(item->cvar, va("%f", value)); + } + + return qtrue; + } + } + } + + return qfalse; +} + +#define MIN_FIELD_WIDTH 10 +#define EDIT_CURSOR_WIDTH 10 + +static void Item_TextField_CalcPaintOffset(itemDef_t *item, char *buff) +{ + editFieldDef_t *editPtr = item->typeData.edit; + + if (item->cursorPos < editPtr->paintOffset) + editPtr->paintOffset = item->cursorPos; + else + { + // If there is a maximum field width + + if (editPtr->maxFieldWidth > 0) + { + // If the cursor is at the end of the string, maximise the amount of the + // string that's visible + + if (buff[item->cursorPos + 1] == '\0') + { + while (UI_Text_Width(&buff[editPtr->paintOffset], item->textscale) <= + (editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH) && + editPtr->paintOffset > 0) + editPtr->paintOffset--; + } + + buff[item->cursorPos + 1] = '\0'; + + // Shift paintOffset so that the cursor is visible + + while (UI_Text_Width(&buff[editPtr->paintOffset], item->textscale) > + (editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH)) + editPtr->paintOffset++; + } + } +} + +qboolean Item_TextField_HandleKey(itemDef_t *item, int key) +{ + char buff[1024]; + int len; + itemDef_t *newItem = NULL; + editFieldDef_t *editPtr = item->typeData.edit; + qboolean releaseFocus = qtrue; + + if (item->cvar) + { + Com_Memset(buff, 0, sizeof(buff)); + DC->getCVarString(item->cvar, buff, sizeof(buff)); + len = strlen(buff); + + if (len < item->cursorPos) + item->cursorPos = len; + + if (editPtr->maxChars && len > editPtr->maxChars) + len = editPtr->maxChars; + + if (key & K_CHAR_FLAG) + { + key &= ~K_CHAR_FLAG; + + if (key == 'h' - 'a' + 1) + { + // ctrl-h is backspace + + if (item->cursorPos > 0) + { + memmove(&buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos); + item->cursorPos--; + } + + DC->setCVar(item->cvar, buff); + } + else if (key < 32 || !item->cvar) + { + // Ignore any non printable chars + releaseFocus = qfalse; + goto exit; + } + else if (item->type == ITEM_TYPE_NUMERICFIELD && (key < '0' || key > '9')) + { + // Ignore non-numeric characters + releaseFocus = qfalse; + goto exit; + } + else + { + if (!DC->getOverstrikeMode()) + { + if ((len == MAX_EDITFIELD - 1) || (editPtr->maxChars && len >= editPtr->maxChars)) + { + // Reached maximum field length + releaseFocus = qfalse; + goto exit; + } + + memmove(&buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos); + } + else + { + // Reached maximum field length + if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars) + { + releaseFocus = qfalse; + goto exit; + } + } + + buff[item->cursorPos] = key; + + DC->setCVar(item->cvar, buff); + + if (item->cursorPos < len + 1) + item->cursorPos++; + } + } + else + { + switch (key) + { + case K_DEL: + case K_KP_DEL: + if (item->cursorPos < len) + { + memmove(buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos); + DC->setCVar(item->cvar, buff); + } + + break; + + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if (item->cursorPos < len) + item->cursorPos++; + + break; + + case K_LEFTARROW: + case K_KP_LEFTARROW: + if (item->cursorPos > 0) + item->cursorPos--; + + break; + + case K_HOME: + case K_KP_HOME: + item->cursorPos = 0; + + break; + + case K_END: + case K_KP_END: + item->cursorPos = len; + + break; + + case K_INS: + case K_KP_INS: + DC->setOverstrikeMode(!DC->getOverstrikeMode()); + + break; + + case K_TAB: + case K_DOWNARROW: + case K_KP_DOWNARROW: + case K_UPARROW: + case K_KP_UPARROW: + // Ignore these keys from the say field + if (item->type == ITEM_TYPE_SAYFIELD) + break; + + newItem = Menu_SetNextCursorItem(item->parent); + + if (newItem && Item_IsEditField(newItem)) + { + g_editItem = newItem; + } + else + { + releaseFocus = qtrue; + goto exit; + } + + break; + + case K_MOUSE1: + case K_MOUSE2: + case K_MOUSE3: + case K_MOUSE4: + // Ignore these buttons from the say field + if (item->type == ITEM_TYPE_SAYFIELD) + break; + // FALLTHROUGH + case K_ENTER: + case K_KP_ENTER: + case K_ESCAPE: + releaseFocus = qtrue; + goto exit; + + default: + break; + } + } + + releaseFocus = qfalse; + } + +exit: + Item_TextField_CalcPaintOffset(item, buff); + + return !releaseFocus; +} + +static void _Scroll_ListBox_AutoFunc(scrollInfo_t *si) +{ + if (DC->realTime > si->nextScrollTime) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); + + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if (DC->realTime > si->nextAdjustTime) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + + if (si->adjustValue > SCROLL_TIME_FLOOR) + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } +} + +static void Scroll_ListBox_AutoFunc(void *p) +{ + scrollInfo_t *si = (scrollInfo_t *)p; + + qboolean cast = Item_ComboBox_MaybeCastToListBox(si->item); + _Scroll_ListBox_AutoFunc(si); + Item_ComboBox_MaybeUnCastFromListBox(si->item, cast); +} + +static void _Scroll_ListBox_ThumbFunc(scrollInfo_t *si) +{ + rectDef_t r; + int pos, max; + + if (DC->cursory != si->yStart) + { + r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_ARROW_WIDTH - 1; + r.y = si->item->window.rect.y + SCROLLBAR_ARROW_HEIGHT + 1; + r.w = SCROLLBAR_ARROW_WIDTH; + r.h = si->item->window.rect.h - (SCROLLBAR_ARROW_HEIGHT * 2) - 2; + max = Item_ListBox_MaxScroll(si->item); + // + pos = (DC->cursory - r.y - SCROLLBAR_ARROW_HEIGHT / 2) * max / (r.h - SCROLLBAR_ARROW_HEIGHT); + + if (pos < 0) + pos = 0; + else if (pos > max) + pos = max; + + Item_ListBox_SetStartPos(si->item, pos); + si->yStart = DC->cursory; + } + + if (DC->realTime > si->nextScrollTime) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); + + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if (DC->realTime > si->nextAdjustTime) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + + if (si->adjustValue > SCROLL_TIME_FLOOR) + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } +} + +static void Scroll_ListBox_ThumbFunc(void *p) +{ + scrollInfo_t *si = (scrollInfo_t *)p; + + qboolean cast = Item_ComboBox_MaybeCastToListBox(si->item); + _Scroll_ListBox_ThumbFunc(si); + Item_ComboBox_MaybeUnCastFromListBox(si->item, cast); +} + +static void Scroll_Slider_ThumbFunc(void *p) +{ + float x, value, cursorx; + scrollInfo_t *si = (scrollInfo_t *)p; + + if (si->item->text) + x = si->item->textRect.x + si->item->textRect.w + ITEM_VALUE_OFFSET; + else + x = si->item->window.rect.x; + + cursorx = DC->cursorx; + + if (cursorx < x) + cursorx = x; + else if (cursorx > x + SLIDER_WIDTH) + cursorx = x + SLIDER_WIDTH; + + value = cursorx - x; + value /= SLIDER_WIDTH; + value *= si->item->typeData.edit->maxVal - si->item->typeData.edit->minVal; + value += si->item->typeData.edit->minVal; + DC->setCVar(si->item->cvar, va("%f", value)); +} + +void Item_StartCapture(itemDef_t *item, int key) +{ + int flags; + + // Don't allow captureFunc to be overridden + + if (captureFunc != voidFunction) + return; + + switch (item->type) + { + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + { + qboolean cast = Item_ComboBox_MaybeCastToListBox(item); + flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory); + Item_ComboBox_MaybeUnCastFromListBox(item, cast); + + if (flags & (WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW)) + { + scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; + scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + scrollInfo.adjustValue = SCROLL_TIME_START; + scrollInfo.scrollKey = key; + scrollInfo.scrollDir = (flags & WINDOW_LB_UPARROW) ? qtrue : qfalse; + scrollInfo.item = item; + UI_InstallCaptureFunc(Scroll_ListBox_AutoFunc, &scrollInfo, 0); + itemCapture = item; + } + else if (flags & WINDOW_LB_THUMB) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + UI_InstallCaptureFunc(Scroll_ListBox_ThumbFunc, &scrollInfo, 0); + itemCapture = item; + } + + break; + } + + case ITEM_TYPE_SLIDER: + { + flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory); + + if (flags & WINDOW_LB_THUMB) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + UI_InstallCaptureFunc(Scroll_Slider_ThumbFunc, &scrollInfo, 0); + itemCapture = item; + } + + break; + } + } +} + +void Item_StopCapture(itemDef_t *item) {} + +qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) +{ + float x, value, width; + + if (item->window.flags & WINDOW_HASFOCUS && item->cvar && + Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) + { + if (item->typeData.edit && (key == K_ENTER || key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3)) + { + rectDef_t testRect; + width = SLIDER_WIDTH; + + if (item->text) + x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET; + else + x = item->window.rect.x; + + testRect = item->window.rect; + value = (float)SLIDER_THUMB_WIDTH / 2; + testRect.x = x - value; + testRect.w = SLIDER_WIDTH + value; + + if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) + { + value = (float)(DC->cursorx - x) / width; + value *= (item->typeData.edit->maxVal - item->typeData.edit->minVal); + value += item->typeData.edit->minVal; + DC->setCVar(item->cvar, va("%f", value)); + return qtrue; + } + } + } + + return qfalse; +} + +qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) +{ + if (itemCapture) + { + Item_StopCapture(itemCapture); + itemCapture = NULL; + UI_RemoveCaptureFunc(); + } + else + { + if (down && (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3)) + Item_StartCapture(item, key); + } + + if (!down) + return qfalse; + + // Edit fields are handled specially + if (Item_IsEditField(item)) + return qfalse; + + switch (item->type) + { + case ITEM_TYPE_BUTTON: + return qfalse; + + case ITEM_TYPE_RADIOBUTTON: + return qfalse; + + case ITEM_TYPE_CHECKBOX: + return qfalse; + + case ITEM_TYPE_CYCLE: + return Item_Cycle_HandleKey(item, key); + + case ITEM_TYPE_LISTBOX: + return Item_ListBox_HandleKey(item, key, down, qfalse); + + case ITEM_TYPE_COMBOBOX: + return Item_ComboBox_HandleKey(item, key, down, qfalse); + + case ITEM_TYPE_YESNO: + return Item_YesNo_HandleKey(item, key); + + case ITEM_TYPE_MULTI: + return Item_Multi_HandleKey(item, key); + + case ITEM_TYPE_OWNERDRAW: + return Item_OwnerDraw_HandleKey(item, key); + + case ITEM_TYPE_BIND: + return Item_Bind_HandleKey(item, key, down); + + case ITEM_TYPE_SLIDER: + return Item_Slider_HandleKey(item, key, down); + + default: + return qfalse; + } +} + +void Item_Action(itemDef_t *item) +{ + if (item) + Item_RunScript(item, item->action); +} + +itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) +{ + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + if (menu->cursorItem < 0) + { + menu->cursorItem = menu->itemCount - 1; + wrapped = qtrue; + } + + while (menu->cursorItem > -1) + { + menu->cursorItem--; + + if (menu->cursorItem < 0 && !wrapped) + { + wrapped = qtrue; + menu->cursorItem = menu->itemCount - 1; + } + + if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) + { + Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, + menu->items[menu->cursorItem]->window.rect.y + 1); + return menu->items[menu->cursorItem]; + } + } + + menu->cursorItem = oldCursor; + return NULL; +} + +itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu) +{ + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + if (menu->cursorItem == -1) + { + menu->cursorItem = 0; + wrapped = qtrue; + } + + while (menu->cursorItem < menu->itemCount) + { + menu->cursorItem++; + + if (menu->cursorItem >= menu->itemCount && !wrapped) + { + wrapped = qtrue; + menu->cursorItem = 0; + } + + if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) + { + Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, + menu->items[menu->cursorItem]->window.rect.y + 1); + return menu->items[menu->cursorItem]; + } + } + + menu->cursorItem = oldCursor; + return NULL; +} + +static void Window_CloseCinematic(Window *window) +{ + if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) + { + DC->stopCinematic(window->cinematic); + window->cinematic = -1; + } +} + +static void Menu_CloseCinematics(menuDef_t *menu) +{ + if (menu) + { + int i; + Window_CloseCinematic(&menu->window); + + for (i = 0; i < menu->itemCount; i++) + { + Window_CloseCinematic(&menu->items[i]->window); + + if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW) + DC->stopCinematic(0 - menu->items[i]->window.ownerDraw); + } + } +} + +static void Display_CloseCinematics(void) +{ + int i; + + for (i = 0; i < menuCount; i++) + Menu_CloseCinematics(&Menus[i]); +} + +void Menus_Activate(menuDef_t *menu) +{ + int i; + qboolean onTopOfMenuStack = qfalse; + + if (openMenuCount > 0 && menuStack[openMenuCount - 1] == menu) + onTopOfMenuStack = qtrue; + + menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); + + // If being opened for the first time + if (!onTopOfMenuStack) + { + if (menu->onOpen) + { + itemDef_t item; + item.parent = menu; + Item_RunScript(&item, menu->onOpen); + } + + if (menu->soundName && *menu->soundName) + DC->startBackgroundTrack(menu->soundName, menu->soundName); + + Display_CloseCinematics(); + + Menu_HandleMouseMove(menu, DC->cursorx, DC->cursory); // force the item under the cursor to focus + + for (i = 0; i < menu->itemCount; i++) // reset selection in listboxes when opened + { + if (Item_IsListBox(menu->items[i])) + { + menu->items[i]->cursorPos = DC->feederInitialise(menu->items[i]->feederID); + Item_ListBox_SetStartPos(menu->items[i], 0); + DC->feederSelection(menu->items[i]->feederID, menu->items[i]->cursorPos); + } + else if (menu->items[i]->type == ITEM_TYPE_CYCLE) + { + menu->items[i]->typeData.cycle->cursorPos = DC->feederInitialise(menu->items[i]->feederID); + } + } + + if (openMenuCount < MAX_OPEN_MENUS) + menuStack[openMenuCount++] = menu; + } +} + +qboolean Menus_ReplaceActive(menuDef_t *menu) +{ + int i; + menuDef_t *active; + + if (openMenuCount < 1) + return qfalse; + + active = menuStack[openMenuCount - 1]; + + if (!(active->window.flags & WINDOW_HASFOCUS) || !(active->window.flags & WINDOW_VISIBLE)) + { + return qfalse; + } + + if (menu == active) + return qfalse; + + if (menu->itemCount != active->itemCount) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Menus_ReplaceActive: expecting %i menu items, found %i\n", menu->itemCount, + active->itemCount); + return qfalse; + } + + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->type != active->items[i]->type) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Menus_ReplaceActive: type mismatch on item %i\n", i + 1); + return qfalse; + } + } + + active->window.flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE); + menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); + + menuStack[openMenuCount - 1] = menu; + if (menu->onOpen) + { + itemDef_t item; + item.parent = menu; + Item_RunScript(&item, menu->onOpen); + } + + // set the cursor position on the new menu to match the active one + for (i = 0; i < menu->itemCount; i++) + { + menu->items[i]->cursorPos = active->items[i]->cursorPos; + menu->items[i]->feederID = active->items[i]->feederID; + switch (Item_DataType(menu->items[i])) + { + case TYPE_LIST: + menu->items[i]->typeData.list->startPos = active->items[i]->typeData.list->startPos; + menu->items[i]->typeData.list->cursorPos = active->items[i]->typeData.list->cursorPos; + break; + case TYPE_COMBO: + menu->items[i]->typeData.cycle->cursorPos = active->items[i]->typeData.cycle->cursorPos; + break; + default: + break; + } + } + return qtrue; +} + +int Display_VisibleMenuCount(void) +{ + int i, count; + count = 0; + + for (i = 0; i < menuCount; i++) + { + if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) + count++; + } + + return count; +} + +void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) +{ + if (menu) + { + int i; + // basically the behaviour we are looking for is if there are windows in the stack.. see if + // the cursor is within any of them.. if not close them otherwise activate them and pass the + // key on.. force a mouse move to activate focus and script stuff + + if (down && menu->window.flags & WINDOW_OOB_CLICK) + Menus_Close(menu); + + for (i = 0; i < menuCount; i++) + { + if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) + { + Menus_Close(menu); + Menus_Activate(&Menus[i]); + Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory); + Menu_HandleKey(&Menus[i], key, down); + } + } + + if (Display_VisibleMenuCount() == 0) + { + if (DC->Pause) + DC->Pause(qfalse); + } + + Display_CloseCinematics(); + } +} + +static rectDef_t *Item_CorrectedTextRect(itemDef_t *item) +{ + static rectDef_t rect; + memset(&rect, 0, sizeof(rectDef_t)); + + if (item) + { + rect = item->textRect; + + if (rect.w) + rect.y -= rect.h; + } + + return ▭ +} + +void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) +{ + int i; + itemDef_t *item = NULL; + + if (g_waitingForKey && down) + { + Item_Bind_HandleKey(g_bindItem, key, down); + return; + } + + if (g_editingField && down) + { + if (!Item_TextField_HandleKey(g_editItem, key)) + { + g_editingField = qfalse; + Item_RunScript(g_editItem, g_editItem->onTextEntry); + g_editItem = NULL; + return; + } + else + { + Item_RunScript(g_editItem, g_editItem->onCharEntry); + } + } + + if (menu == NULL) + return; + + // see if the mouse is within the window bounds and if so is this a mouse click + if (down && !(menu->window.flags & WINDOW_POPUP) && + !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) + { + static qboolean inHandleKey = qfalse; + + if (!inHandleKey && (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3)) + { + inHandleKey = qtrue; + Menus_HandleOOBClick(menu, key, down); + inHandleKey = qfalse; + return; + } + } + + if (g_comboBoxItem == NULL) + { + // get the item with focus + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) + item = menu->items[i]; + } + } + else + item = g_comboBoxItem; + + if (item != NULL) + { + if (Item_HandleKey(item, key, down)) + { + Item_Action(item); + return; + } + } + + if (!down) + return; + + // default handling + switch (key) + { + case K_F12: + if (DC->getCVarValue("developer")) + DC->executeText(EXEC_APPEND, "screenshot\n"); + + break; + + case K_KP_UPARROW: + case K_UPARROW: + Menu_SetPrevCursorItem(menu); + break; + + case K_ESCAPE: + if (!g_waitingForKey && menu->onESC) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onESC); + } + + break; + + case K_TAB: + case K_KP_DOWNARROW: + case K_DOWNARROW: + Menu_SetNextCursorItem(menu); + break; + + case K_MOUSE1: + case K_MOUSE2: + if (item) + { + if (item->type == ITEM_TYPE_TEXT) + { + if (Rect_ContainsPoint(Item_CorrectedTextRect(item), DC->cursorx, DC->cursory)) + Item_Action(item); + } + else if (Item_IsEditField(item)) + { + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) + { + char buffer[MAX_STRING_CHARS] = {0}; + + if (item->cvar) + DC->getCVarString(item->cvar, buffer, sizeof(buffer)); + + item->cursorPos = strlen(buffer); + + Item_TextField_CalcPaintOffset(item, buffer); + + g_editingField = qtrue; + + g_editItem = item; + } + } + else + { + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) + Item_Action(item); + } + } + + break; + + 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: + break; + + case K_KP_ENTER: + case K_ENTER: + if (item) + { + if (Item_IsEditField(item)) + { + char buffer[MAX_STRING_CHARS] = {0}; + + if (item->cvar) + DC->getCVarString(item->cvar, buffer, sizeof(buffer)); + + item->cursorPos = strlen(buffer); + + Item_TextField_CalcPaintOffset(item, buffer); + + g_editingField = qtrue; + + g_editItem = item; + } + else + Item_Action(item); + } + + break; + } +} + +void ToWindowCoords(float *x, float *y, Window *window) +{ + if (window->border != 0) + { + *x += window->borderSize; + *y += window->borderSize; + } + + *x += window->rect.x; + *y += window->rect.y; +} + +void Rect_ToWindowCoords(rectDef_t *rect, Window *window) { ToWindowCoords(&rect->x, &rect->y, window); } + +void Item_SetTextExtents(itemDef_t *item, const char *text) +{ + const char *textPtr = (text) ? text : item->text; + qboolean cvarContent; + + // It's hard to make a policy on what should be aligned statically and what + // should be aligned dynamically; there are reasonable cases for both. If + // it continues to be a problem then there should probably be an item keyword + // for it; but for the moment only adjusting the alignment of ITEM_TYPE_TEXT + // seems to suffice. + cvarContent = (item->cvar && item->textalignment != ALIGN_LEFT && item->type == ITEM_TYPE_TEXT); + + if (textPtr == NULL) + return; + + // as long as the item isn't dynamic content (ownerdraw or cvar), this + // keeps us from computing the widths and heights more than once + if (item->textRect.w == 0.0f || cvarContent || + (item->type == ITEM_TYPE_OWNERDRAW && item->textalignment != ALIGN_LEFT)) + { + float originalWidth = 0.0f; + + if (item->textalignment == ALIGN_CENTER || item->textalignment == ALIGN_RIGHT) + { + if (cvarContent) + { + char buff[MAX_CVAR_VALUE_STRING]; + DC->getCVarString(item->cvar, buff, sizeof(buff)); + originalWidth = UI_Text_Width(item->text, item->textscale) + UI_Text_Width(buff, item->textscale); + } + else + originalWidth = UI_Text_Width(item->text, item->textscale); + } + + item->textRect.w = UI_Text_Width(textPtr, item->textscale); + item->textRect.h = UI_Text_Height(textPtr, item->textscale); + + if (item->textvalignment == VALIGN_BOTTOM) + item->textRect.y = item->textaligny + item->window.rect.h; + else if (item->textvalignment == VALIGN_CENTER) + item->textRect.y = item->textaligny + ((item->textRect.h + item->window.rect.h) / 2.0f); + else if (item->textvalignment == VALIGN_TOP) + item->textRect.y = item->textaligny + item->textRect.h; + + if (item->textalignment == ALIGN_LEFT) + item->textRect.x = item->textalignx; + else if (item->textalignment == ALIGN_CENTER) + item->textRect.x = item->textalignx + ((item->window.rect.w - originalWidth) / 2.0f); + else if (item->textalignment == ALIGN_RIGHT) + item->textRect.x = item->textalignx + item->window.rect.w - originalWidth; + + ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window); + } +} + +void Item_TextColor(itemDef_t *item, vec4_t *newColor) +{ + vec4_t lowLight; + menuDef_t *parent = (menuDef_t *)item->parent; + + Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, + qtrue, parent->fadeAmount); + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); + else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime / BLINK_DIVISOR) & 1)) + { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor(item->window.foreColor, lowLight, *newColor, 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR)); + } + else + { + memcpy(newColor, &item->window.foreColor, sizeof(vec4_t)); + // items can be enabled and disabled based on cvars + } + + if (item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest) + { + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + memcpy(newColor, &parent->disableColor, sizeof(vec4_t)); + } +} + +static void SkipColorCodes(const char **text, char *lastColor) +{ + while (Q_IsColorString(*text)) + { + lastColor[0] = (*text)[0]; + lastColor[1] = (*text)[1]; + (*text) += 2; + } +} + +static void SkipWhiteSpace(const char **text, char *lastColor) +{ + while (**text) + { + SkipColorCodes(text, lastColor); + + if (**text != '\n' && isspace(**text)) + (*text)++; + else + break; + } +} + +const char *Item_Text_Wrap(const char *text, float scale, float width) +{ + static char out[8192] = ""; + char *paint = out; + char c[3] = ""; + const char *p; + const char *eos; + float indentWidth = 0.0f; + + if (!text) + return NULL; + + p = text; + eos = p + strlen(p); + + if ((eos - p) >= sizeof(out)) + return NULL; + + *paint = '\0'; + + while (*p) + { + float textWidth = 0.0f; + const char *eol = p; + const char *q = p; + float testWidth = width - indentWidth; + + SkipColorCodes(&q, c); + + while (q && textWidth < testWidth) + { + qboolean previousCharIsSpace = qfalse; + + // Remaining string is too short to wrap + if (q >= eos) + { + eol = eos; + break; + } + + if (q > p && *q == INDENT_MARKER) + { + indentWidth = textWidth; + eol = p; + } + + // Some color escapes might still be present + SkipColorCodes(&q, c); + + // Manual line break + if (*q == '\n') + { + eol = q + 1; + break; + } + + if (!previousCharIsSpace && isspace(*q)) + eol = q; + + textWidth += UI_Char_Width(&q, scale); + } + + // No split has taken place, so just split mid-word + if (eol == p) + eol = q; + + // Note that usage of strcat and strlen is deliberately being + // avoided here as it becomes surprisingly expensive on larger + // blocks of text + + // Copy text + strncpy(paint, p, eol - p); + paint += (eol - p); + *paint = '\0'; + + p = eol; + + if (paint - out > 0 && *(paint - 1) == '\n') + { + // The line is deliberately broken, clear the color and + // any current indent + c[0] = '\0'; + indentWidth = 0.0f; + } + else + { + // Add a \n if it's not there already + *paint++ = '\n'; + *paint = '\0'; + + // Insert a pixel indent on the next line + if (indentWidth > 0.0f) + { + const char *indentMarkerText = va("%f%c", indentWidth, INDENT_MARKER); + int indentMarkerTextLength = strlen(indentMarkerText); + + strncpy(paint, indentMarkerText, indentMarkerTextLength); + paint += indentMarkerTextLength; + *paint = '\0'; + } + + // Skip leading whitespace on next line and save the + // last color code + SkipWhiteSpace(&p, c); + } + + if (c[0]) + { + *paint++ = c[0]; + *paint++ = c[1]; + *paint = '\0'; + } + } + + return out; +} + +#define MAX_WRAP_CACHE 16 +#define MAX_WRAP_LINES 32 +#define MAX_WRAP_TEXT 512 + +typedef struct { + char text[MAX_WRAP_TEXT * MAX_WRAP_LINES]; + rectDef_t rect; + float scale; + char lines[MAX_WRAP_LINES][MAX_WRAP_TEXT]; + float lineCoords[MAX_WRAP_LINES][2]; + int numLines; +} wrapCache_t; + +static wrapCache_t wrapCache[MAX_WRAP_CACHE]; +static qboolean cacheCreationFailed = qfalse; +static int cacheWriteIndex = 0; +static int cacheReadIndex = 0; +static int cacheReadLineNum = 0; + +static void UI_CreateCacheEntry(const char *text, const rectDef_t *rect, float scale) +{ + wrapCache_t *cacheEntry = &wrapCache[cacheWriteIndex]; + + if (strlen(text) >= sizeof(cacheEntry->text)) + { + cacheCreationFailed = qtrue; + return; + } + + strcpy(cacheEntry->text, text); + cacheEntry->rect = *rect; + cacheEntry->scale = scale; + cacheEntry->numLines = 0; +} + +static void UI_AddCacheEntryLine(const char *text, float x, float y) +{ + wrapCache_t *cacheEntry = &wrapCache[cacheWriteIndex]; + + if (cacheCreationFailed) + return; + + if (cacheEntry->numLines >= MAX_WRAP_LINES || strlen(text) >= sizeof(cacheEntry->lines[0])) + { + cacheCreationFailed = qtrue; + return; + } + + strcpy(cacheEntry->lines[cacheEntry->numLines], text); + cacheEntry->lineCoords[cacheEntry->numLines][0] = x; + cacheEntry->lineCoords[cacheEntry->numLines][1] = y; + cacheEntry->numLines++; +} + +static void UI_FinishCacheEntry(void) +{ + if (cacheCreationFailed) + { + wrapCache[cacheWriteIndex].text[0] = '\0'; + wrapCache[cacheWriteIndex].numLines = 0; + cacheCreationFailed = qfalse; + } + else + cacheWriteIndex = (cacheWriteIndex + 1) % MAX_WRAP_CACHE; +} + +static qboolean UI_CheckWrapCache(const char *text, const rectDef_t *rect, float scale) +{ + int i; + + for (i = 0; i < MAX_WRAP_CACHE; i++) + { + wrapCache_t *cacheEntry = &wrapCache[i]; + + if (rect->x != cacheEntry->rect.x || rect->y != cacheEntry->rect.y || rect->w != cacheEntry->rect.w || + rect->h != cacheEntry->rect.h) + continue; + + if (strcmp(text, cacheEntry->text)) + continue; + + if (cacheEntry->scale != scale) + continue; + + cacheReadIndex = i; + cacheReadLineNum = 0; + + return qtrue; + } + + return qfalse; +} + +static qboolean UI_NextWrapLine(const char **text, float *x, float *y) +{ + wrapCache_t *cacheEntry = &wrapCache[cacheReadIndex]; + + if (cacheReadLineNum >= cacheEntry->numLines) + return qfalse; + + *text = cacheEntry->lines[cacheReadLineNum]; + *x = cacheEntry->lineCoords[cacheReadLineNum][0]; + *y = cacheEntry->lineCoords[cacheReadLineNum][1]; + + cacheReadLineNum++; + + return qtrue; +} + +void Item_Text_Wrapped_Paint(itemDef_t *item) +{ + char text[1024]; + const char *p, *textPtr; + float x, y, w, h; + vec4_t color; + qboolean useWrapCache = (qboolean)DC->getCVarValue("ui_textWrapCache"); + + if (item->text == NULL) + { + if (item->cvar == NULL) + return; + else + { + DC->getCVarString(item->cvar, text, sizeof(text)); + textPtr = text; + } + } + else + textPtr = item->text; + + if (*textPtr == '\0') + return; + + Item_TextColor(item, &color); + + // Check if this block is cached + if (useWrapCache && UI_CheckWrapCache(textPtr, &item->window.rect, item->textscale)) + { + while (UI_NextWrapLine(&p, &x, &y)) + { + UI_Text_Paint(x, y, item->textscale, color, p, 0, 0, item->textStyle); + } + } + else + { + char buff[1024]; + float fontHeight = UI_Text_EmHeight(item->textscale); + const float lineSpacing = fontHeight * 0.4f; + float lineHeight = fontHeight + lineSpacing; + float textHeight; + int textLength; + int firstLine, paintLines, totalLines, lineNum; + float paintY; + int i; + + if (useWrapCache) + UI_CreateCacheEntry(textPtr, &item->window.rect, item->textscale); + + x = item->window.rect.x + item->textalignx; + y = item->window.rect.y + item->textaligny; + w = item->window.rect.w - (2.0f * item->textalignx); + h = item->window.rect.h - (2.0f * item->textaligny); + + textPtr = Item_Text_Wrap(textPtr, item->textscale, w); + textLength = strlen(textPtr); + + // Count lines + totalLines = 0; + + for (i = 0; i < textLength; i++) + { + if (textPtr[i] == '\n') + totalLines++; + } + if (textLength && textPtr[textLength - 1] != '\n') + { + totalLines++; // count the last, non-newline-terminated line + textLength++; // a '\0' will mark the end of the last line + } + + paintLines = (int)floor((h + lineSpacing) / lineHeight); + + if (totalLines > paintLines) + firstLine = totalLines - paintLines; + else + { + firstLine = 0; + paintLines = totalLines; + } + + textHeight = (paintLines * lineHeight) - lineSpacing; + + switch (item->textvalignment) + { + default: + + case VALIGN_BOTTOM: + paintY = y + (h - textHeight); + break; + + case VALIGN_CENTER: + paintY = y + ((h - textHeight) / 2.0f); + break; + + case VALIGN_TOP: + paintY = y; + break; + } + + p = textPtr; + + // skip the first few lines + for (lineNum = 0; lineNum < firstLine; lineNum++) + p = strchr(p, '\n') + 1; + + for (i = p - textPtr; i < textLength && lineNum < firstLine + paintLines; i++) + { + unsigned long lineLength = &textPtr[i] - p; + + if (lineLength >= sizeof(buff)) + break; + + if (textPtr[i] == '\n' || textPtr[i] == '\0') + { + itemDef_t lineItem; + + memset(&lineItem, 0, sizeof(itemDef_t)); + strncpy(buff, p, lineLength); + buff[lineLength] = '\0'; + p = &textPtr[i + 1]; + + lineItem.type = ITEM_TYPE_TEXT; + lineItem.textscale = item->textscale; + lineItem.textStyle = item->textStyle; + lineItem.text = buff; + lineItem.textalignment = item->textalignment; + lineItem.textvalignment = VALIGN_TOP; + lineItem.textalignx = 0.0f; + lineItem.textaligny = 0.0f; + + lineItem.textRect.w = 0.0f; + lineItem.textRect.h = 0.0f; + lineItem.window.rect.x = x; + lineItem.window.rect.y = paintY + ((lineNum - firstLine) * lineHeight); + lineItem.window.rect.w = w; + lineItem.window.rect.h = lineHeight; + lineItem.window.border = item->window.border; + lineItem.window.borderSize = item->window.borderSize; + + if (DC->getCVarValue("ui_developer")) + { + vec4_t color; + color[0] = color[2] = color[3] = 1.0f; + color[1] = 0.0f; + DC->drawRect(lineItem.window.rect.x, lineItem.window.rect.y, lineItem.window.rect.w, + lineItem.window.rect.h, 1, color); + } + + Item_SetTextExtents(&lineItem, buff); + UI_Text_Paint(lineItem.textRect.x, lineItem.textRect.y, lineItem.textscale, color, buff, 0, 0, + lineItem.textStyle); + + if (useWrapCache) + UI_AddCacheEntryLine(buff, lineItem.textRect.x, lineItem.textRect.y); + + lineNum++; + } + } + + if (useWrapCache) + UI_FinishCacheEntry(); + } +} + +/* +============== +UI_DrawTextBlock +============== +*/ +void UI_DrawTextBlock(rectDef_t *rect, float text_x, float text_y, vec4_t color, float scale, int textalign, + int textvalign, int textStyle, const char *text) +{ + static menuDef_t dummyParent; + static itemDef_t textItem; + + textItem.text = text; + + textItem.parent = &dummyParent; + memcpy(textItem.window.foreColor, color, sizeof(vec4_t)); + textItem.window.flags = 0; + + textItem.window.rect.x = rect->x; + textItem.window.rect.y = rect->y; + textItem.window.rect.w = rect->w; + textItem.window.rect.h = rect->h; + textItem.window.border = 0; + textItem.window.borderSize = 0.0f; + textItem.textRect.x = 0.0f; + textItem.textRect.y = 0.0f; + textItem.textRect.w = 0.0f; + textItem.textRect.h = 0.0f; + textItem.textalignment = textalign; + textItem.textvalignment = textvalign; + textItem.textalignx = text_x; + textItem.textaligny = text_y; + textItem.textscale = scale; + textItem.textStyle = textStyle; + + // Utilise existing wrap code + Item_Text_Wrapped_Paint(&textItem); +} + +void Item_Text_Paint(itemDef_t *item) +{ + char text[1024]; + const char *textPtr; + vec4_t color; + + if (item->window.flags & WINDOW_WRAPPED) + { + Item_Text_Wrapped_Paint(item); + return; + } + + if (item->text == NULL) + { + if (item->cvar == NULL) + return; + else + { + DC->getCVarString(item->cvar, text, sizeof(text)); + textPtr = text; + } + } + else + textPtr = item->text; + + // this needs to go here as it sets extents for cvar types as well + Item_SetTextExtents(item, textPtr); + + if (*textPtr == '\0') + return; + + Item_TextColor(item, &color); + + UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle); +} + +void Item_TextField_Paint(itemDef_t *item) +{ + char buff[1024]; + vec4_t newColor; + menuDef_t *parent; + int offset = (item->text && *item->text) ? ITEM_VALUE_OFFSET : 0; + editFieldDef_t *editPtr = item->typeData.edit; + char cursor = DC->getOverstrikeMode() ? '|' : '_'; + qboolean editing = (item->window.flags & WINDOW_HASFOCUS && g_editingField); + const int cursorWidth = editing ? EDIT_CURSOR_WIDTH : 0; + + // FIXME: causes duplicate printing if item->text is not set (NULL) + Item_Text_Paint(item); + + buff[0] = '\0'; + + if (item->cvar) + DC->getCVarString(item->cvar, buff, sizeof(buff)); + + // maxFieldWidth hasn't been set, so use the item's rect + if (editPtr->maxFieldWidth == 0) + { + editPtr->maxFieldWidth = + item->window.rect.w - (item->textRect.w + offset + (item->textRect.x - item->window.rect.x)); + + if (editPtr->maxFieldWidth < MIN_FIELD_WIDTH) + editPtr->maxFieldWidth = MIN_FIELD_WIDTH; + } + + if (!editing) + editPtr->paintOffset = 0; + + // Shorten string to max viewable + while (UI_Text_Width(buff + editPtr->paintOffset, item->textscale) > (editPtr->maxFieldWidth - cursorWidth) && + strlen(buff) > 0) + buff[strlen(buff) - 1] = '\0'; + + parent = (menuDef_t *)item->parent; + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); + else + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + + if (editing) + { + UI_Text_PaintWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, + newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset, cursor, + editPtr->maxPaintChars, item->textStyle); + } + else + { + UI_Text_Paint(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, + buff + editPtr->paintOffset, 0, editPtr->maxPaintChars, item->textStyle); + } +} + +void Item_YesNo_Paint(itemDef_t *item) +{ + vec4_t newColor; + float value; + int offset; + menuDef_t *parent = (menuDef_t *)item->parent; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); + else + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + + offset = (item->text && *item->text) ? ITEM_VALUE_OFFSET : 0; + + if (item->text) + { + Item_Text_Paint(item); + UI_Text_Paint(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, + (value != 0) ? "Yes" : "No", 0, 0, item->textStyle); + } + else + UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, + item->textStyle); +} + +void Item_Multi_Paint(itemDef_t *item) +{ + vec4_t newColor; + const char *text = ""; + menuDef_t *parent = (menuDef_t *)item->parent; + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); + else + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + + text = Item_Multi_Setting(item); + + if (item->text) + { + Item_Text_Paint(item); + UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale, + newColor, text, 0, 0, item->textStyle); + } + else + UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle); +} + +void Item_Cycle_Paint(itemDef_t *item) +{ + vec4_t newColor; + const char *text = ""; + menuDef_t *parent = (menuDef_t *)item->parent; + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); + else + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + + if (item->typeData.cycle) + text = DC->feederItemText(item->feederID, item->typeData.cycle->cursorPos, 0, NULL); + + if (item->text) + { + Item_Text_Paint(item); + UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale, + newColor, text, 0, 0, item->textStyle); + } + else + UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle); +} + +typedef struct { + char *command; + int id; + int defaultbind1; + int defaultbind2; + int bind1; + int bind2; +} bind_t; + +static bind_t g_bindings[] = {{"+scores", K_TAB, -1, -1, -1, -1}, {"+button2", K_ENTER, -1, -1, -1, -1}, + {"+speed", K_SHIFT, -1, -1, -1, -1}, {"+button6", 'z', -1, -1, -1, -1}, // human dodging + {"+button8", 'x', -1, -1, -1, -1}, {"+forward", K_UPARROW, -1, -1, -1, -1}, {"+back", K_DOWNARROW, -1, -1, -1, -1}, + {"+moveleft", ',', -1, -1, -1, -1}, {"+moveright", '.', -1, -1, -1, -1}, {"+moveup", K_SPACE, -1, -1, -1, -1}, + {"+movedown", 'c', -1, -1, -1, -1}, {"+left", K_LEFTARROW, -1, -1, -1, -1}, + {"+right", K_RIGHTARROW, -1, -1, -1, -1}, {"+strafe", K_ALT, -1, -1, -1, -1}, {"+lookup", K_PGDN, -1, -1, -1, -1}, + {"+lookdown", K_DEL, -1, -1, -1, -1}, {"+mlook", '/', -1, -1, -1, -1}, {"centerview", K_END, -1, -1, -1, -1}, + {"+zoom", -1, -1, -1, -1, -1}, {"weapon 1", '1', -1, -1, -1, -1}, {"weapon 2", '2', -1, -1, -1, -1}, + {"weapon 3", '3', -1, -1, -1, -1}, {"weapon 4", '4', -1, -1, -1, -1}, {"weapon 5", '5', -1, -1, -1, -1}, + {"weapon 6", '6', -1, -1, -1, -1}, {"weapon 7", '7', -1, -1, -1, -1}, {"weapon 8", '8', -1, -1, -1, -1}, + {"weapon 9", '9', -1, -1, -1, -1}, {"weapon 10", '0', -1, -1, -1, -1}, {"weapon 11", -1, -1, -1, -1, -1}, + {"weapon 12", -1, -1, -1, -1, -1}, {"weapon 13", -1, -1, -1, -1, -1}, {"+attack", K_MOUSE1, -1, -1, -1, -1}, + {"+button5", K_MOUSE2, -1, -1, -1, -1}, // secondary attack + {"reload", 'r', -1, -1, -1, -1}, // reload + {"buy ammo", 'b', -1, -1, -1, -1}, // buy ammo + {"itemact medkit", 'm', -1, -1, -1, -1}, // use medkit + {"+button7", 'q', -1, -1, -1, -1}, // buildable use + {"deconstruct", 'e', -1, -1, -1, -1}, // buildable destroy + {"weapprev", '[', -1, -1, -1, -1}, {"weapnext", ']', -1, -1, -1, -1}, {"+button3", K_MOUSE3, -1, -1, -1, -1}, + {"+button4", K_MOUSE4, -1, -1, -1, -1}, {"vote yes", K_F1, -1, -1, -1, -1}, {"vote no", K_F2, -1, -1, -1, -1}, + {"teamvote yes", K_F3, -1, -1, -1, -1}, {"teamvote no", K_F4, -1, -1, -1, -1}, + {"scoresUp", K_KP_PGUP, -1, -1, -1, -1}, {"scoresDown", K_KP_PGDN, -1, -1, -1, -1}, + {"screenshotJPEG", -1, -1, -1, -1, -1}, {"messagemode", -1, -1, -1, -1, -1}, {"messagemode2", -1, -1, -1, -1, -1}}; + +static const size_t g_bindCount = ARRAY_LEN(g_bindings); + +/* +================= +Controls_GetKeyAssignment +================= +*/ +static void Controls_GetKeyAssignment(char *command, int *twokeys) +{ + int count; + int j; + char b[256]; + + twokeys[0] = twokeys[1] = -1; + count = 0; + + for (j = 0; j < 256; j++) + { + DC->getBindingBuf(j, b, 256); + + if (*b == 0) + continue; + + if (!Q_stricmp(b, command)) + { + twokeys[count] = j; + count++; + + if (count == 2) + break; + } + } +} + +/* +================= +Controls_GetConfig + +Iterate each command, get its numeric binding +================= +*/ +void Controls_GetConfig(void) +{ + size_t i; + int twokeys[2]; + + for (i = 0; i < g_bindCount; i++) + { + Controls_GetKeyAssignment(g_bindings[i].command, twokeys); + + g_bindings[i].bind1 = twokeys[0]; + g_bindings[i].bind2 = twokeys[1]; + } +} + +/* +================= +Controls_SetConfig + +Iterate each command, get its numeric binding +================= +*/ +void Controls_SetConfig(qboolean restart) +{ + unsigned int i; + + (void)restart; + + for (i = 0; i < g_bindCount; i++) + { + if (g_bindings[i].bind1 != -1) + { + DC->setBinding(g_bindings[i].bind1, g_bindings[i].command); + if (g_bindings[i].bind2 != -1) + DC->setBinding(g_bindings[i].bind2, g_bindings[i].command); + } + } + + DC->executeText(EXEC_APPEND, "in_restart\n"); +} + +/* +================= +Controls_SetDefaults + +Iterate each command, set its default binding +================= +*/ +void Controls_SetDefaults(void) +{ + unsigned int i; + for (i = 0; i < g_bindCount; i++) + { + g_bindings[i].bind1 = g_bindings[i].defaultbind1; + g_bindings[i].bind2 = g_bindings[i].defaultbind2; + } +} + +int BindingIDFromName(const char *name) +{ + size_t i; + for (i = 0; i < g_bindCount; i++) + { + if (Q_stricmp(name, g_bindings[i].command) == 0) + return i; + } + + return -1; +} + +char g_nameBind1[32]; +char g_nameBind2[32]; + +void BindingFromName(const char *cvar) +{ + int i, b1, b2; + + // iterate each command, set its default binding + + for (i = 0; i < g_bindCount; i++) + { + if (Q_stricmp(cvar, g_bindings[i].command) == 0) + { + b1 = g_bindings[i].bind1; + + if (b1 == -1) + break; + + DC->keynumToStringBuf(b1, g_nameBind1, 32); + Q_strupr(g_nameBind1); + + b2 = g_bindings[i].bind2; + + if (b2 != -1) + { + DC->keynumToStringBuf(b2, g_nameBind2, 32); + Q_strupr(g_nameBind2); + strcat(g_nameBind1, " or "); + strcat(g_nameBind1, g_nameBind2); + } + + return; + } + } + + strcpy(g_nameBind1, "???"); +} + +void Item_Slider_Paint(itemDef_t *item) +{ + vec4_t newColor; + float x, y; + + menuDef_t *parent = (menuDef_t *)item->parent; + float vScale = Item_Slider_VScale(item); + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); + else + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + + if (item->text) + { + Item_Text_Paint(item); + x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET; + y = item->textRect.y - item->textRect.h + ((item->textRect.h - (SLIDER_HEIGHT * vScale)) / 2.0f); + } + else + { + x = item->window.rect.x; + y = item->window.rect.y; + } + + DC->setColor(newColor); + DC->drawHandlePic(x, y, SLIDER_WIDTH, SLIDER_HEIGHT * vScale, DC->Assets.sliderBar); + + y = item->textRect.y - item->textRect.h + ((item->textRect.h - (SLIDER_THUMB_HEIGHT * vScale)) / 2.0f); + + x = Item_Slider_ThumbPosition(item); + DC->drawHandlePic( + x - (SLIDER_THUMB_WIDTH / 2), y, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT * vScale, DC->Assets.sliderThumb); +} + +void Item_Bind_Paint(itemDef_t *item) +{ + vec4_t newColor, lowLight; + float value; + int maxChars = 0; + menuDef_t *parent = (menuDef_t *)item->parent; + + if (item->typeData.edit) + maxChars = item->typeData.edit->maxPaintChars; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + if (item->window.flags & WINDOW_HASFOCUS) + { + if (g_bindItem == item) + { + lowLight[0] = 0.8f * parent->focusColor[0]; + lowLight[1] = 0.8f * parent->focusColor[1]; + lowLight[2] = 0.8f * parent->focusColor[2]; + lowLight[3] = 0.8f * parent->focusColor[3]; + + LerpColor(parent->focusColor, lowLight, newColor, 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR)); + } + else + memcpy(&newColor, &parent->focusColor, sizeof(vec4_t)); + } + else + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + + if (item->text) + { + Item_Text_Paint(item); + + if (g_bindItem == item && g_waitingForKey) + { + UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale, + newColor, "Press key", 0, maxChars, item->textStyle); + } + else + { + BindingFromName(item->cvar); + UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale, + newColor, g_nameBind1, 0, maxChars, item->textStyle); + } + } + else + UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME", + 0, maxChars, item->textStyle); +} + +qboolean Display_KeyBindPending(void) { return g_waitingForKey; } + +qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) +{ + int id; + + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey) + { + if (down && (key == K_MOUSE1 || key == K_ENTER)) + { + g_waitingForKey = qtrue; + g_bindItem = item; + } + + return qtrue; + } + else + { + if (!g_waitingForKey || g_bindItem == NULL) + return qtrue; + + if (key & K_CHAR_FLAG) + return qtrue; + + switch (key) + { + case K_ESCAPE: + g_waitingForKey = qfalse; + return qtrue; + + case K_BACKSPACE: + id = BindingIDFromName(item->cvar); + + if (id != -1) + { + g_bindings[id].bind1 = -1; + g_bindings[id].bind2 = -1; + } + + Controls_SetConfig(qtrue); + g_waitingForKey = qfalse; + g_bindItem = NULL; + return qtrue; + + case '`': + return qtrue; + } + } + + if (key != -1) + { + unsigned int i; + for (i = 0; i < g_bindCount; i++) + { + if (g_bindings[i].bind2 == key) + g_bindings[i].bind2 = -1; + + if (g_bindings[i].bind1 == key) + { + g_bindings[i].bind1 = g_bindings[i].bind2; + g_bindings[i].bind2 = -1; + } + } + } + + id = BindingIDFromName(item->cvar); + + if (id != -1) + { + if (key == -1) + { + if (g_bindings[id].bind1 != -1) + { + DC->setBinding(g_bindings[id].bind1, ""); + g_bindings[id].bind1 = -1; + } + + if (g_bindings[id].bind2 != -1) + { + DC->setBinding(g_bindings[id].bind2, ""); + g_bindings[id].bind2 = -1; + } + } + else if (g_bindings[id].bind1 == -1) + g_bindings[id].bind1 = key; + else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1) + g_bindings[id].bind2 = key; + else + { + DC->setBinding(g_bindings[id].bind1, ""); + DC->setBinding(g_bindings[id].bind2, ""); + g_bindings[id].bind1 = key; + g_bindings[id].bind2 = -1; + } + } + + Controls_SetConfig(qtrue); + g_waitingForKey = qfalse; + + return qtrue; +} + +void Item_Model_Paint(itemDef_t *item) +{ + float x, y, w, h; + refdef_t refdef; + refEntity_t ent; + vec3_t mins, maxs, origin; + vec3_t angles; + + modelDef_t *modelPtr = item->typeData.model; + if (modelPtr == NULL) + return; + + // setup the refdef + memset(&refdef, 0, sizeof(refdef)); + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear(refdef.viewaxis); + + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + w = item->window.rect.w - 2; + h = item->window.rect.h - 2; + + UI_AdjustFrom640(&x, &y, &w, &h); + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + DC->modelBounds(item->asset, mins, maxs); + + origin[2] = -0.5 * (mins[2] + maxs[2]); + origin[1] = 0.5 * (mins[1] + maxs[1]); + + // calculate distance so the model nearly fills the box + if (qtrue) + { + float len = 0.5 * (maxs[2] - mins[2]); + origin[0] = len / 0.268; // len / tan( fov/2 ) + // origin[0] = len / tan(w/2); + } + else + origin[0] = item->textscale; + + refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : w; + refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : h; + + // refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); + // xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); + // refdef.fov_y = atan2( refdef.height, xx ); + // refdef.fov_y *= ( 360 / M_PI ); + + DC->clearScene(); + + refdef.time = DC->realTime; + + // add the model + + memset(&ent, 0, sizeof(ent)); + + // adjust = 5.0 * sin( (float)uis.realtime / 500 ); + // adjust = 360 % (int)((float)uis.realtime / 1000); + // VectorSet( angles, 0, 0, 1 ); + + // use item storage to track + + if (modelPtr->rotationSpeed) + { + if (DC->realTime > item->window.nextTime) + { + item->window.nextTime = DC->realTime + modelPtr->rotationSpeed; + modelPtr->angle = (int)(modelPtr->angle + 1) % 360; + } + } + + VectorSet(angles, 0, modelPtr->angle, 0); + AnglesToAxis(angles, ent.axis); + + ent.hModel = item->asset; + VectorCopy(origin, ent.origin); + VectorCopy(origin, ent.lightingOrigin); + ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + VectorCopy(ent.origin, ent.oldorigin); + + DC->addRefEntityToScene(&ent); + DC->renderScene(&refdef); +} + +void Item_ListBoxRow_Paint(itemDef_t *item, int row, int renderPos, qboolean highlight, qboolean scrollbar) +{ + float x, y, w; + listBoxDef_t *listPtr = item->typeData.list; + menuDef_t *menu = (menuDef_t *)item->parent; + float one, two; + + one = 1.0f * DC->aspectScale; + two = 2.0f * DC->aspectScale; + + x = SCROLLBAR_X(item); + y = SCROLLBAR_Y(item) + (listPtr->elementHeight * renderPos); + w = item->window.rect.w - (two * item->window.borderSize); + + if (scrollbar) + w -= SCROLLBAR_ARROW_WIDTH; + + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + qhandle_t image = DC->feederItemImage(item->feederID, row); + + UI_SetClipRegion(x, y, listPtr->elementWidth, listPtr->elementHeight); + + if (image) + DC->drawHandlePic(x + one, y + 1.0f, listPtr->elementWidth - two, listPtr->elementHeight - 2.0f, image); + + if (highlight && row == item->cursorPos) + { + DC->drawRect( + x, y, listPtr->elementWidth, listPtr->elementHeight, item->window.borderSize, item->window.borderColor); + } + + UI_ClearClipRegion(); + } + else + { + const float m = UI_Text_EmHeight(item->textscale); + char text[MAX_STRING_CHARS]; + qhandle_t optionalImage; + + if (listPtr->numColumns > 0) + { + int j; + + for (j = 0; j < listPtr->numColumns; j++) + { + float columnPos; + float width, height, yOffset; + + if (menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE) + { + columnPos = (listPtr->columnInfo[j].pos + 4.0f) * DC->aspectScale; + width = listPtr->columnInfo[j].width * DC->aspectScale; + } + else + { + columnPos = (listPtr->columnInfo[j].pos + 4.0f); + width = listPtr->columnInfo[j].width; + } + + height = listPtr->columnInfo[j].width; + yOffset = y + ((listPtr->elementHeight - height) / 2.0f); + + Q_strncpyz(text, DC->feederItemText(item->feederID, row, j, &optionalImage), sizeof(text)); + + UI_SetClipRegion(x + columnPos, yOffset, width, height); + + if (optionalImage >= 0) + DC->drawHandlePic(x + columnPos, yOffset, width, height, optionalImage); + else if (text[0]) + { + float alignOffset = 0.0f, tw; + + tw = UI_Text_Width(text, item->textscale); + + switch (listPtr->columnInfo[j].align) + { + case ALIGN_LEFT: + alignOffset = 0.0f; + break; + + case ALIGN_RIGHT: + alignOffset = width - tw; + break; + + case ALIGN_CENTER: + alignOffset = (width / 2.0f) - (tw / 2.0f); + break; + + default: + alignOffset = 0.0f; + } + + UI_Text_Paint(x + columnPos + alignOffset, y + m + ((listPtr->elementHeight - m) / 2.0f), + item->textscale, item->window.foreColor, text, 0, 0, item->textStyle); + } + + UI_ClearClipRegion(); + } + } + else + { + float offset; + + if (menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE) + offset = 4.0f * DC->aspectScale; + else + offset = 4.0f; + + Q_strncpyz(text, DC->feederItemText(item->feederID, row, 0, &optionalImage), sizeof(text)); + + UI_SetClipRegion(x, y, w, listPtr->elementHeight); + + if (optionalImage >= 0) + DC->drawHandlePic(x + offset, y, listPtr->elementHeight, listPtr->elementHeight, optionalImage); + else if (text[0]) + { + UI_Text_Paint(x + offset, y + m + ((listPtr->elementHeight - m) / 2.0f), item->textscale, + item->window.foreColor, text, 0, 0, item->textStyle); + } + + UI_ClearClipRegion(); + } + + if (highlight && row == item->cursorPos) + DC->fillRect(x, y, w, listPtr->elementHeight, item->window.outlineColor); + } +} + +void Item_ListBox_Paint(itemDef_t *item) +{ + float size; + int i; + listBoxDef_t *listPtr = item->typeData.list; + int count = DC->feederCount(item->feederID); + qboolean scrollbar = !(listPtr->noscrollbar && count > Item_ListBox_NumItemsForItemHeight(item)); + if (scrollbar) + { + float x = SCROLLBAR_SLIDER_X(item); + float y = SCROLLBAR_Y(item); + float thumbY = Item_ListBox_ThumbDrawPosition(item); + + // Up arrow + DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowUp); + y = SCROLLBAR_SLIDER_Y(item); + + // Scroll bar + size = SCROLLBAR_SLIDER_HEIGHT(item); + DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, size, DC->Assets.scrollBar); + y = SCROLLBAR_SLIDER_Y(item) + size; + + // Down arrow + DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowDown); + + // Thumb + DC->drawHandlePic(x, thumbY, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarThumb); + } + + // Paint rows + for (i = listPtr->startPos; i < listPtr->endPos; i++) + Item_ListBoxRow_Paint(item, i, i - listPtr->startPos, qtrue, scrollbar); +} + +void Item_Paint(itemDef_t *item); + +void Item_ComboBox_Paint(itemDef_t *item) +{ + float x, y, h; + + x = SCROLLBAR_SLIDER_X(item); + y = SCROLLBAR_Y(item); + h = item->window.rect.h - 2.0f; + + // Down arrow + DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, h, DC->Assets.scrollBarArrowDown); + Item_ListBoxRow_Paint(item, item->cursorPos, 0, qfalse, qtrue); + if (g_comboBoxItem != NULL) + { + qboolean cast = Item_ComboBox_MaybeCastToListBox(item); + Item_Paint(item); + Item_ComboBox_MaybeUnCastFromListBox(item, cast); + } +} + +void Item_ListBox_Update(itemDef_t *item) +{ + listBoxDef_t *listPtr = item->typeData.list; + int feederCount = DC->feederCount(item->feederID); + + if (listPtr->lastFeederCount != feederCount) + { + if (listPtr->resetonfeederchange) + { + item->cursorPos = DC->feederInitialise(item->feederID); + Item_ListBox_SetStartPos(item, 0); + DC->feederSelection(item->feederID, item->cursorPos); + } + else + { + // Make sure endPos is up-to-date + Item_ListBox_SetStartPos(item, listPtr->startPos); + + // If the selection is off the end now, select the last element + if (item->cursorPos >= feederCount) + item->cursorPos = feederCount - 1; + } + } + + listPtr->lastFeederCount = feederCount; +} + +void Item_OwnerDraw_Paint(itemDef_t *item) +{ + menuDef_t *parent; + const char *text; + + if (item == NULL) + return; + + parent = (menuDef_t *)item->parent; + + if (DC->ownerDrawItem) + { + vec4_t color, lowLight; + Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, + parent->fadeCycle, qtrue, parent->fadeAmount); + memcpy(&color, &item->window.foreColor, sizeof(color)); + + if (item->numColors > 0 && DC->getValue) + { + // if the value is within one of the ranges then set color to that, otherwise leave at default + int i; + float f = DC->getValue(item->window.ownerDraw); + + for (i = 0; i < item->numColors; i++) + { + if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high) + { + memcpy(&color, &item->colorRanges[i].color, sizeof(color)); + break; + } + } + } + + if (item->window.flags & WINDOW_HASFOCUS) + memcpy(color, &parent->focusColor, sizeof(vec4_t)); + else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime / BLINK_DIVISOR) & 1)) + { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor(item->window.foreColor, lowLight, color, 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR)); + } + + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + Com_Memcpy(color, parent->disableColor, sizeof(vec4_t)); + + if (DC->ownerDrawText && (text = DC->ownerDrawText(item->window.ownerDraw))) + { + if (item->text && *item->text) + { + Item_Text_Paint(item); + + UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, + item->textscale, color, text, 0, 0, item->textStyle); + } + else + { + item->text = text; + Item_Text_Paint(item); + item->text = NULL; + } + } + else + { + DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, + item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, + item->alignment, item->textalignment, item->textvalignment, item->window.borderSize, item->textscale, + color, item->window.backColor, item->window.background, item->textStyle); + } + } +} + +void Item_Update(itemDef_t *item) +{ + if (item == NULL) + return; + + if (Item_IsListBox(item)) + Item_ListBox_Update(item); +} + +void Item_Paint(itemDef_t *item) +{ + vec4_t red; + menuDef_t *parent = (menuDef_t *)item->parent; + red[0] = red[3] = 1; + red[1] = red[2] = 0; + + if (item == NULL) + return; + + if (item->window.flags & WINDOW_ORBITING) + { + if (DC->realTime > item->window.nextTime) + { + float rx, ry, a, c, s, w, h; + + item->window.nextTime = DC->realTime + item->window.offsetTime; + // translate + w = item->window.rectClient.w / 2; + h = item->window.rectClient.h / 2; + rx = item->window.rectClient.x + w - item->window.rectEffects.x; + ry = item->window.rectClient.y + h - item->window.rectEffects.y; + a = 3 * M_PI / 180; + c = cos(a); + s = sin(a); + item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w; + item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h; + Item_UpdatePosition(item); + } + } + + if (item->window.flags & WINDOW_INTRANSITION) + { + if (DC->realTime > item->window.nextTime) + { + int done = 0; + item->window.nextTime = DC->realTime + item->window.offsetTime; + // transition the x,y + + if (item->window.rectClient.x == item->window.rectEffects.x) + done++; + else + { + if (item->window.rectClient.x < item->window.rectEffects.x) + { + item->window.rectClient.x += item->window.rectEffects2.x; + + if (item->window.rectClient.x > item->window.rectEffects.x) + { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + else + { + item->window.rectClient.x -= item->window.rectEffects2.x; + + if (item->window.rectClient.x < item->window.rectEffects.x) + { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + } + + if (item->window.rectClient.y == item->window.rectEffects.y) + done++; + else + { + if (item->window.rectClient.y < item->window.rectEffects.y) + { + item->window.rectClient.y += item->window.rectEffects2.y; + + if (item->window.rectClient.y > item->window.rectEffects.y) + { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + else + { + item->window.rectClient.y -= item->window.rectEffects2.y; + + if (item->window.rectClient.y < item->window.rectEffects.y) + { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + } + + if (item->window.rectClient.w == item->window.rectEffects.w) + done++; + else + { + if (item->window.rectClient.w < item->window.rectEffects.w) + { + item->window.rectClient.w += item->window.rectEffects2.w; + + if (item->window.rectClient.w > item->window.rectEffects.w) + { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + else + { + item->window.rectClient.w -= item->window.rectEffects2.w; + + if (item->window.rectClient.w < item->window.rectEffects.w) + { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + } + + if (item->window.rectClient.h == item->window.rectEffects.h) + done++; + else + { + if (item->window.rectClient.h < item->window.rectEffects.h) + { + item->window.rectClient.h += item->window.rectEffects2.h; + + if (item->window.rectClient.h > item->window.rectEffects.h) + { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + else + { + item->window.rectClient.h -= item->window.rectEffects2.h; + + if (item->window.rectClient.h < item->window.rectEffects.h) + { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + } + + Item_UpdatePosition(item); + + if (done == 4) + item->window.flags &= ~WINDOW_INTRANSITION; + } + } + + if (item->window.ownerDrawFlags && DC->ownerDrawVisible) + { + if (!DC->ownerDrawVisible(item->window.ownerDrawFlags)) + item->window.flags &= ~WINDOW_VISIBLE; + else + item->window.flags |= WINDOW_VISIBLE; + } + + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE)) + { + if (!Item_EnableShowViaCvar(item, CVAR_SHOW)) + return; + } + + if (item->window.flags & WINDOW_TIMEDVISIBLE) + { + } + + if (!(item->window.flags & WINDOW_VISIBLE)) + return; + + Window_Paint(&item->window, parent->fadeAmount, parent->fadeClamp, parent->fadeCycle); + + if (DC->getCVarValue("ui_developer")) + { + vec4_t color; + rectDef_t *r = Item_CorrectedTextRect(item); + color[1] = color[3] = 1; + color[0] = color[2] = 0; + DC->drawRect(r->x, r->y, r->w, r->h, 1, color); + } + + switch (item->type) + { + case ITEM_TYPE_OWNERDRAW: + Item_OwnerDraw_Paint(item); + break; + + case ITEM_TYPE_TEXT: + case ITEM_TYPE_BUTTON: + Item_Text_Paint(item); + break; + + case ITEM_TYPE_RADIOBUTTON: + break; + + case ITEM_TYPE_CHECKBOX: + break; + + case ITEM_TYPE_CYCLE: + Item_Cycle_Paint(item); + break; + + case ITEM_TYPE_LISTBOX: + Item_ListBox_Paint(item); + break; + + case ITEM_TYPE_COMBOBOX: + Item_ComboBox_Paint(item); + break; + + case ITEM_TYPE_MODEL: + Item_Model_Paint(item); + break; + + case ITEM_TYPE_YESNO: + Item_YesNo_Paint(item); + break; + + case ITEM_TYPE_MULTI: + Item_Multi_Paint(item); + break; + + case ITEM_TYPE_BIND: + Item_Bind_Paint(item); + break; + + case ITEM_TYPE_SLIDER: + Item_Slider_Paint(item); + break; + + default: + if (Item_IsEditField(item)) + Item_TextField_Paint(item); + + break; + } + + Border_Paint(&item->window); +} + +void Menu_Init(menuDef_t *menu) +{ + memset(menu, 0, sizeof(menuDef_t)); + menu->cursorItem = -1; + menu->fadeAmount = DC->Assets.fadeAmount; + menu->fadeClamp = DC->Assets.fadeClamp; + menu->fadeCycle = DC->Assets.fadeCycle; + Window_Init(&menu->window); + menu->window.aspectBias = ALIGN_CENTER; +} + +itemDef_t *Menu_GetFocusedItem(menuDef_t *menu) +{ + int i; + + if (menu) + { + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) + return menu->items[i]; + } + } + + return NULL; +} + +menuDef_t *Menu_GetFocused(void) +{ + int i; + + for (i = 0; i < menuCount; i++) + { + if (Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE) + return &Menus[i]; + } + + return NULL; +} + +void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down) +{ + if (menu) + { + int i; + + for (i = 0; i < menu->itemCount; i++) + { + itemDef_t *item = menu->items[i]; + + if (item->feederID == feeder) + { + qboolean cast = Item_ComboBox_MaybeCastToListBox(item); + Item_ListBox_HandleKey(item, down ? K_DOWNARROW : K_UPARROW, qtrue, qtrue); + Item_ComboBox_MaybeUnCastFromListBox(item, cast); + + return; + } + } + } +} + +void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name) +{ + if (menu == NULL) + { + if (name == NULL) + menu = Menu_GetFocused(); + else + menu = Menus_FindByName(name); + } + + if (menu) + { + int i; + + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->feederID == feeder) + { + if (Item_IsListBox(menu->items[i]) && index == 0) + { + menu->items[i]->typeData.list->cursorPos = 0; + Item_ListBox_SetStartPos(menu->items[i], 0); + } + + menu->items[i]->cursorPos = index; + DC->feederSelection(menu->items[i]->feederID, menu->items[i]->cursorPos); + return; + } + } + } +} + +qboolean Menus_AnyFullScreenVisible(void) +{ + int i; + + for (i = 0; i < menuCount; i++) + { + if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) + return qtrue; + } + + return qfalse; +} + +menuDef_t *Menus_ActivateByName(const char *p) +{ + int i; + menuDef_t *m = NULL; + + // Activate one menu + + for (i = 0; i < menuCount; i++) + { + if (Q_stricmp(Menus[i].window.name, p) == 0) + { + m = &Menus[i]; + Menus_Activate(m); + break; + } + } + + // Defocus the others + for (i = 0; i < menuCount; i++) + { + if (Q_stricmp(Menus[i].window.name, p) != 0) + Menus[i].window.flags &= ~WINDOW_HASFOCUS; + } + + return m; +} + +menuDef_t *Menus_ReplaceActiveByName(const char *p) +{ + int i; + menuDef_t *m = NULL; + + // Activate one menu + + for (i = 0; i < menuCount; i++) + { + if (Q_stricmp(Menus[i].window.name, p) == 0) + { + m = &Menus[i]; + if (!Menus_ReplaceActive(m)) + return NULL; + break; + } + } + return m; +} + +void Item_Init(itemDef_t *item) +{ + memset(item, 0, sizeof(itemDef_t)); + item->textscale = 0.55f; + Window_Init(&item->window); + item->window.aspectBias = ASPECT_NONE; +} + +static qboolean Item_HandleMouseMove(itemDef_t *item, float x, float y, int pass, qboolean focusSet) +{ + if (Rect_ContainsPoint(&item->window.rect, x, y)) + { + if (pass == 1) + { + if (item->type == ITEM_TYPE_TEXT && item->text) + { + if (!Rect_ContainsPoint(Item_CorrectedTextRect(item), x, y)) + return qtrue; + } + + // if we are over an item + if (IsVisible(item->window.flags)) + { + // different one + Item_MouseEnter(item, x, y); + + if (!focusSet) + focusSet = Item_SetFocus(item, x, y); + } + } + + return qtrue; + } + + return qfalse; +} + +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) +{ + int i, pass; + qboolean focusSet = qfalse; + qboolean result; + qboolean cast; + + if (menu == NULL) + return; + + if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) + return; + + if (itemCapture) + { + // Item_MouseMove(itemCapture, x, y); + return; + } + + if (g_waitingForKey || g_editingField) + return; + + if (g_comboBoxItem != NULL) + { + Item_SetFocus(g_comboBoxItem, x, y); + focusSet = qtrue; + } + + // FIXME: this is the whole issue of focus vs. mouse over.. + // need a better overall solution as i don't like going through everything twice + for (pass = 0; pass < 2; pass++) + { + for (i = 0; i < menu->itemCount; i++) + { + itemDef_t *item = menu->items[i]; + + // turn off focus each item + // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + + if (!(item->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) + continue; + + // items can be enabled and disabled based on cvars + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + continue; + + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) + continue; + + cast = Item_ComboBox_MaybeCastToListBox(item); + result = Item_HandleMouseMove(item, x, y, pass, focusSet); + Item_ComboBox_MaybeUnCastFromListBox(item, cast); + + if (!result && item->window.flags & WINDOW_MOUSEOVER) + { + Item_MouseLeave(item); + Item_SetMouseOver(item, qfalse); + } + } + } +} + +void Menu_Update(menuDef_t *menu) +{ + int i; + + if (menu == NULL) + return; + + for (i = 0; i < menu->itemCount; i++) + Item_Update(menu->items[i]); +} + +void Menu_Paint(menuDef_t *menu, qboolean forcePaint) +{ + int i; + + if (menu == NULL) + return; + + if (!(menu->window.flags & WINDOW_VISIBLE) && !forcePaint) + return; + + if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) + return; + + if (forcePaint) + menu->window.flags |= WINDOW_FORCED; + + // draw the background if necessary + if (menu->fullScreen) + { + // implies a background shader + // FIXME: make sure we have a default shader if fullscreen is set with no background + DC->drawHandlePic(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background); + } + + // paint the background and or border + Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle); + + Border_Paint(&menu->window); + + for (i = 0; i < menu->itemCount; i++) + Item_Paint(menu->items[i]); + + if (DC->getCVarValue("ui_developer")) + { + vec4_t color; + color[0] = color[2] = color[3] = 1; + color[1] = 0; + DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color); + } +} + + /* + =============== + Keyword Hash + =============== + */ + +#define KEYWORDHASH_SIZE 512 + +typedef struct keywordHash_s { + char *keyword; + qboolean (*func)(itemDef_t *item, int handle); + int param; + struct keywordHash_s *next; +} keywordHash_t; + +int KeywordHash_Key(char *keyword) +{ + int register hash, i; + + hash = 0; + + for (i = 0; keyword[i] != '\0'; i++) + { + if (keyword[i] >= 'A' && keyword[i] <= 'Z') + hash += (keyword[i] + ('a' - 'A')) * (119 + i); + else + hash += keyword[i] * (119 + i); + } + + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE - 1); + return hash; +} + +void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) +{ + int hash; + + hash = KeywordHash_Key(key->keyword); + /* + if(table[hash]) int collision = qtrue; + */ + key->next = table[hash]; + table[hash] = key; +} + +keywordHash_t *KeywordHash_Find(keywordHash_t *table[], char *keyword) +{ + keywordHash_t *key; + int hash; + + hash = KeywordHash_Key(keyword); + + for (key = table[hash]; key; key = key->next) + { + if (!Q_stricmp(key->keyword, keyword)) + return key; + } + + return NULL; +} + +/* +=============== +Item_DataType + +Give a numeric representation of which typeData union element this item uses +=============== +*/ +itemDataType_t Item_DataType(itemDef_t *item) +{ + switch (item->type) + { + default: + case ITEM_TYPE_NONE: + return TYPE_NONE; + + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + return TYPE_LIST; + + case ITEM_TYPE_CYCLE: + return TYPE_COMBO; + + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_SAYFIELD: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_TEXT: + return TYPE_EDIT; + + case ITEM_TYPE_MULTI: + return TYPE_MULTI; + + case ITEM_TYPE_MODEL: + return TYPE_MODEL; + } +} + +/* +=============== +Item_IsEditField +=============== +*/ +static ID_INLINE qboolean Item_IsEditField(itemDef_t *item) +{ + switch (item->type) + { + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_SAYFIELD: + return qtrue; + + default: + return qfalse; + } +} + +/* +=============== +Item_IsListBox +=============== +*/ +static ID_INLINE qboolean Item_IsListBox(itemDef_t *item) +{ + switch (item->type) + { + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + return qtrue; + + default: + return qfalse; + } +} + +/* +=============== +Item Keyword Parse functions +=============== +*/ + +// name +qboolean ItemParse_name(itemDef_t *item, int handle) +{ + if (!PC_String_Parse(handle, &item->window.name)) + return qfalse; + + return qtrue; +} + +// name +qboolean ItemParse_focusSound(itemDef_t *item, int handle) +{ + const char *temp; + + if (!PC_String_Parse(handle, &temp)) + return qfalse; + + item->focusSound = DC->registerSound(temp, qfalse); + return qtrue; +} + +// text +qboolean ItemParse_text(itemDef_t *item, int handle) +{ + if (!PC_String_Parse(handle, &item->text)) + return qfalse; + + return qtrue; +} + +// group +qboolean ItemParse_group(itemDef_t *item, int handle) +{ + if (!PC_String_Parse(handle, &item->window.group)) + return qfalse; + + return qtrue; +} + +// asset_model +qboolean ItemParse_asset_model(itemDef_t *item, int handle) +{ + const char *temp; + + if (!PC_String_Parse(handle, &temp)) + return qfalse; + + item->asset = DC->registerModel(temp); + item->typeData.model->angle = rand() % 360; + return qtrue; +} + +// asset_shader +qboolean ItemParse_asset_shader(itemDef_t *item, int handle) +{ + const char *temp; + + if (!PC_String_Parse(handle, &temp)) + return qfalse; + + item->asset = DC->registerShaderNoMip(temp); + return qtrue; +} + +// model_origin +qboolean ItemParse_model_origin(itemDef_t *item, int handle) +{ + return (PC_Float_Parse(handle, &item->typeData.model->origin[0]) && + PC_Float_Parse(handle, &item->typeData.model->origin[1]) && + PC_Float_Parse(handle, &item->typeData.model->origin[2])); +} + +// model_fovx +qboolean ItemParse_model_fovx(itemDef_t *item, int handle) +{ + return PC_Float_Parse(handle, &item->typeData.model->fov_x); +} + +// model_fovy +qboolean ItemParse_model_fovy(itemDef_t *item, int handle) +{ + return PC_Float_Parse(handle, &item->typeData.model->fov_y); +} + +// model_rotation +qboolean ItemParse_model_rotation(itemDef_t *item, int handle) +{ + return PC_Int_Parse(handle, &item->typeData.model->rotationSpeed); +} + +// model_angle +qboolean ItemParse_model_angle(itemDef_t *item, int handle) +{ + return PC_Int_Parse(handle, &item->typeData.model->angle); +} + +// rect +qboolean ItemParse_rect(itemDef_t *item, int handle) +{ + if (!PC_Rect_Parse(handle, &item->window.rectClient)) + return qfalse; + + return qtrue; +} + +// aspectBias +qboolean ItemParse_aspectBias(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->window.aspectBias)) + return qfalse; + + return qtrue; +} + +// style +qboolean ItemParse_style(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->window.style)) + return qfalse; + + return qtrue; +} + +// decoration +qboolean ItemParse_decoration(itemDef_t *item, int handle) +{ + (void)handle; + + item->window.flags |= WINDOW_DECORATION; + return qtrue; +} + +// notselectable +qboolean ItemParse_notselectable(itemDef_t *item, int handle) +{ + (void)handle; + + item->typeData.list->notselectable = qtrue; + return qtrue; +} + +// noscrollbar +qboolean ItemParse_noscrollbar(itemDef_t *item, int handle) +{ + (void)handle; + + item->typeData.list->noscrollbar = qtrue; + return qtrue; +} + +// resetonfeederchange +qboolean ItemParse_resetonfeederchange(itemDef_t *item, int handle) +{ + (void)handle; + + item->typeData.list->resetonfeederchange = qtrue; + return qtrue; +} + +// auto wrapped +qboolean ItemParse_wrapped(itemDef_t *item, int handle) +{ + (void)handle; + + item->window.flags |= WINDOW_WRAPPED; + return qtrue; +} + +// type +qboolean ItemParse_type(itemDef_t *item, int handle) +{ + if (item->type != ITEM_TYPE_NONE) + { + PC_SourceError(handle, "item already has a type"); + return qfalse; + } + + if (!PC_Int_Parse(handle, &item->type)) + return qfalse; + + if (item->type == ITEM_TYPE_NONE) + { + PC_SourceError(handle, "type must not be none"); + return qfalse; + } + + // allocate the relevant type data + switch (item->type) + { + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + item->typeData.list = UI_Alloc(sizeof(listBoxDef_t)); + memset(item->typeData.list, 0, sizeof(listBoxDef_t)); + break; + + case ITEM_TYPE_CYCLE: + item->typeData.cycle = UI_Alloc(sizeof(cycleDef_t)); + memset(item->typeData.cycle, 0, sizeof(cycleDef_t)); + break; + + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_SAYFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_TEXT: + item->typeData.edit = UI_Alloc(sizeof(editFieldDef_t)); + memset(item->typeData.edit, 0, sizeof(editFieldDef_t)); + + if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD) + item->typeData.edit->maxPaintChars = MAX_EDITFIELD; + break; + + case ITEM_TYPE_MULTI: + item->typeData.multi = UI_Alloc(sizeof(multiDef_t)); + memset(item->typeData.multi, 0, sizeof(multiDef_t)); + break; + + case ITEM_TYPE_MODEL: + item->typeData.model = UI_Alloc(sizeof(modelDef_t)); + memset(item->typeData.model, 0, sizeof(modelDef_t)); + break; + + default: + break; + } + + return qtrue; +} + +// elementwidth, used for listbox image elements +qboolean ItemParse_elementwidth(itemDef_t *item, int handle) +{ + return PC_Float_Parse(handle, &item->typeData.list->elementWidth); +} + +// elementheight, used for listbox image elements +qboolean ItemParse_elementheight(itemDef_t *item, int handle) +{ + return PC_Float_Parse(handle, &item->typeData.list->elementHeight); +} + +// dropitems, number of items to drop from a combobox +qboolean ItemParse_dropitems(itemDef_t *item, int handle) +{ + return PC_Int_Parse(handle, &item->typeData.list->dropItems); +} + +// feeder +qboolean ItemParse_feeder(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->feederID)) + return qfalse; + + return qtrue; +} + +// elementtype, used to specify what type of elements a listbox contains +// uses textstyle for storage +qboolean ItemParse_elementtype(itemDef_t *item, int handle) +{ + return PC_Int_Parse(handle, &item->typeData.list->elementStyle); +} + +// columns sets a number of columns and an x pos and width per.. +qboolean ItemParse_columns(itemDef_t *item, int handle) +{ + int i; + + if (!PC_Int_Parse(handle, &item->typeData.list->numColumns)) + return qfalse; + + if (item->typeData.list->numColumns > MAX_LB_COLUMNS) + { + PC_SourceError(handle, "exceeded maximum allowed columns (%d)", MAX_LB_COLUMNS); + return qfalse; + } + + for (i = 0; i < item->typeData.list->numColumns; i++) + { + int pos, width, align; + + if (!PC_Int_Parse(handle, &pos) || !PC_Int_Parse(handle, &width) || !PC_Int_Parse(handle, &align)) + return qfalse; + + item->typeData.list->columnInfo[i].pos = pos; + item->typeData.list->columnInfo[i].width = width; + item->typeData.list->columnInfo[i].align = align; + } + + return qtrue; +} + +qboolean ItemParse_border(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->window.border)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_bordersize(itemDef_t *item, int handle) +{ + if (!PC_Float_Parse(handle, &item->window.borderSize)) + return qfalse; + + return qtrue; +} + +// FIXME: why does this require a parameter? visible MENU_FALSE does nothing +qboolean ItemParse_visible(itemDef_t *item, int handle) +{ + int i; + + if (!PC_Int_Parse(handle, &i)) + return qfalse; + + if (i) + item->window.flags |= WINDOW_VISIBLE; + + return qtrue; +} + +// ownerdraw , implies ITEM_TYPE_OWNERDRAW +qboolean ItemParse_ownerdraw(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->window.ownerDraw)) + return qfalse; + + if (item->type != ITEM_TYPE_NONE && item->type != ITEM_TYPE_OWNERDRAW) + { + PC_SourceError(handle, "ownerdraws cannot have an item type"); + return qfalse; + } + + item->type = ITEM_TYPE_OWNERDRAW; + + return qtrue; +} + +qboolean ItemParse_align(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->alignment)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textalign(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->textalignment)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textvalign(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->textvalignment)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textalignx(itemDef_t *item, int handle) +{ + if (!PC_Float_Parse(handle, &item->textalignx)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textaligny(itemDef_t *item, int handle) +{ + if (!PC_Float_Parse(handle, &item->textaligny)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textscale(itemDef_t *item, int handle) +{ + if (!PC_Float_Parse(handle, &item->textscale)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textstyle(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->textStyle)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_backcolor(itemDef_t *item, int handle) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; + + item->window.backColor[i] = f; + } + + return qtrue; +} + +qboolean ItemParse_forecolor(itemDef_t *item, int handle) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; + + item->window.foreColor[i] = f; + item->window.flags |= WINDOW_FORECOLORSET; + } + + return qtrue; +} + +qboolean ItemParse_bordercolor(itemDef_t *item, int handle) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; + + item->window.borderColor[i] = f; + } + + return qtrue; +} + +qboolean ItemParse_outlinecolor(itemDef_t *item, int handle) +{ + if (!PC_Color_Parse(handle, &item->window.outlineColor)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_background(itemDef_t *item, int handle) +{ + const char *temp; + + if (!PC_String_Parse(handle, &temp)) + return qfalse; + + item->window.background = DC->registerShaderNoMip(temp); + return qtrue; +} + +qboolean ItemParse_cinematic(itemDef_t *item, int handle) +{ + if (!PC_String_Parse(handle, &item->window.cinematicName)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_doubleClick(itemDef_t *item, int handle) +{ + return (item->typeData.list && PC_Script_Parse(handle, &item->typeData.list->doubleClick)); +} + +qboolean ItemParse_onFocus(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->onFocus)) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_leaveFocus(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->leaveFocus)) + return qfalse; - if (!PC_Int_Parse(handle, &i)) { - return qfalse; - } - if (i) { - item->window.flags |= WINDOW_VISIBLE; - } - return qtrue; + return qtrue; } -qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->window.ownerDraw)) { - return qfalse; - } - item->type = ITEM_TYPE_OWNERDRAW; - return qtrue; -} +qboolean ItemParse_mouseEnter(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->mouseEnter)) + return qfalse; -qboolean ItemParse_align( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->alignment)) { - return qfalse; - } - return qtrue; + return qtrue; } -qboolean ItemParse_textalign( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->textalignment)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_mouseExit(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->mouseExit)) + return qfalse; + + return qtrue; } -qboolean ItemParse_textalignx( itemDef_t *item, int handle ) { - if (!PC_Float_Parse(handle, &item->textalignx)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_mouseEnterText(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->mouseEnterText)) + return qfalse; + + return qtrue; } -qboolean ItemParse_textaligny( itemDef_t *item, int handle ) { - if (!PC_Float_Parse(handle, &item->textaligny)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_mouseExitText(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->mouseExitText)) + return qfalse; + + return qtrue; } -qboolean ItemParse_textscale( itemDef_t *item, int handle ) { - if (!PC_Float_Parse(handle, &item->textscale)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_onTextEntry(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->onTextEntry)) + return qfalse; + + return qtrue; } -qboolean ItemParse_textstyle( itemDef_t *item, int handle ) { - if (!PC_Int_Parse(handle, &item->textStyle)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_onCharEntry(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->onCharEntry)) + return qfalse; + + return qtrue; } -qboolean ItemParse_backcolor( itemDef_t *item, int handle ) { - int i; - float f; +qboolean ItemParse_action(itemDef_t *item, int handle) +{ + if (!PC_Script_Parse(handle, &item->action)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - item->window.backColor[i] = f; - } - return qtrue; + return qtrue; } -qboolean ItemParse_forecolor( itemDef_t *item, int handle ) { - int i; - float f; +qboolean ItemParse_cvarTest(itemDef_t *item, int handle) +{ + if (!PC_String_Parse(handle, &item->cvarTest)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - item->window.foreColor[i] = f; - item->window.flags |= WINDOW_FORECOLORSET; - } - return qtrue; + return qtrue; } -qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) { - int i; - float f; +qboolean ItemParse_cvar(itemDef_t *item, int handle) +{ + if (!PC_String_Parse(handle, &item->cvar)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; + if (Item_DataType(item) == TYPE_EDIT) + { + item->typeData.edit->minVal = -1; + item->typeData.edit->maxVal = -1; + item->typeData.edit->defVal = -1; } - item->window.borderColor[i] = f; - } - return qtrue; + + return qtrue; } -qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) { - if (!PC_Color_Parse(handle, &item->window.outlineColor)){ - return qfalse; - } - return qtrue; +qboolean ItemParse_maxChars(itemDef_t *item, int handle) +{ + return PC_Int_Parse(handle, &item->typeData.edit->maxChars); } -qboolean ItemParse_background( itemDef_t *item, int handle ) { - const char *temp; +qboolean ItemParse_maxPaintChars(itemDef_t *item, int handle) +{ + return PC_Int_Parse(handle, &item->typeData.edit->maxPaintChars); +} - if (!PC_String_Parse(handle, &temp)) { - return qfalse; - } - item->window.background = DC->registerShaderNoMip(temp); - return qtrue; +qboolean ItemParse_maxFieldWidth(itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->typeData.edit->maxFieldWidth)) + return qfalse; + + if (item->typeData.edit->maxFieldWidth < MIN_FIELD_WIDTH) + { + PC_SourceError(handle, "max field width must be at least %d", MIN_FIELD_WIDTH); + return qfalse; + } + + return qtrue; } -qboolean ItemParse_cinematic( itemDef_t *item, int handle ) { - if (!PC_String_Parse(handle, &item->window.cinematicName)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_cvarFloat(itemDef_t *item, int handle) +{ + return (PC_String_Parse(handle, &item->cvar) && PC_Float_Parse(handle, &item->typeData.edit->defVal) && + PC_Float_Parse(handle, &item->typeData.edit->minVal) && + PC_Float_Parse(handle, &item->typeData.edit->maxVal)); } -qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) { - listBoxDef_t *listPtr; +qboolean ItemParse_cvarStrList(itemDef_t *item, int handle) +{ + pc_token_t token; + multiDef_t *multiPtr; + int pass; + + multiPtr = item->typeData.multi; + multiPtr->count = 0; + multiPtr->strDef = qtrue; - Item_ValidateTypeData(item); - if (!item->typeData) { - return qfalse; - } + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; - listPtr = (listBoxDef_t*)item->typeData; + if (*token.string != '{') + return qfalse; - if (!PC_Script_Parse(handle, &listPtr->doubleClick)) { - return qfalse; - } - return qtrue; -} + pass = 0; -qboolean ItemParse_onFocus( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->onFocus)) { - return qfalse; - } - return qtrue; -} + while (1) + { + if (!trap_Parse_ReadToken(handle, &token)) + { + PC_SourceError(handle, "end of file inside menu item\n"); + return qfalse; + } + + if (*token.string == '}') + return qtrue; + + if (*token.string == ',' || *token.string == ';') + continue; + + if (pass == 0) + { + multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string); + pass = 1; + } + else + { + multiPtr->cvarStr[multiPtr->count] = String_Alloc(token.string); + pass = 0; + multiPtr->count++; + + if (multiPtr->count >= MAX_MULTI_CVARS) + { + PC_SourceError(handle, "cvar string list may not exceed %d cvars", MAX_MULTI_CVARS); + return qfalse; + } + } + } -qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->leaveFocus)) { return qfalse; - } - return qtrue; } -qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->mouseEnter)) { +qboolean ItemParse_cvarFloatList(itemDef_t *item, int handle) +{ + pc_token_t token; + multiDef_t *multiPtr; + + multiPtr = item->typeData.multi; + multiPtr->count = 0; + multiPtr->strDef = qfalse; + + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + if (*token.string != '{') + return qfalse; + + while (1) + { + if (!trap_Parse_ReadToken(handle, &token)) + { + PC_SourceError(handle, "end of file inside menu item\n"); + return qfalse; + } + + if (*token.string == '}') + return qtrue; + + if (*token.string == ',' || *token.string == ';') + continue; + + multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string); + + if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count])) + return qfalse; + + multiPtr->count++; + + if (multiPtr->count >= MAX_MULTI_CVARS) + { + PC_SourceError(handle, "cvar string list may not exceed %d cvars", MAX_MULTI_CVARS); + return qfalse; + } + } + return qfalse; - } - return qtrue; } -qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->mouseExit)) { +qboolean ItemParse_addColorRange(itemDef_t *item, int handle) +{ + colorRangeDef_t color; + + if (PC_Float_Parse(handle, &color.low) && PC_Float_Parse(handle, &color.high) && + PC_Color_Parse(handle, &color.color)) + { + if (item->numColors < MAX_COLOR_RANGES) + { + memcpy(&item->colorRanges[item->numColors], &color, sizeof(color)); + item->numColors++; + } + else + { + PC_SourceError(handle, "may not exceed %d color ranges", MAX_COLOR_RANGES); + return qfalse; + } + + return qtrue; + } + return qfalse; - } - return qtrue; } -qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->mouseEnterText)) { - return qfalse; - } - return qtrue; +qboolean ItemParse_ownerdrawFlag(itemDef_t *item, int handle) +{ + int i; + + if (!PC_Int_Parse(handle, &i)) + return qfalse; + + item->window.ownerDrawFlags |= i; + return qtrue; } -qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->mouseExitText)) { +qboolean ItemParse_enableCvar(itemDef_t *item, int handle) +{ + if (PC_Script_Parse(handle, &item->enableCvar)) + { + item->cvarFlags = CVAR_ENABLE; + return qtrue; + } + return qfalse; - } - return qtrue; } -qboolean ItemParse_onTextEntry( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->onTextEntry)) { +qboolean ItemParse_disableCvar(itemDef_t *item, int handle) +{ + if (PC_Script_Parse(handle, &item->enableCvar)) + { + item->cvarFlags = CVAR_DISABLE; + return qtrue; + } + return qfalse; - } - return qtrue; } -qboolean ItemParse_action( itemDef_t *item, int handle ) { - if (!PC_Script_Parse(handle, &item->action)) { +qboolean ItemParse_showCvar(itemDef_t *item, int handle) +{ + if (PC_Script_Parse(handle, &item->enableCvar)) + { + item->cvarFlags = CVAR_SHOW; + return qtrue; + } + return qfalse; - } - return qtrue; } -qboolean ItemParse_special( itemDef_t *item, int handle ) { - if (!PC_Float_Parse(handle, &item->special)) { +qboolean ItemParse_hideCvar(itemDef_t *item, int handle) +{ + if (PC_Script_Parse(handle, &item->enableCvar)) + { + item->cvarFlags = CVAR_HIDE; + return qtrue; + } + return qfalse; - } - return qtrue; } -qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) { - if (!PC_String_Parse(handle, &item->cvarTest)) { - return qfalse; - } - return qtrue; +keywordHash_t itemParseKeywords[] = {{"name", ItemParse_name, TYPE_ANY, NULL}, {"type", ItemParse_type, TYPE_ANY, NULL}, + {"text", ItemParse_text, TYPE_ANY, NULL}, {"group", ItemParse_group, TYPE_ANY, NULL}, + {"asset_model", ItemParse_asset_model, TYPE_MODEL, NULL}, + {"asset_shader", ItemParse_asset_shader, TYPE_ANY, NULL}, // ? + {"model_origin", ItemParse_model_origin, TYPE_MODEL, NULL}, {"model_fovx", ItemParse_model_fovx, TYPE_MODEL, NULL}, + {"model_fovy", ItemParse_model_fovy, TYPE_MODEL, NULL}, + {"model_rotation", ItemParse_model_rotation, TYPE_MODEL, NULL}, + {"model_angle", ItemParse_model_angle, TYPE_MODEL, NULL}, {"rect", ItemParse_rect, TYPE_ANY, NULL}, + {"aspectBias", ItemParse_aspectBias, TYPE_ANY, NULL}, {"style", ItemParse_style, TYPE_ANY, NULL}, + {"decoration", ItemParse_decoration, TYPE_ANY, NULL}, {"notselectable", ItemParse_notselectable, TYPE_LIST, NULL}, + {"noscrollbar", ItemParse_noscrollbar, TYPE_LIST, NULL}, + {"resetonfeederchange", ItemParse_resetonfeederchange, TYPE_LIST, NULL}, + {"wrapped", ItemParse_wrapped, TYPE_ANY, NULL}, {"elementwidth", ItemParse_elementwidth, TYPE_LIST, NULL}, + {"elementheight", ItemParse_elementheight, TYPE_LIST, NULL}, {"dropitems", ItemParse_dropitems, TYPE_LIST, NULL}, + {"feeder", ItemParse_feeder, TYPE_ANY, NULL}, {"elementtype", ItemParse_elementtype, TYPE_LIST, NULL}, + {"columns", ItemParse_columns, TYPE_LIST, NULL}, {"border", ItemParse_border, TYPE_ANY, NULL}, + {"bordersize", ItemParse_bordersize, TYPE_ANY, NULL}, {"visible", ItemParse_visible, TYPE_ANY, NULL}, + {"ownerdraw", ItemParse_ownerdraw, TYPE_ANY, NULL}, {"align", ItemParse_align, TYPE_ANY, NULL}, + {"textalign", ItemParse_textalign, TYPE_ANY, NULL}, {"textvalign", ItemParse_textvalign, TYPE_ANY, NULL}, + {"textalignx", ItemParse_textalignx, TYPE_ANY, NULL}, {"textaligny", ItemParse_textaligny, TYPE_ANY, NULL}, + {"textscale", ItemParse_textscale, TYPE_ANY, NULL}, {"textstyle", ItemParse_textstyle, TYPE_ANY, NULL}, + {"backcolor", ItemParse_backcolor, TYPE_ANY, NULL}, {"forecolor", ItemParse_forecolor, TYPE_ANY, NULL}, + {"bordercolor", ItemParse_bordercolor, TYPE_ANY, NULL}, {"outlinecolor", ItemParse_outlinecolor, TYPE_ANY, NULL}, + {"background", ItemParse_background, TYPE_ANY, NULL}, {"onFocus", ItemParse_onFocus, TYPE_ANY, NULL}, + {"leaveFocus", ItemParse_leaveFocus, TYPE_ANY, NULL}, {"mouseEnter", ItemParse_mouseEnter, TYPE_ANY, NULL}, + {"mouseExit", ItemParse_mouseExit, TYPE_ANY, NULL}, {"mouseEnterText", ItemParse_mouseEnterText, TYPE_ANY, NULL}, + {"mouseExitText", ItemParse_mouseExitText, TYPE_ANY, NULL}, {"onTextEntry", ItemParse_onTextEntry, TYPE_ANY, NULL}, + {"onCharEntry", ItemParse_onCharEntry, TYPE_ANY, NULL}, {"action", ItemParse_action, TYPE_ANY, NULL}, + {"cvar", ItemParse_cvar, TYPE_ANY, NULL}, {"maxChars", ItemParse_maxChars, TYPE_EDIT, NULL}, + {"maxPaintChars", ItemParse_maxPaintChars, TYPE_EDIT, NULL}, + {"maxFieldWidth", ItemParse_maxFieldWidth, TYPE_EDIT, NULL}, {"focusSound", ItemParse_focusSound, TYPE_ANY, NULL}, + {"cvarFloat", ItemParse_cvarFloat, TYPE_EDIT, NULL}, {"cvarStrList", ItemParse_cvarStrList, TYPE_MULTI, NULL}, + {"cvarFloatList", ItemParse_cvarFloatList, TYPE_MULTI, NULL}, + {"addColorRange", ItemParse_addColorRange, TYPE_ANY, NULL}, + {"ownerdrawFlag", ItemParse_ownerdrawFlag, TYPE_ANY, NULL}, // hm. + {"enableCvar", ItemParse_enableCvar, TYPE_ANY, NULL}, {"cvarTest", ItemParse_cvarTest, TYPE_ANY, NULL}, + {"disableCvar", ItemParse_disableCvar, TYPE_ANY, NULL}, {"showCvar", ItemParse_showCvar, TYPE_ANY, NULL}, + {"hideCvar", ItemParse_hideCvar, TYPE_ANY, NULL}, {"cinematic", ItemParse_cinematic, TYPE_ANY, NULL}, + {"doubleclick", ItemParse_doubleClick, TYPE_LIST, NULL}, {NULL, voidFunction2, 0, NULL}}; + +keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Item_SetupKeywordHash +=============== +*/ +void Item_SetupKeywordHash(void) +{ + int i; + + memset(itemParseKeywordHash, 0, sizeof(itemParseKeywordHash)); + + for (i = 0; itemParseKeywords[i].keyword; i++) + KeywordHash_Add(itemParseKeywordHash, &itemParseKeywords[i]); } -qboolean ItemParse_cvar( itemDef_t *item, int handle ) { - editFieldDef_t *editPtr; +/* +=============== +Item_Parse +=============== +*/ +qboolean Item_Parse(int handle, itemDef_t *item) +{ + pc_token_t token; + keywordHash_t *key; + + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; + + if (*token.string != '{') + return qfalse; - Item_ValidateTypeData(item); - if (!PC_String_Parse(handle, &item->cvar)) { - return qfalse; - } - if (item->typeData) { - editPtr = (editFieldDef_t*)item->typeData; - editPtr->minVal = -1; - editPtr->maxVal = -1; - editPtr->defVal = -1; - } - return qtrue; -} - -qboolean ItemParse_maxChars( itemDef_t *item, int handle ) { - editFieldDef_t *editPtr; - int maxChars; - - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; + while (1) + { + if (!trap_Parse_ReadToken(handle, &token)) + { + PC_SourceError(handle, "end of file inside menu item\n"); + return qfalse; + } + + if (*token.string == '}') + return qtrue; + + key = KeywordHash_Find(itemParseKeywordHash, token.string); + + if (!key) + { + PC_SourceError(handle, "unknown menu item keyword %s", token.string); + continue; + } + + // do type-checks + if (key->param != TYPE_ANY) + { + itemDataType_t test = Item_DataType(item); + + if (test != key->param) + { + if (test == TYPE_NONE) + PC_SourceError(handle, + "menu item keyword %s requires " + "type specification", + token.string); + else + PC_SourceError(handle, + "menu item keyword %s is incompatible with " + "specified item type", + token.string); + continue; + } + } + + if (!key->func(item, handle)) + { + PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string); + return qfalse; + } + } - if (!PC_Int_Parse(handle, &maxChars)) { return qfalse; - } - editPtr = (editFieldDef_t*)item->typeData; - editPtr->maxChars = maxChars; - return qtrue; } -qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) { - editFieldDef_t *editPtr; - int maxChars; +// Item_InitControls +// init's special control types +void Item_InitControls(itemDef_t *item) +{ + if (item == NULL) + return; - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; + if (Item_IsListBox(item)) + { + item->cursorPos = 0; - if (!PC_Int_Parse(handle, &maxChars)) { - return qfalse; - } - editPtr = (editFieldDef_t*)item->typeData; - editPtr->maxPaintChars = maxChars; - return qtrue; + if (item->typeData.list) + { + item->typeData.list->cursorPos = 0; + Item_ListBox_SetStartPos(item, 0); + item->typeData.list->cursorPos = 0; + } + } } +/* +=============== +Menu Keyword Parse functions +=============== +*/ +qboolean MenuParse_font(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) { - editFieldDef_t *editPtr; + if (!PC_String_Parse(handle, &menu->font)) + return qfalse; + + if (!DC->Assets.fontRegistered) + { + DC->registerFont(menu->font, 48, &DC->Assets.textFont); + DC->Assets.fontRegistered = qtrue; + } - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; - editPtr = (editFieldDef_t*)item->typeData; - if (PC_String_Parse(handle, &item->cvar) && - PC_Float_Parse(handle, &editPtr->defVal) && - PC_Float_Parse(handle, &editPtr->minVal) && - PC_Float_Parse(handle, &editPtr->maxVal)) { return qtrue; - } - return qfalse; } -qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) { - pc_token_t token; - multiDef_t *multiPtr; - int pass; +qboolean MenuParse_name(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; - multiPtr = (multiDef_t*)item->typeData; - multiPtr->count = 0; - multiPtr->strDef = qtrue; + if (!PC_String_Parse(handle, &menu->window.name)) + return qfalse; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (*token.string != '{') { - return qfalse; - } + return qtrue; +} - pass = 0; - while ( 1 ) { - if (!trap_Parse_ReadToken(handle, &token)) { - PC_SourceError(handle, "end of file inside menu item\n"); - return qfalse; - } +qboolean MenuParse_fullscreen(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - if (*token.string == '}') { - return qtrue; - } + if (!PC_Int_Parse(handle, (int *)&menu->fullScreen)) + return qfalse; - if (*token.string == ',' || *token.string == ';') { - continue; - } + return qtrue; +} + +qboolean MenuParse_rect(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - if (pass == 0) { - multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string); - pass = 1; - } else { - multiPtr->cvarStr[multiPtr->count] = String_Alloc(token.string); - pass = 0; - multiPtr->count++; - if (multiPtr->count >= MAX_MULTI_CVARS) { + if (!PC_Rect_Parse(handle, &menu->window.rect)) return qfalse; - } - } - } - return qfalse; // bk001205 - LCC missing return value + return qtrue; } -qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) { - pc_token_t token; - multiDef_t *multiPtr; +qboolean MenuParse_aspectBias(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - Item_ValidateTypeData(item); - if (!item->typeData) - return qfalse; - multiPtr = (multiDef_t*)item->typeData; - multiPtr->count = 0; - multiPtr->strDef = qfalse; + if (!PC_Int_Parse(handle, &menu->window.aspectBias)) + return qfalse; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (*token.string != '{') { - return qfalse; - } + return qtrue; +} - while ( 1 ) { - if (!trap_Parse_ReadToken(handle, &token)) { - PC_SourceError(handle, "end of file inside menu item\n"); - return qfalse; - } +qboolean MenuParse_style(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - if (*token.string == '}') { - return qtrue; - } + if (!PC_Int_Parse(handle, &menu->window.style)) + return qfalse; - if (*token.string == ',' || *token.string == ';') { - continue; - } + return qtrue; +} - multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string); - if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count])) { - return qfalse; - } +qboolean MenuParse_visible(itemDef_t *item, int handle) +{ + int i; + menuDef_t *menu = (menuDef_t *)item; - multiPtr->count++; - if (multiPtr->count >= MAX_MULTI_CVARS) { - return qfalse; - } + if (!PC_Int_Parse(handle, &i)) + return qfalse; - } - return qfalse; // bk001205 - LCC missing return value + if (i) + menu->window.flags |= WINDOW_VISIBLE; + + return qtrue; } +qboolean MenuParse_onOpen(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; + + if (!PC_Script_Parse(handle, &menu->onOpen)) + return qfalse; + + return qtrue; +} +qboolean MenuParse_onClose(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) { - colorRangeDef_t color; + if (!PC_Script_Parse(handle, &menu->onClose)) + return qfalse; - if (PC_Float_Parse(handle, &color.low) && - PC_Float_Parse(handle, &color.high) && - PC_Color_Parse(handle, &color.color) ) { - if (item->numColors < MAX_COLOR_RANGES) { - memcpy(&item->colorRanges[item->numColors], &color, sizeof(color)); - item->numColors++; - } return qtrue; - } - return qfalse; } -qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) { - int i; - if (!PC_Int_Parse(handle, &i)) { - return qfalse; - } - item->window.ownerDrawFlags |= i; - return qtrue; -} +qboolean MenuParse_onESC(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) { - if (PC_Script_Parse(handle, &item->enableCvar)) { - item->cvarFlags = CVAR_ENABLE; - return qtrue; - } - return qfalse; -} + if (!PC_Script_Parse(handle, &menu->onESC)) + return qfalse; -qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) { - if (PC_Script_Parse(handle, &item->enableCvar)) { - item->cvarFlags = CVAR_DISABLE; return qtrue; - } - return qfalse; } -qboolean ItemParse_showCvar( itemDef_t *item, int handle ) { - if (PC_Script_Parse(handle, &item->enableCvar)) { - item->cvarFlags = CVAR_SHOW; - return qtrue; - } - return qfalse; -} +qboolean MenuParse_border(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) { - if (PC_Script_Parse(handle, &item->enableCvar)) { - item->cvarFlags = CVAR_HIDE; - return qtrue; - } - return qfalse; -} - - -keywordHash_t itemParseKeywords[] = { - {"name", ItemParse_name, NULL}, - {"text", ItemParse_text, NULL}, - {"group", ItemParse_group, NULL}, - {"asset_model", ItemParse_asset_model, NULL}, - {"asset_shader", ItemParse_asset_shader, NULL}, - {"model_origin", ItemParse_model_origin, NULL}, - {"model_fovx", ItemParse_model_fovx, NULL}, - {"model_fovy", ItemParse_model_fovy, NULL}, - {"model_rotation", ItemParse_model_rotation, NULL}, - {"model_angle", ItemParse_model_angle, NULL}, - {"rect", ItemParse_rect, NULL}, - {"style", ItemParse_style, NULL}, - {"decoration", ItemParse_decoration, NULL}, - {"notselectable", ItemParse_notselectable, NULL}, - {"wrapped", ItemParse_wrapped, NULL}, - {"autowrapped", ItemParse_autowrapped, NULL}, - {"horizontalscroll", ItemParse_horizontalscroll, NULL}, - {"type", ItemParse_type, NULL}, - {"elementwidth", ItemParse_elementwidth, NULL}, - {"elementheight", ItemParse_elementheight, NULL}, - {"feeder", ItemParse_feeder, NULL}, - {"elementtype", ItemParse_elementtype, NULL}, - {"columns", ItemParse_columns, NULL}, - {"border", ItemParse_border, NULL}, - {"bordersize", ItemParse_bordersize, NULL}, - {"visible", ItemParse_visible, NULL}, - {"ownerdraw", ItemParse_ownerdraw, NULL}, - {"align", ItemParse_align, NULL}, - {"textalign", ItemParse_textalign, NULL}, - {"textalignx", ItemParse_textalignx, NULL}, - {"textaligny", ItemParse_textaligny, NULL}, - {"textscale", ItemParse_textscale, NULL}, - {"textstyle", ItemParse_textstyle, NULL}, - {"backcolor", ItemParse_backcolor, NULL}, - {"forecolor", ItemParse_forecolor, NULL}, - {"bordercolor", ItemParse_bordercolor, NULL}, - {"outlinecolor", ItemParse_outlinecolor, NULL}, - {"background", ItemParse_background, NULL}, - {"onFocus", ItemParse_onFocus, NULL}, - {"leaveFocus", ItemParse_leaveFocus, NULL}, - {"mouseEnter", ItemParse_mouseEnter, NULL}, - {"mouseExit", ItemParse_mouseExit, NULL}, - {"mouseEnterText", ItemParse_mouseEnterText, NULL}, - {"mouseExitText", ItemParse_mouseExitText, NULL}, - {"onTextEntry", ItemParse_onTextEntry, NULL}, - {"action", ItemParse_action, NULL}, - {"special", ItemParse_special, NULL}, - {"cvar", ItemParse_cvar, NULL}, - {"maxChars", ItemParse_maxChars, NULL}, - {"maxPaintChars", ItemParse_maxPaintChars, NULL}, - {"focusSound", ItemParse_focusSound, NULL}, - {"cvarFloat", ItemParse_cvarFloat, NULL}, - {"cvarStrList", ItemParse_cvarStrList, NULL}, - {"cvarFloatList", ItemParse_cvarFloatList, NULL}, - {"addColorRange", ItemParse_addColorRange, NULL}, - {"ownerdrawFlag", ItemParse_ownerdrawFlag, NULL}, - {"enableCvar", ItemParse_enableCvar, NULL}, - {"cvarTest", ItemParse_cvarTest, NULL}, - {"disableCvar", ItemParse_disableCvar, NULL}, - {"showCvar", ItemParse_showCvar, NULL}, - {"hideCvar", ItemParse_hideCvar, NULL}, - {"cinematic", ItemParse_cinematic, NULL}, - {"doubleclick", ItemParse_doubleClick, NULL}, - {NULL, voidFunction2, NULL} -}; + if (!PC_Int_Parse(handle, &menu->window.border)) + return qfalse; -keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE]; + return qtrue; +} -/* -=============== -Item_SetupKeywordHash -=============== -*/ -void Item_SetupKeywordHash( void ) +qboolean MenuParse_borderSize(itemDef_t *item, int handle) { - int i; + menuDef_t *menu = (menuDef_t *)item; - memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) ); + if (!PC_Float_Parse(handle, &menu->window.borderSize)) + return qfalse; - for( i = 0; itemParseKeywords[ i ].keyword; i++ ) - KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] ); + return qtrue; } -/* -=============== -Item_Parse -=============== -*/ -qboolean Item_Parse(int handle, itemDef_t *item) { - pc_token_t token; - keywordHash_t *key; - +qboolean MenuParse_backcolor(itemDef_t *item, int handle) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t *)item; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (*token.string != '{') { - return qfalse; - } - while ( 1 ) { - if (!trap_Parse_ReadToken(handle, &token)) { - PC_SourceError(handle, "end of file inside menu item\n"); - return qfalse; - } + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; - if (*token.string == '}') { - return qtrue; + menu->window.backColor[i] = f; } - key = KeywordHash_Find(itemParseKeywordHash, token.string); - if (!key) { - PC_SourceError(handle, "unknown menu item keyword %s", token.string); - continue; - } - if ( !key->func(item, handle) ) { - PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string); - return qfalse; - } - } - return qfalse; // bk001205 - LCC missing return value + return qtrue; } +qboolean MenuParse_forecolor(itemDef_t *item, int handle) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t *)item; -// Item_InitControls -// init's special control types -void Item_InitControls(itemDef_t *item) { - if (item == NULL) { - return; - } - if (item->type == ITEM_TYPE_LISTBOX) { - listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - item->cursorPos = 0; - if (listPtr) { - listPtr->cursorPos = 0; - listPtr->startPos = 0; - listPtr->endPos = 0; - listPtr->cursorPos = 0; - } - } -} + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; -/* -=============== -Menu Keyword Parse functions -=============== -*/ + menu->window.foreColor[i] = f; + menu->window.flags |= WINDOW_FORECOLORSET; + } -qboolean MenuParse_font( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_String_Parse(handle, &menu->font)) { - return qfalse; - } - if (!DC->Assets.fontRegistered) { - DC->registerFont(menu->font, 48, &DC->Assets.textFont); - DC->Assets.fontRegistered = qtrue; - } - return qtrue; + return qtrue; } -qboolean MenuParse_name( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_String_Parse(handle, &menu->window.name)) { - return qfalse; - } - if (Q_stricmp(menu->window.name, "main") == 0) { - // default main as having focus - //menu->window.flags |= WINDOW_HASFOCUS; - } - return qtrue; -} +qboolean MenuParse_bordercolor(itemDef_t *item, int handle) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t *)item; -qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Int_Parse(handle, (int*) &menu->fullScreen)) { // bk001206 - cast qboolean - return qfalse; - } - return qtrue; -} + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; -qboolean MenuParse_rect( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Rect_Parse(handle, &menu->window.rect)) { - return qfalse; - } - return qtrue; -} + menu->window.borderColor[i] = f; + } -qboolean MenuParse_style( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Int_Parse(handle, &menu->window.style)) { - return qfalse; - } - return qtrue; + return qtrue; } -qboolean MenuParse_visible( itemDef_t *item, int handle ) { - int i; - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_focuscolor(itemDef_t *item, int handle) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t *)item; - if (!PC_Int_Parse(handle, &i)) { - return qfalse; - } - if (i) { - menu->window.flags |= WINDOW_VISIBLE; - } - return qtrue; -} + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; -qboolean MenuParse_onOpen( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Script_Parse(handle, &menu->onOpen)) { - return qfalse; - } - return qtrue; -} + menu->focusColor[i] = f; + } -qboolean MenuParse_onClose( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Script_Parse(handle, &menu->onClose)) { - return qfalse; - } - return qtrue; + return qtrue; } -qboolean MenuParse_onESC( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Script_Parse(handle, &menu->onESC)) { - return qfalse; - } - return qtrue; -} +qboolean MenuParse_disablecolor(itemDef_t *item, int handle) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t *)item; + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + return qfalse; + menu->disableColor[i] = f; + } -qboolean MenuParse_border( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Int_Parse(handle, &menu->window.border)) { - return qfalse; - } - return qtrue; + return qtrue; } -qboolean MenuParse_borderSize( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Float_Parse(handle, &menu->window.borderSize)) { - return qfalse; - } - return qtrue; -} +qboolean MenuParse_outlinecolor(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -qboolean MenuParse_backcolor( itemDef_t *item, int handle ) { - int i; - float f; - menuDef_t *menu = (menuDef_t*)item; + if (!PC_Color_Parse(handle, &menu->window.outlineColor)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - menu->window.backColor[i] = f; - } - return qtrue; + return qtrue; } -qboolean MenuParse_forecolor( itemDef_t *item, int handle ) { - int i; - float f; - menuDef_t *menu = (menuDef_t*)item; - - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - menu->window.foreColor[i] = f; - menu->window.flags |= WINDOW_FORECOLORSET; - } - return qtrue; -} +qboolean MenuParse_background(itemDef_t *item, int handle) +{ + const char *buff; + menuDef_t *menu = (menuDef_t *)item; -qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) { - int i; - float f; - menuDef_t *menu = (menuDef_t*)item; + if (!PC_String_Parse(handle, &buff)) + return qfalse; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - menu->window.borderColor[i] = f; - } - return qtrue; + menu->window.background = DC->registerShaderNoMip(buff); + return qtrue; } -qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) { - int i; - float f; - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_cinematic(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - menu->focusColor[i] = f; - } - return qtrue; -} + if (!PC_String_Parse(handle, &menu->window.cinematicName)) + return qfalse; -qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) { - int i; - float f; - menuDef_t *menu = (menuDef_t*)item; - for (i = 0; i < 4; i++) { - if (!PC_Float_Parse(handle, &f)) { - return qfalse; - } - menu->disableColor[i] = f; - } - return qtrue; + return qtrue; } +qboolean MenuParse_ownerdrawFlag(itemDef_t *item, int handle) +{ + int i; + menuDef_t *menu = (menuDef_t *)item; -qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (!PC_Color_Parse(handle, &menu->window.outlineColor)){ - return qfalse; - } - return qtrue; -} - -qboolean MenuParse_background( itemDef_t *item, int handle ) { - const char *buff; - menuDef_t *menu = (menuDef_t*)item; + if (!PC_Int_Parse(handle, &i)) + return qfalse; - if (!PC_String_Parse(handle, &buff)) { - return qfalse; - } - menu->window.background = DC->registerShaderNoMip(buff); - return qtrue; + menu->window.ownerDrawFlags |= i; + return qtrue; } -qboolean MenuParse_cinematic( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_ownerdraw(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; + + if (!PC_Int_Parse(handle, &menu->window.ownerDraw)) + return qfalse; - if (!PC_String_Parse(handle, &menu->window.cinematicName)) { - return qfalse; - } - return qtrue; + return qtrue; } -qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) { - int i; - menuDef_t *menu = (menuDef_t*)item; +// decoration +qboolean MenuParse_popup(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; + menu->window.flags |= WINDOW_POPUP; - if (!PC_Int_Parse(handle, &i)) { - return qfalse; - } - menu->window.ownerDrawFlags |= i; - return qtrue; + (void)handle; + + return qtrue; } -qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_outOfBounds(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; + menu->window.flags |= WINDOW_OOB_CLICK; + + (void)handle; - if (!PC_Int_Parse(handle, &menu->window.ownerDraw)) { - return qfalse; - } - return qtrue; + return qtrue; } +qboolean MenuParse_soundLoop(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -// decoration -qboolean MenuParse_popup( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - menu->window.flags |= WINDOW_POPUP; - return qtrue; + if (!PC_String_Parse(handle, &menu->soundName)) + return qfalse; + + return qtrue; } +qboolean MenuParse_fadeClamp(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; -qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; + if (!PC_Float_Parse(handle, &menu->fadeClamp)) + return qfalse; - menu->window.flags |= WINDOW_OOB_CLICK; - return qtrue; + return qtrue; } -qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_fadeAmount(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - if (!PC_String_Parse(handle, &menu->soundName)) { - return qfalse; - } - return qtrue; + if (!PC_Float_Parse(handle, &menu->fadeAmount)) + return qfalse; + + return qtrue; } -qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_fadeCycle(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; - if (!PC_Float_Parse(handle, &menu->fadeClamp)) { - return qfalse; - } - return qtrue; + if (!PC_Int_Parse(handle, &menu->fadeCycle)) + return qfalse; + + return qtrue; } -qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; +qboolean MenuParse_itemDef(itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t *)item; + + if (menu->itemCount < MAX_MENUITEMS) + { + menu->items[menu->itemCount] = UI_Alloc(sizeof(itemDef_t)); + Item_Init(menu->items[menu->itemCount]); - if (!PC_Float_Parse(handle, &menu->fadeAmount)) { - return qfalse; - } - return qtrue; -} + if (!Item_Parse(handle, menu->items[menu->itemCount])) + return qfalse; + Item_InitControls(menu->items[menu->itemCount]); + menu->items[menu->itemCount++]->parent = menu; + } + else + { + PC_SourceError(handle, "itemDefs per menu may not exceed %d", MAX_MENUITEMS); + return qfalse; + } -qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; + return qtrue; +} - if (!PC_Int_Parse(handle, &menu->fadeCycle)) { - return qfalse; - } - return qtrue; -} - - -qboolean MenuParse_itemDef( itemDef_t *item, int handle ) { - menuDef_t *menu = (menuDef_t*)item; - if (menu->itemCount < MAX_MENUITEMS) { - menu->items[menu->itemCount] = UI_Alloc(sizeof(itemDef_t)); - Item_Init(menu->items[menu->itemCount]); - if (!Item_Parse(handle, menu->items[menu->itemCount])) { - return qfalse; - } - Item_InitControls(menu->items[menu->itemCount]); - menu->items[menu->itemCount++]->parent = menu; - } - return qtrue; -} - -keywordHash_t menuParseKeywords[] = { - {"font", MenuParse_font, NULL}, - {"name", MenuParse_name, NULL}, - {"fullscreen", MenuParse_fullscreen, NULL}, - {"rect", MenuParse_rect, NULL}, - {"style", MenuParse_style, NULL}, - {"visible", MenuParse_visible, NULL}, - {"onOpen", MenuParse_onOpen, NULL}, - {"onClose", MenuParse_onClose, NULL}, - {"onESC", MenuParse_onESC, NULL}, - {"border", MenuParse_border, NULL}, - {"borderSize", MenuParse_borderSize, NULL}, - {"backcolor", MenuParse_backcolor, NULL}, - {"forecolor", MenuParse_forecolor, NULL}, - {"bordercolor", MenuParse_bordercolor, NULL}, - {"focuscolor", MenuParse_focuscolor, NULL}, - {"disablecolor", MenuParse_disablecolor, NULL}, - {"outlinecolor", MenuParse_outlinecolor, NULL}, - {"background", MenuParse_background, NULL}, - {"ownerdraw", MenuParse_ownerdraw, NULL}, - {"ownerdrawFlag", MenuParse_ownerdrawFlag, NULL}, - {"outOfBoundsClick", MenuParse_outOfBounds, NULL}, - {"soundLoop", MenuParse_soundLoop, NULL}, - {"itemDef", MenuParse_itemDef, NULL}, - {"cinematic", MenuParse_cinematic, NULL}, - {"popup", MenuParse_popup, NULL}, - {"fadeClamp", MenuParse_fadeClamp, NULL}, - {"fadeCycle", MenuParse_fadeCycle, NULL}, - {"fadeAmount", MenuParse_fadeAmount, NULL}, - {NULL, voidFunction2, NULL} -}; +keywordHash_t menuParseKeywords[] = {{"font", MenuParse_font, 0, NULL}, {"name", MenuParse_name, 0, NULL}, + {"fullscreen", MenuParse_fullscreen, 0, NULL}, {"rect", MenuParse_rect, 0, NULL}, + {"aspectBias", MenuParse_aspectBias, 0, NULL}, {"style", MenuParse_style, 0, NULL}, + {"visible", MenuParse_visible, 0, NULL}, {"onOpen", MenuParse_onOpen, 0, NULL}, + {"onClose", MenuParse_onClose, 0, NULL}, {"onESC", MenuParse_onESC, 0, NULL}, {"border", MenuParse_border, 0, NULL}, + {"borderSize", MenuParse_borderSize, 0, NULL}, {"backcolor", MenuParse_backcolor, 0, NULL}, + {"forecolor", MenuParse_forecolor, 0, NULL}, {"bordercolor", MenuParse_bordercolor, 0, NULL}, + {"focuscolor", MenuParse_focuscolor, 0, NULL}, {"disablecolor", MenuParse_disablecolor, 0, NULL}, + {"outlinecolor", MenuParse_outlinecolor, 0, NULL}, {"background", MenuParse_background, 0, NULL}, + {"ownerdraw", MenuParse_ownerdraw, 0, NULL}, {"ownerdrawFlag", MenuParse_ownerdrawFlag, 0, NULL}, + {"outOfBoundsClick", MenuParse_outOfBounds, 0, NULL}, {"soundLoop", MenuParse_soundLoop, 0, NULL}, + {"itemDef", MenuParse_itemDef, 0, NULL}, {"cinematic", MenuParse_cinematic, 0, NULL}, + {"popup", MenuParse_popup, 0, NULL}, {"fadeClamp", MenuParse_fadeClamp, 0, NULL}, + {"fadeCycle", MenuParse_fadeCycle, 0, NULL}, {"fadeAmount", MenuParse_fadeAmount, 0, NULL}, + {NULL, voidFunction2, 0, NULL}}; keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; @@ -5872,14 +7515,14 @@ keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; Menu_SetupKeywordHash =============== */ -void Menu_SetupKeywordHash( void ) +void Menu_SetupKeywordHash(void) { - int i; + int i; - memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) ); + memset(menuParseKeywordHash, 0, sizeof(menuParseKeywordHash)); - for(i = 0; menuParseKeywords[ i ].keyword; i++ ) - KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] ); + for (i = 0; menuParseKeywords[i].keyword; i++) + KeywordHash_Add(menuParseKeywordHash, &menuParseKeywords[i]); } /* @@ -5887,39 +7530,46 @@ void Menu_SetupKeywordHash( void ) Menu_Parse =============== */ -qboolean Menu_Parse(int handle, menuDef_t *menu) { - pc_token_t token; - keywordHash_t *key; +qboolean Menu_Parse(int handle, menuDef_t *menu) +{ + pc_token_t token; + keywordHash_t *key; - if (!trap_Parse_ReadToken(handle, &token)) - return qfalse; - if (*token.string != '{') { - return qfalse; - } + if (!trap_Parse_ReadToken(handle, &token)) + return qfalse; - while ( 1 ) { + if (*token.string != '{') + return qfalse; - memset(&token, 0, sizeof(pc_token_t)); - if (!trap_Parse_ReadToken(handle, &token)) { - PC_SourceError(handle, "end of file inside menu\n"); - return qfalse; - } + while (1) + { + memset(&token, 0, sizeof(pc_token_t)); - if (*token.string == '}') { - return qtrue; - } + if (!trap_Parse_ReadToken(handle, &token)) + { + PC_SourceError(handle, "end of file inside menu\n"); + return qfalse; + } - key = KeywordHash_Find(menuParseKeywordHash, token.string); - if (!key) { - PC_SourceError(handle, "unknown menu keyword %s", token.string); - continue; - } - if ( !key->func((itemDef_t*)menu, handle) ) { - PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); - return qfalse; + if (*token.string == '}') + return qtrue; + + key = KeywordHash_Find(menuParseKeywordHash, token.string); + + if (!key) + { + PC_SourceError(handle, "unknown menu keyword %s", token.string); + continue; + } + + if (!key->func((itemDef_t *)menu, handle)) + { + PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); + return qfalse; + } } - } - return qfalse; // bk001205 - LCC missing return value + + return qfalse; } /* @@ -5927,189 +7577,211 @@ qboolean Menu_Parse(int handle, menuDef_t *menu) { Menu_New =============== */ -void Menu_New(int handle) { - menuDef_t *menu = &Menus[menuCount]; +void Menu_New(int handle) +{ + menuDef_t *menu = &Menus[menuCount]; + + if (menuCount < MAX_MENUS) + { + Menu_Init(menu); - if (menuCount < MAX_MENUS) { - Menu_Init(menu); - if (Menu_Parse(handle, menu)) { - Menu_PostParse(menu); - menuCount++; + if (Menu_Parse(handle, menu)) + { + Menu_PostParse(menu); + menuCount++; + } } - } } -int Menu_Count( void ) { - return menuCount; +int Menu_Count(void) { return menuCount; } + +void Menu_UpdateAll(void) +{ + int i; + + for (i = 0; i < openMenuCount; i++) + Menu_Update(menuStack[i]); } -void Menu_PaintAll( void ) { - int i; +void Menu_PaintAll(void) +{ + int i; - if( g_editingField || g_waitingForKey ) - DC->setCVar( "ui_hideCursor", "1" ); - else - DC->setCVar( "ui_hideCursor", "0" ); + if (g_editingField || g_waitingForKey) + DC->setCVar("ui_hideCursor", "1"); + else + DC->setCVar("ui_hideCursor", "0"); - if (captureFunc) { - captureFunc(captureData); - } + if (captureFunc != voidFunction) + { + if (captureFuncExpiry > 0 && DC->realTime > captureFuncExpiry) + UI_RemoveCaptureFunc(); + else + captureFunc(captureData); + } - for (i = 0; i < Menu_Count(); i++) { - Menu_Paint(&Menus[i], qfalse); - } + for (i = 0; i < openMenuCount; i++) + Menu_Paint(menuStack[i], qfalse); - if (debugMode) { - vec4_t v = {1, 1, 1, 1}; - DC->drawText(5, 25, .5, v, va("fps: %f", DC->FPS), 0, 0, 0); - } + if (DC->getCVarValue("ui_developer")) + { + vec4_t v = {1, 1, 1, 1}; + UI_Text_Paint(5, 25, .5, v, va("fps: %f", DC->FPS), 0, 0, 0); + } } -void Menu_Reset( void ) -{ - menuCount = 0; -} +void Menu_Reset(void) { menuCount = 0; } -displayContextDef_t *Display_GetContext( void ) { - return DC; -} +displayContextDef_t *Display_GetContext(void) { return DC; } -void *Display_CaptureItem(int x, int y) { - int i; +void *Display_CaptureItem(int x, int y) +{ + int i; - for (i = 0; i < menuCount; i++) { - // turn off focus each item - // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; - if (Rect_ContainsPoint(&Menus[i].window.rect, x, y)) { - return &Menus[i]; + for (i = 0; i < menuCount; i++) + { + if (Rect_ContainsPoint(&Menus[i].window.rect, x, y)) + return &Menus[i]; } - } - return NULL; -} + return NULL; +} // FIXME: -qboolean Display_MouseMove(void *p, int x, int y) { - int i; - menuDef_t *menu = p; - - if (menu == NULL) { - menu = Menu_GetFocused(); - if (menu) { - if (menu->window.flags & WINDOW_POPUP) { - Menu_HandleMouseMove(menu, x, y); - return qtrue; - } - } - for (i = 0; i < menuCount; i++) { - Menu_HandleMouseMove(&Menus[i], x, y); - } - } else { - menu->window.rect.x += x; - menu->window.rect.y += y; - Menu_UpdatePosition(menu); - } - return qtrue; +qboolean Display_MouseMove(void *p, float x, float y) +{ + int i; + menuDef_t *menu = p; -} + if (menu == NULL) + { + menu = Menu_GetFocused(); + + if (menu) + { + if (menu->window.flags & WINDOW_POPUP) + { + Menu_HandleMouseMove(menu, x, y); + return qtrue; + } + } -int Display_CursorType(int x, int y) { - int i; - for (i = 0; i < menuCount; i++) { - rectDef_t r2; - r2.x = Menus[i].window.rect.x - 3; - r2.y = Menus[i].window.rect.y - 3; - r2.w = r2.h = 7; - if (Rect_ContainsPoint(&r2, x, y)) { - return CURSOR_SIZER; + for (i = 0; i < menuCount; i++) + Menu_HandleMouseMove(&Menus[i], x, y); + } + else + { + menu->window.rect.x += x; + menu->window.rect.y += y; + Menu_UpdatePosition(menu); } - } - return CURSOR_ARROW; + + return qtrue; } +int Display_CursorType(int x, int y) +{ + int i; -void Display_HandleKey(int key, qboolean down, int x, int y) { - menuDef_t *menu = Display_CaptureItem(x, y); - if (menu == NULL) { - menu = Menu_GetFocused(); - } - if (menu) { - Menu_HandleKey(menu, key, down ); - } -} + for (i = 0; i < menuCount; i++) + { + rectDef_t r2; + r2.x = Menus[i].window.rect.x - 3; + r2.y = Menus[i].window.rect.y - 3; + r2.w = r2.h = 7; -static void Window_CacheContents(windowDef_t *window) { - if (window) { - if (window->cinematicName) { - int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0); - DC->stopCinematic(cin); + if (Rect_ContainsPoint(&r2, x, y)) + return CURSOR_SIZER; } - } + + return CURSOR_ARROW; } +void Display_HandleKey(int key, qboolean down, int x, int y) +{ + menuDef_t *menu = Display_CaptureItem(x, y); -static void Item_CacheContents(itemDef_t *item) { - if (item) { - Window_CacheContents(&item->window); - } + if (menu == NULL) + menu = Menu_GetFocused(); + if (menu) + Menu_HandleKey(menu, key, down); } -static void Menu_CacheContents(menuDef_t *menu) { - if (menu) { - int i; - Window_CacheContents(&menu->window); - for (i = 0; i < menu->itemCount; i++) { - Item_CacheContents(menu->items[i]); - } - - if (menu->soundName && *menu->soundName) { - DC->registerSound(menu->soundName, qfalse); +static void Window_CacheContents(Window *window) +{ + if (window) + { + if (window->cinematicName) + { + int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0); + DC->stopCinematic(cin); + } } - } - } -void Display_CacheAll( void ) { - int i; - for (i = 0; i < menuCount; i++) { - Menu_CacheContents(&Menus[i]); - } +static void Item_CacheContents(itemDef_t *item) +{ + if (item) + Window_CacheContents(&item->window); } +static void Menu_CacheContents(menuDef_t *menu) +{ + if (menu) + { + int i; + Window_CacheContents(&menu->window); + + for (i = 0; i < menu->itemCount; i++) + Item_CacheContents(menu->items[i]); -static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) { - if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)) { - if (Rect_ContainsPoint(&menu->window.rect, x, y)) { - int i; - for (i = 0; i < menu->itemCount; i++) { - // turn off focus each item - // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + if (menu->soundName && *menu->soundName) + DC->registerSound(menu->soundName, qfalse); + } +} - if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { - continue; - } +void Display_CacheAll(void) +{ + int i; - if (menu->items[i]->window.flags & WINDOW_DECORATION) { - continue; - } + for (i = 0; i < menuCount; i++) + Menu_CacheContents(&Menus[i]); +} - if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { - itemDef_t *overItem = menu->items[i]; - if (overItem->type == ITEM_TYPE_TEXT && overItem->text) { - if (Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) { - return qtrue; - } else { - continue; +static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) +{ + if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)) + { + if (Rect_ContainsPoint(&menu->window.rect, x, y)) + { + int i; + + for (i = 0; i < menu->itemCount; i++) + { + if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) + continue; + + if (menu->items[i]->window.flags & WINDOW_DECORATION) + continue; + + if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) + { + itemDef_t *overItem = menu->items[i]; + + if (overItem->type == ITEM_TYPE_TEXT && overItem->text) + { + if (Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) + return qtrue; + else + continue; + } + else + return qtrue; + } } - } else { - return qtrue; - } } - } - } - } - return qfalse; -} + return qfalse; +} diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h index 210899e..470792c 100644 --- a/src/ui/ui_shared.h +++ b/src/ui/ui_shared.h @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,20 +17,19 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ #ifndef __UI_SHARED_H #define __UI_SHARED_H +#include "client/keycodes.h" +#include "qcommon/q_shared.h" +#include "renderercommon/tr_types.h" -#include "../qcommon/q_shared.h" -#include "../renderer/tr_types.h" -#include "../client/keycodes.h" - -#include "../ui/menudef.h" +#include "../../assets/ui/menudef.h" #define MAX_MENUNAME 32 #define MAX_ITEMTEXT 64 @@ -41,118 +41,128 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MAX_COLOR_RANGES 10 #define MAX_OPEN_MENUS 16 -#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive -#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive -#define WINDOW_VISIBLE 0x00000004 // is visible -#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active ) -#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. -#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active -#define WINDOW_FADINGIN 0x00000040 // fading in -#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive -#define WINDOW_INTRANSITION 0x00000100 // window is in transition -#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not ) -#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal -#define WINDOW_LB_LEFTARROW 0x00000800 // mouse is over left/up arrow -#define WINDOW_LB_RIGHTARROW 0x00001000 // mouse is over right/down arrow -#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb -#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up -#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down -#define WINDOW_ORBITING 0x00010000 // item is in orbit -#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click -#define WINDOW_WRAPPED 0x00040000 // manually wrap text -#define WINDOW_AUTOWRAPPED 0x00080000 // auto wrap text -#define WINDOW_FORCED 0x00100000 // forced open -#define WINDOW_POPUP 0x00200000 // popup -#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set -#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented ) - +#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive +#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive +#define WINDOW_VISIBLE 0x00000004 // is visible +#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active ) +#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. +#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active +#define WINDOW_FADINGIN 0x00000040 // fading in +#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive +#define WINDOW_INTRANSITION 0x00000100 // window is in transition +#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not ) +#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal +#define WINDOW_LB_UPARROW 0x00000800 // mouse is over up arrow +#define WINDOW_LB_DOWNARROW 0x00001000 // mouse is over down arrow +#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb +#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up +#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down +#define WINDOW_ORBITING 0x00010000 // item is in orbit +#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click +#define WINDOW_WRAPPED 0x00080000 // wrap text +#define WINDOW_FORCED 0x00100000 // forced open +#define WINDOW_POPUP 0x00200000 // popup +#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set +#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented ) // CGAME cursor type bits -#define CURSOR_NONE 0x00000001 -#define CURSOR_ARROW 0x00000002 -#define CURSOR_SIZER 0x00000004 +#define CURSOR_NONE 0x00000001 +#define CURSOR_ARROW 0x00000002 +#define CURSOR_SIZER 0x00000004 #ifdef CGAME -#define STRING_POOL_SIZE 128*1024 +#define STRING_POOL_SIZE 128 * 1024 #else -#define STRING_POOL_SIZE 384*1024 +#define STRING_POOL_SIZE 384 * 1024 #endif #define MAX_STRING_HANDLES 4096 #define MAX_SCRIPT_ARGS 12 #define MAX_EDITFIELD 256 +#define ITEM_VALUE_OFFSET 8 -#define ART_FX_BASE "menu/art/fx_base" -#define ART_FX_BLUE "menu/art/fx_blue" -#define ART_FX_CYAN "menu/art/fx_cyan" -#define ART_FX_GREEN "menu/art/fx_grn" -#define ART_FX_RED "menu/art/fx_red" -#define ART_FX_TEAL "menu/art/fx_teal" -#define ART_FX_WHITE "menu/art/fx_white" -#define ART_FX_YELLOW "menu/art/fx_yel" +#define ART_FX_BASE "menu/art/fx_base" +#define ART_FX_BLUE "menu/art/fx_blue" +#define ART_FX_CYAN "menu/art/fx_cyan" +#define ART_FX_GREEN "menu/art/fx_grn" +#define ART_FX_RED "menu/art/fx_red" +#define ART_FX_TEAL "menu/art/fx_teal" +#define ART_FX_WHITE "menu/art/fx_white" +#define ART_FX_YELLOW "menu/art/fx_yel" #define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga" -#define ASSET_SCROLLBAR "ui/assets/scrollbar.tga" -#define ASSET_SCROLLBAR_ARROWDOWN "ui/assets/scrollbar_arrow_dwn_a.tga" -#define ASSET_SCROLLBAR_ARROWUP "ui/assets/scrollbar_arrow_up_a.tga" -#define ASSET_SCROLLBAR_ARROWLEFT "ui/assets/scrollbar_arrow_left.tga" -#define ASSET_SCROLLBAR_ARROWRIGHT "ui/assets/scrollbar_arrow_right.tga" -#define ASSET_SCROLL_THUMB "ui/assets/scrollbar_thumb.tga" -#define ASSET_SLIDER_BAR "ui/assets/slider2.tga" -#define ASSET_SLIDER_THUMB "ui/assets/sliderbutt_1.tga" -#define SCROLLBAR_SIZE 16.0 -#define SLIDER_WIDTH 96.0 -#define SLIDER_HEIGHT 16.0 -#define SLIDER_THUMB_WIDTH 12.0 -#define SLIDER_THUMB_HEIGHT 20.0 -#define NUM_CROSSHAIRS 10 +#define ASSET_SCROLLBAR "ui/assets/scrollbar.tga" +#define ASSET_SCROLLBAR_ARROWDOWN "ui/assets/scrollbar_arrow_dwn_a.tga" +#define ASSET_SCROLLBAR_ARROWUP "ui/assets/scrollbar_arrow_up_a.tga" +#define ASSET_SCROLLBAR_ARROWLEFT "ui/assets/scrollbar_arrow_left.tga" +#define ASSET_SCROLLBAR_ARROWRIGHT "ui/assets/scrollbar_arrow_right.tga" +#define ASSET_SCROLL_THUMB "ui/assets/scrollbar_thumb.tga" +#define ASSET_SLIDER_BAR "ui/assets/slider2.tga" +#define ASSET_SLIDER_THUMB "ui/assets/sliderbutt_1.tga" + +#define SCROLLBAR_ARROW_SIZE 16.0f +#define SCROLLBAR_ARROW_WIDTH (SCROLLBAR_ARROW_SIZE * DC->aspectScale) +#define SCROLLBAR_ARROW_HEIGHT SCROLLBAR_ARROW_SIZE +#define SCROLLBAR_SLIDER_X(_item) \ + (_item->window.rect.x + _item->window.rect.w - SCROLLBAR_ARROW_WIDTH - DC->aspectScale) +#define SCROLLBAR_SLIDER_Y(_item) (SCROLLBAR_Y(_item) + SCROLLBAR_ARROW_HEIGHT) +#define SCROLLBAR_SLIDER_HEIGHT(_item) (_item->window.rect.h - (SCROLLBAR_ARROW_HEIGHT * 2.0f) - 2.0f) +#define SCROLLBAR_X(_item) (_item->window.rect.x + DC->aspectScale) +#define SCROLLBAR_Y(_item) (_item->window.rect.y + 1.0f) +#define SCROLLBAR_W(_item) (SCROLLBAR_SLIDER_X(_item) - SCROLLBAR_X(_item)) +#define SCROLLBAR_H(_item) (_item->window.rect.h - 2.0f) + +#define SLIDER_WIDTH (96.0f * DC->aspectScale) +#define SLIDER_HEIGHT 16.0f +#define SLIDER_THUMB_WIDTH (12.0f * DC->aspectScale) +#define SLIDER_THUMB_HEIGHT 20.0f +#define NUM_CROSSHAIRS 10 typedef struct { - const char *command; - const char *args[MAX_SCRIPT_ARGS]; + const char *command; + const char *args[MAX_SCRIPT_ARGS]; } scriptDef_t; - typedef struct { - float x; // horiz position - float y; // vert position - float w; // width - float h; // height; + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; } rectDef_t; typedef rectDef_t Rectangle; // FIXME: do something to separate text vs window stuff + typedef struct { - Rectangle rect; // client coord rectangle - Rectangle rectClient; // screen coord rectangle - const char *name; // - const char *group; // if it belongs to a group - const char *cinematicName; // cinematic name - int cinematic; // cinematic handle - int style; // - int border; // - int ownerDraw; // ownerDraw style - int ownerDrawFlags; // show flags for ownerdraw items - float borderSize; // - int flags; // visible, focus, mouseover, cursor - Rectangle rectEffects; // for various effects - Rectangle rectEffects2; // for various effects - int offsetTime; // time based value for various effects - int nextTime; // time next effect should cycle - vec4_t foreColor; // text color - vec4_t backColor; // border color - vec4_t borderColor; // border color - vec4_t outlineColor; // border color - qhandle_t background; // background asset -} windowDef_t; - -typedef windowDef_t Window; + Rectangle rect; // client coord rectangle + int aspectBias; // direction in which to aspect compensate + Rectangle rectClient; // screen coord rectangle + const char *name; // + const char *group; // if it belongs to a group + const char *cinematicName; // cinematic name + int cinematic; // cinematic handle + int style; // + int border; // + int ownerDraw; // ownerDraw style + int ownerDrawFlags; // show flags for ownerdraw items + float borderSize; // + int flags; // visible, focus, mouseover, cursor + Rectangle rectEffects; // for various effects + Rectangle rectEffects2; // for various effects + int offsetTime; // time based value for various effects + int nextTime; // time next effect should cycle + vec4_t foreColor; // text color + vec4_t backColor; // border color + vec4_t borderColor; // border color + vec4_t outlineColor; // border color + qhandle_t background; // background asset +} Window; typedef struct { - vec4_t color; - float low; - float high; + vec4_t color; + float low; + float high; } colorRangeDef_t; // FIXME: combine flags into bitfields to save space @@ -168,231 +178,259 @@ typedef struct { #define MAX_LB_COLUMNS 16 typedef struct columnInfo_s { - int pos; - int width; - int maxChars; - int align; + int pos; + int width; + int align; } columnInfo_t; typedef struct listBoxDef_s { - int startPos; - int endPos; - int drawPadding; - int cursorPos; - float elementWidth; - float elementHeight; - int elementStyle; - int numColumns; - columnInfo_t columnInfo[MAX_LB_COLUMNS]; - const char *doubleClick; - qboolean notselectable; + int startPos; + int endPos; + int cursorPos; + + float elementWidth; + float elementHeight; + int elementStyle; + int dropItems; + + int numColumns; + columnInfo_t columnInfo[MAX_LB_COLUMNS]; + + const char *doubleClick; + + qboolean notselectable; + qboolean noscrollbar; + + qboolean resetonfeederchange; + int lastFeederCount; } listBoxDef_t; +typedef struct cycleDef_s { + int cursorPos; +} cycleDef_t; + typedef struct editFieldDef_s { - float minVal; // edit field limits - float maxVal; // - float defVal; // - float range; // - int maxChars; // for edit fields - int maxPaintChars; // for edit fields - int paintOffset; // + float minVal; // edit field limits + float maxVal; // + float defVal; // + float range; // + int maxChars; // for edit fields + int maxPaintChars; // for edit fields + int maxFieldWidth; // for edit fields + int paintOffset; // } editFieldDef_t; #define MAX_MULTI_CVARS 32 typedef struct multiDef_s { - const char *cvarList[MAX_MULTI_CVARS]; - const char *cvarStr[MAX_MULTI_CVARS]; - float cvarValue[MAX_MULTI_CVARS]; - int count; - qboolean strDef; + const char *cvarList[MAX_MULTI_CVARS]; + const char *cvarStr[MAX_MULTI_CVARS]; + float cvarValue[MAX_MULTI_CVARS]; + int count; + qboolean strDef; } multiDef_t; typedef struct modelDef_s { - int angle; - vec3_t origin; - float fov_x; - float fov_y; - int rotationSpeed; + int angle; + vec3_t origin; + float fov_x; + float fov_y; + int rotationSpeed; } modelDef_t; -#define CVAR_ENABLE 0x00000001 -#define CVAR_DISABLE 0x00000002 -#define CVAR_SHOW 0x00000004 -#define CVAR_HIDE 0x00000008 +#define CVAR_ENABLE 0x00000001 +#define CVAR_DISABLE 0x00000002 +#define CVAR_SHOW 0x00000004 +#define CVAR_HIDE 0x00000008 + +typedef enum { TYPE_ANY = -1, TYPE_NONE, TYPE_LIST, TYPE_EDIT, TYPE_MULTI, TYPE_COMBO, TYPE_MODEL } itemDataType_t; typedef struct itemDef_s { - Window window; // common positional, border, style, layout info - Rectangle textRect; // rectangle the text ( if any ) consumes - int type; // text, button, radiobutton, checkbox, textfield, listbox, combo - int alignment; // left center right - int textalignment; // ( optional ) alignment for text within rect based on text width - float textalignx; // ( optional ) text alignment x coord - float textaligny; // ( optional ) text alignment x coord - float textscale; // scale percentage from 72pts - int textStyle; // ( optional ) style, normal and shadowed are it for now - const char *text; // display text - void *parent; // menu owner - qhandle_t asset; // handle to asset - const char *mouseEnterText; // mouse enter script - const char *mouseExitText; // mouse exit script - const char *mouseEnter; // mouse enter script - const char *mouseExit; // mouse exit script - const char *action; // select script - const char *onFocus; // select script - const char *leaveFocus; // select script - const char *onTextEntry; // called when text entered - const char *cvar; // associated cvar - const char *cvarTest; // associated cvar for enable actions - const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list - int cvarFlags; // what type of action to take on cvarenables - sfxHandle_t focusSound; - int numColors; // number of color ranges - colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; - float special; // used for feeder id's etc.. diff per type - int cursorPos; // cursor position in characters - void *typeData; // type specific data ptr's + Window window; // common positional, border, style, layout info + Rectangle textRect; // rectangle the text ( if any ) consumes + int type; // text, button, radiobutton, checkbox, textfield, listbox, combo + int alignment; // left center right + int textalignment; // ( optional ) alignment for text within rect based on text width + int textvalignment; // ( optional ) alignment for text within rect based on text width + float textalignx; // ( optional ) text alignment x coord + float textaligny; // ( optional ) text alignment x coord + float textscale; // scale percentage from 72pts + int textStyle; // ( optional ) style, normal and shadowed are it for now + const char *text; // display text + void *parent; // menu owner + qhandle_t asset; // handle to asset + const char *mouseEnterText; // mouse enter script + const char *mouseExitText; // mouse exit script + const char *mouseEnter; // mouse enter script + const char *mouseExit; // mouse exit script + const char *action; // select script + const char *onFocus; // select script + const char *leaveFocus; // select script + const char *onTextEntry; // called when text entered + const char *onCharEntry; // called when text entered + const char *cvar; // associated cvar + const char *cvarTest; // associated cvar for enable actions + const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list + int cvarFlags; // what type of action to take on cvarenables + sfxHandle_t focusSound; + int numColors; // number of color ranges + colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; + int feederID; // where to get data for this item + int cursorPos; // cursor position in characters + union { + void *data; + listBoxDef_t *list; + editFieldDef_t *edit; + multiDef_t *multi; + cycleDef_t *cycle; + modelDef_t *model; + } typeData; // type specific data pointers } itemDef_t; typedef struct { - Window window; - const char *font; // font - qboolean fullScreen; // covers entire screen - int itemCount; // number of items; - int fontIndex; // - int cursorItem; // which item as the cursor - int fadeCycle; // - float fadeClamp; // - float fadeAmount; // - const char *onOpen; // run when the menu is first opened - const char *onClose; // run when the menu is closed - const char *onESC; // run when the menu is closed - const char *soundName; // background loop sound for menu - - vec4_t focusColor; // focus color for items - vec4_t disableColor; // focus color for items - itemDef_t *items[MAX_MENUITEMS]; // items this menu contains + Window window; + const char *font; // font + qboolean fullScreen; // covers entire screen + int itemCount; // number of items; + int fontIndex; // + int cursorItem; // which item as the cursor + int fadeCycle; // + float fadeClamp; // + float fadeAmount; // + const char *onOpen; // run when the menu is first opened + const char *onClose; // run when the menu is closed + const char *onESC; // run when the menu is closed + const char *soundName; // background loop sound for menu + + vec4_t focusColor; // focus color for items + vec4_t disableColor; // focus color for items + itemDef_t *items[MAX_MENUITEMS]; // items this menu contains } menuDef_t; typedef struct { - const char *fontStr; - const char *cursorStr; - const char *gradientStr; - fontInfo_t textFont; - fontInfo_t smallFont; - fontInfo_t bigFont; - qhandle_t cursor; - qhandle_t gradientBar; - qhandle_t scrollBarArrowUp; - qhandle_t scrollBarArrowDown; - qhandle_t scrollBarArrowLeft; - qhandle_t scrollBarArrowRight; - qhandle_t scrollBar; - qhandle_t scrollBarThumb; - qhandle_t buttonMiddle; - qhandle_t buttonInside; - qhandle_t solidBox; - qhandle_t sliderBar; - qhandle_t sliderThumb; - sfxHandle_t menuEnterSound; - sfxHandle_t menuExitSound; - sfxHandle_t menuBuzzSound; - sfxHandle_t itemFocusSound; - float fadeClamp; - int fadeCycle; - float fadeAmount; - float shadowX; - float shadowY; - vec4_t shadowColor; - float shadowFadeClamp; - qboolean fontRegistered; - + const char *fontStr; + const char *cursorStr; + const char *gradientStr; + fontInfo_t textFont; + fontInfo_t smallFont; + fontInfo_t bigFont; + qhandle_t cursor; + qhandle_t gradientBar; + qhandle_t scrollBarArrowUp; + qhandle_t scrollBarArrowDown; + qhandle_t scrollBarArrowLeft; + qhandle_t scrollBarArrowRight; + qhandle_t scrollBar; + qhandle_t scrollBarThumb; + qhandle_t buttonMiddle; + qhandle_t buttonInside; + qhandle_t solidBox; + qhandle_t sliderBar; + qhandle_t sliderThumb; + sfxHandle_t menuEnterSound; + sfxHandle_t menuExitSound; + sfxHandle_t menuBuzzSound; + sfxHandle_t itemFocusSound; + float fadeClamp; + int fadeCycle; + float fadeAmount; + float shadowX; + float shadowY; + vec4_t shadowColor; + float shadowFadeClamp; + qboolean fontRegistered; + emoticon_t emoticons[MAX_EMOTICONS]; + int emoticonCount; } cachedAssets_t; typedef struct { - const char *name; - void (*handler) (itemDef_t *item, char** args); + const char *name; + void (*handler)(itemDef_t *item, char **args); } commandDef_t; typedef struct { - qhandle_t (*registerShaderNoMip) (const char *p); - void (*setColor) (const vec4_t v); - void (*drawHandlePic) (float x, float y, float w, float h, qhandle_t asset); - void (*drawStretchPic) (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); - void (*drawText) (float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); - int (*textWidth) (const char *text, float scale, int limit); - int (*textHeight) (const char *text, float scale, int limit); - qhandle_t (*registerModel) (const char *p); - void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max); - void (*fillRect) ( float x, float y, float w, float h, const vec4_t color); - void (*drawRect) ( float x, float y, float w, float h, float size, const vec4_t color); - void (*drawSides) (float x, float y, float w, float h, float size); - void (*drawTopBottom) (float x, float y, float w, float h, float size); - void (*clearScene) (void); - void (*addRefEntityToScene) (const refEntity_t *re ); - void (*renderScene) ( const refdef_t *fd ); - void (*registerFont) (const char *pFontname, int pointSize, fontInfo_t *font); - void (*ownerDrawItem) (float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); - float (*getValue) (int ownerDraw); - qboolean (*ownerDrawVisible) (int flags); - void (*runScript)(char **p); - void (*getTeamColor)(vec4_t *color); - void (*getCVarString)(const char *cvar, char *buffer, int bufsize); - float (*getCVarValue)(const char *cvar); - void (*setCVar)(const char *cvar, const char *value); - void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style); - void (*setOverstrikeMode)(qboolean b); - qboolean (*getOverstrikeMode)( void ); - void (*startLocalSound)( sfxHandle_t sfx, int channelNum ); - qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key); - int (*feederCount)(float feederID); - const char *(*feederItemText)(float feederID, int index, int column, qhandle_t *handle); - qhandle_t (*feederItemImage)(float feederID, int index); - void (*feederSelection)(float feederID, int index); - void (*keynumToStringBuf)( int keynum, char *buf, int buflen ); - void (*getBindingBuf)( int keynum, char *buf, int buflen ); - void (*setBinding)( int keynum, const char *binding ); - void (*executeText)(int exec_when, const char *text ); - void (*Error)(int level, const char *error, ...); - void (*Print)(const char *msg, ...); - void (*Pause)(qboolean b); - int (*ownerDrawWidth)(int ownerDraw, float scale); - sfxHandle_t (*registerSound)(const char *name, qboolean compressed); - void (*startBackgroundTrack)( const char *intro, const char *loop); - void (*stopBackgroundTrack)( void ); - int (*playCinematic)(const char *name, float x, float y, float w, float h); - void (*stopCinematic)(int handle); - void (*drawCinematic)(int handle, float x, float y, float w, float h); - void (*runCinematicFrame)(int handle); - - float yscale; - float xscale; - float bias; - int realTime; - int frameTime; - int cursorx; - int cursory; - qboolean debug; - - cachedAssets_t Assets; - - glconfig_t glconfig; - qhandle_t whiteShader; - qhandle_t gradientImage; - qhandle_t cursor; - float FPS; + qhandle_t (*registerShaderNoMip)(const char *p); + void (*setColor)(const vec4_t v); + void (*drawHandlePic)(float x, float y, float w, float h, qhandle_t asset); + void (*drawStretchPic)( + float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader); + qhandle_t (*registerModel)(const char *p); + void (*modelBounds)(qhandle_t model, vec3_t min, vec3_t max); + void (*fillRect)(float x, float y, float w, float h, const vec4_t color); + void (*drawRect)(float x, float y, float w, float h, float size, const vec4_t color); + void (*drawSides)(float x, float y, float w, float h, float size); + void (*drawTopBottom)(float x, float y, float w, float h, float size); + void (*clearScene)(void); + void (*addRefEntityToScene)(const refEntity_t *re); + void (*renderScene)(const refdef_t *fd); + void (*registerFont)(const char *pFontname, int pointSize, fontInfo_t *font); + void (*ownerDrawItem)(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, + int ownerDrawFlags, int align, int textalign, int textvalign, float borderSize, float scale, vec4_t foreColor, + vec4_t backColor, qhandle_t shader, int textStyle); + float (*getValue)(int ownerDraw); + qboolean (*ownerDrawVisible)(int flags); + void (*runScript)(char **p); + void (*getCVarString)(const char *cvar, char *buffer, int bufsize); + float (*getCVarValue)(const char *cvar); + void (*setCVar)(const char *cvar, const char *value); + void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, + char cursor, int limit, int style); + void (*setOverstrikeMode)(qboolean b); + qboolean (*getOverstrikeMode)(void); + void (*startLocalSound)(sfxHandle_t sfx, int channelNum); + qboolean (*ownerDrawHandleKey)(int ownerDraw, int key); + int (*feederCount)(int feederID); + const char *(*feederItemText)(int feederID, int index, int column, qhandle_t *handle); + qhandle_t (*feederItemImage)(int feederID, int index); + void (*feederSelection)(int feederID, int index); + int (*feederInitialise)(int feederID); + void (*keynumToStringBuf)(int keynum, char *buf, int buflen); + void (*getBindingBuf)(int keynum, char *buf, int buflen); + void (*setBinding)(int keynum, const char *binding); + void (*executeText)(int exec_when, const char *text); + void (*Error)(int level, const char *error, ...) __attribute__((noreturn, format(printf, 2, 3))); + void (*Print)(const char *msg, ...) __attribute__((format(printf, 1, 2))); + void (*Pause)(qboolean b); + int (*ownerDrawWidth)(int ownerDraw, float scale); + const char *(*ownerDrawText)(int ownerDraw); + sfxHandle_t (*registerSound)(const char *name, qboolean compressed); + void (*startBackgroundTrack)(const char *intro, const char *loop); + void (*stopBackgroundTrack)(void); + int (*playCinematic)(const char *name, float x, float y, float w, float h); + void (*stopCinematic)(int handle); + void (*drawCinematic)(int handle, float x, float y, float w, float h); + void (*runCinematicFrame)(int handle); + + float yscale; + float xscale; + float aspectScale; + int realTime; + int frameTime; + float cursorx; + float cursory; + float smallFontScale; + float bigFontScale; + qboolean debug; + + cachedAssets_t Assets; + + glconfig_t glconfig; + qhandle_t whiteShader; + qhandle_t gradientImage; + qhandle_t cursor; + float FPS; } displayContextDef_t; const char *String_Alloc(const char *p); -void String_Init( void ); -void String_Report( void ); +void String_Init(void); +void String_Report(void); void Init_Display(displayContextDef_t *dc); -void Display_ExpandMacros(char * buff); +void Display_ExpandMacros(char *buff); void Menu_Init(menuDef_t *menu); void Item_Init(itemDef_t *item); void Menu_PostParse(menuDef_t *menu); -menuDef_t *Menu_GetFocused( void ); +menuDef_t *Menu_GetFocused(void); void Menu_HandleKey(menuDef_t *menu, int key, qboolean down); void Menu_HandleMouseMove(menuDef_t *menu, float x, float y); void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down); @@ -408,48 +446,69 @@ qboolean PC_Int_Parse(int handle, int *i); qboolean PC_Rect_Parse(int handle, rectDef_t *r); qboolean PC_String_Parse(int handle, const char **out); qboolean PC_Script_Parse(int handle, const char **out); -int Menu_Count( void ); +int Menu_Count(void); void Menu_New(int handle); -void Menu_PaintAll( void ); +void Menu_UpdateAll(void); +void Menu_PaintAll(void); menuDef_t *Menus_ActivateByName(const char *p); -void Menu_Reset( void ); -qboolean Menus_AnyFullScreenVisible( void ); -void Menus_Activate(menuDef_t *menu); +menuDef_t *Menus_ReplaceActiveByName(const char *p); +void Menu_Reset(void); +qboolean Menus_AnyFullScreenVisible(void); +void Menus_Activate(menuDef_t *menu); +qboolean Menus_ReplaceActive(menuDef_t *menu); -displayContextDef_t *Display_GetContext( void ); +displayContextDef_t *Display_GetContext(void); void *Display_CaptureItem(int x, int y); -qboolean Display_MouseMove(void *p, int x, int y); +qboolean Display_MouseMove(void *p, float x, float y); int Display_CursorType(int x, int y); -qboolean Display_KeyBindPending( void ); -void Menus_OpenByName(const char *p); +qboolean Display_KeyBindPending(void); menuDef_t *Menus_FindByName(const char *p); -void Menus_ShowByName(const char *p); void Menus_CloseByName(const char *p); void Display_HandleKey(int key, qboolean down, int x, int y); void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); -void Menus_CloseAll( void ); +void Menus_CloseAll(void); +void Menu_Update(menuDef_t *menu); void Menu_Paint(menuDef_t *menu, qboolean forcePaint); void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name); -void Display_CacheAll( void ); - -void *UI_Alloc( int size ); -void UI_InitMemory( void ); -qboolean UI_OutOfMemory( void ); +void Display_CacheAll(void); -void Controls_GetConfig( void ); -void Controls_SetConfig(qboolean restart); -void Controls_SetDefaults( void ); +typedef void(CaptureFunc)(void *p); -//for cg_draw.c -void Item_Text_AutoWrapped_Paint( itemDef_t *item ); +void UI_InstallCaptureFunc(CaptureFunc *f, void *data, int timeout); +void UI_RemoveCaptureFunc(void); -int trap_Parse_AddGlobalDefine( char *define ); -int trap_Parse_LoadSource( const char *filename ); -int trap_Parse_FreeSource( int handle ); -int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ); -int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ); +void *UI_Alloc(int size); +void UI_InitMemory(void); +qboolean UI_OutOfMemory(void); -void BindingFromName( const char *cvar ); -extern char g_nameBind1[ 32 ]; -extern char g_nameBind2[ 32 ]; +void Controls_GetConfig(void); +void Controls_SetConfig(qboolean restart); +void Controls_SetDefaults(void); + +void trap_R_SetClipRegion(const float *region); + +// for cg_draw.c +void Item_Text_Wrapped_Paint(itemDef_t *item); +const char *Item_Text_Wrap(const char *text, float scale, float width); +void UI_DrawTextBlock(rectDef_t *rect, float text_x, float text_y, vec4_t color, float scale, int textalign, + int textvalign, int textStyle, const char *text); +void UI_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); +void UI_Text_Paint_Limit( + float *maxX, float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit); +float UI_Text_Width(const char *text, float scale); +float UI_Text_Height(const char *text, float scale); +float UI_Text_EmWidth(float scale); +float UI_Text_EmHeight(float scale); +qboolean UI_Text_IsEmoticon(const char *s, qboolean *escaped, int *length, qhandle_t *h, int *width); +void UI_EscapeEmoticons(char *dest, const char *src, int destsize); + +int trap_Parse_AddGlobalDefine(char *define); +int trap_Parse_LoadSource(const char *filename); +int trap_Parse_FreeSource(int handle); +int trap_Parse_ReadToken(int handle, pc_token_t *pc_token); +int trap_Parse_SourceFileAndLine(int handle, char *filename, int *line); + +void BindingFromName(const char *cvar); +extern char g_nameBind1[32]; +extern char g_nameBind2[32]; #endif diff --git a/src/ui/ui_syscalls.asm b/src/ui/ui_syscalls.asm index 1e797a9..b566800 100644 --- a/src/ui/ui_syscalls.asm +++ b/src/ui/ui_syscalls.asm @@ -42,7 +42,7 @@ equ trap_Key_GetOverstrikeMode -39 equ trap_Key_SetOverstrikeMode -40 equ trap_Key_ClearStates -41 equ trap_Key_GetCatcher -42 -equ trap_Key_SetCatcher -43 +equ trap_Key_SetCatcher -43 equ trap_GetClipboardData -44 equ trap_GetGlconfig -45 equ trap_GetClientState -46 @@ -99,4 +99,3 @@ equ atan2 -106 equ sqrt -107 equ floor -108 equ ceil -109 - diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c index a27e573..11bd93a 100644 --- a/src/ui/ui_syscalls.c +++ b/src/ui/ui_syscalls.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -26,362 +27,284 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // this file is only included when building a dll // syscalls.asm is included instead when building a qvm -static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; +static intptr_t(QDECL *syscall)(intptr_t arg, ...) = (intptr_t(QDECL *)(intptr_t, ...)) - 1; -Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) { - syscall = syscallptr; -} +Q_EXPORT void dllEntry(intptr_t(QDECL *syscallptr)(intptr_t arg, ...)) { syscall = syscallptr; } -int PASSFLOAT( float x ) { - float floatTemp; - floatTemp = x; - return *(int *)&floatTemp; +int PASSFLOAT(float x) +{ + floatint_t fi; + fi.f = x; + return fi.i; } -void trap_Print( const char *string ) { - syscall( UI_PRINT, string ); -} +void trap_Print(const char *string) { syscall(UI_PRINT, string); } -void trap_Error( const char *string ) { - syscall( UI_ERROR, string ); +void trap_Error(const char *string) +{ + syscall(UI_ERROR, string); + // shut up GCC warning about returning functions, because we know better + exit(1); } -int trap_Milliseconds( void ) { - return syscall( UI_MILLISECONDS ); -} +int trap_Milliseconds(void) { return syscall(UI_MILLISECONDS); } -void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) { - syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags ); +void trap_Cvar_Register(vmCvar_t *cvar, const char *var_name, const char *value, int flags) +{ + syscall(UI_CVAR_REGISTER, cvar, var_name, value, flags); } -void trap_Cvar_Update( vmCvar_t *cvar ) { - syscall( UI_CVAR_UPDATE, cvar ); -} +void trap_Cvar_Update(vmCvar_t *cvar) { syscall(UI_CVAR_UPDATE, cvar); } -void trap_Cvar_Set( const char *var_name, const char *value ) { - syscall( UI_CVAR_SET, var_name, value ); -} +void trap_Cvar_Set(const char *var_name, const char *value) { syscall(UI_CVAR_SET, var_name, value); } -float trap_Cvar_VariableValue( const char *var_name ) { - int temp; - temp = syscall( UI_CVAR_VARIABLEVALUE, var_name ); - return (*(float*)&temp); +float trap_Cvar_VariableValue(const char *var_name) +{ + floatint_t fi; + fi.i = syscall(UI_CVAR_VARIABLEVALUE, var_name); + return fi.f; } -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { - syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +void trap_Cvar_VariableStringBuffer(const char *var_name, char *buffer, int bufsize) +{ + syscall(UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize); } -void trap_Cvar_SetValue( const char *var_name, float value ) { - syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) ); -} +void trap_Cvar_SetValue(const char *var_name, float value) { syscall(UI_CVAR_SETVALUE, var_name, PASSFLOAT(value)); } -void trap_Cvar_Reset( const char *name ) { - syscall( UI_CVAR_RESET, name ); -} +void trap_Cvar_Reset(const char *name) { syscall(UI_CVAR_RESET, name); } -void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) { - syscall( UI_CVAR_CREATE, var_name, var_value, flags ); +void trap_Cvar_Create(const char *var_name, const char *var_value, int flags) +{ + syscall(UI_CVAR_CREATE, var_name, var_value, flags); } -void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) { - syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize ); +void trap_Cvar_InfoStringBuffer(int bit, char *buffer, int bufsize) +{ + syscall(UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize); } -int trap_Argc( void ) { - return syscall( UI_ARGC ); -} +int trap_Argc(void) { return syscall(UI_ARGC); } -void trap_Argv( int n, char *buffer, int bufferLength ) { - syscall( UI_ARGV, n, buffer, bufferLength ); -} +void trap_Argv(int n, char *buffer, int bufferLength) { syscall(UI_ARGV, n, buffer, bufferLength); } -void trap_Cmd_ExecuteText( int exec_when, const char *text ) { - syscall( UI_CMD_EXECUTETEXT, exec_when, text ); -} +void trap_Cmd_ExecuteText(int exec_when, const char *text) { syscall(UI_CMD_EXECUTETEXT, exec_when, text); } -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { - return syscall( UI_FS_FOPENFILE, qpath, f, mode ); +int trap_FS_FOpenFile(const char *qpath, fileHandle_t *f, enum FS_Mode mode) +{ + return syscall(UI_FS_FOPENFILE, qpath, f, mode); } -void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { - syscall( UI_FS_READ, buffer, len, f ); -} +void trap_FS_Read(void *buffer, int len, fileHandle_t f) { syscall(UI_FS_READ, buffer, len, f); } -void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { - syscall( UI_FS_WRITE, buffer, len, f ); -} +void trap_FS_Write(const void *buffer, int len, fileHandle_t f) { syscall(UI_FS_WRITE, buffer, len, f); } -void trap_FS_FCloseFile( fileHandle_t f ) { - syscall( UI_FS_FCLOSEFILE, f ); -} +void trap_FS_FCloseFile(fileHandle_t f) { syscall(UI_FS_FCLOSEFILE, f); } -int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { - return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize ); +int trap_FS_GetFileList(const char *path, const char *extension, char *listbuf, int bufsize) +{ + return syscall(UI_FS_GETFILELIST, path, extension, listbuf, bufsize); } -int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { - return syscall( UI_FS_SEEK, f, offset, origin ); -} +int trap_FS_Seek(fileHandle_t f, long offset, enum FS_Mode origin) { return syscall(UI_FS_SEEK, f, offset, origin); } -qhandle_t trap_R_RegisterModel( const char *name ) { - return syscall( UI_R_REGISTERMODEL, name ); -} +qhandle_t trap_R_RegisterModel(const char *name) { return syscall(UI_R_REGISTERMODEL, name); } -qhandle_t trap_R_RegisterSkin( const char *name ) { - return syscall( UI_R_REGISTERSKIN, name ); -} +qhandle_t trap_R_RegisterSkin(const char *name) { return syscall(UI_R_REGISTERSKIN, name); } -void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { - syscall( UI_R_REGISTERFONT, fontName, pointSize, font ); +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) +{ + syscall(UI_R_REGISTERFONT, fontName, pointSize, font); } -qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { - return syscall( UI_R_REGISTERSHADERNOMIP, name ); -} +qhandle_t trap_R_RegisterShaderNoMip(const char *name) { return syscall(UI_R_REGISTERSHADERNOMIP, name); } -void trap_R_ClearScene( void ) { - syscall( UI_R_CLEARSCENE ); -} +void trap_R_ClearScene(void) { syscall(UI_R_CLEARSCENE); } -void trap_R_AddRefEntityToScene( const refEntity_t *re ) { - syscall( UI_R_ADDREFENTITYTOSCENE, re ); -} +void trap_R_AddRefEntityToScene(const refEntity_t *re) { syscall(UI_R_ADDREFENTITYTOSCENE, re); } -void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { - syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +void trap_R_AddPolyToScene(qhandle_t hShader, int numVerts, const polyVert_t *verts) +{ + syscall(UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts); } -void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { - syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +void trap_R_AddLightToScene(const vec3_t org, float intensity, float r, float g, float b) +{ + syscall(UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b)); } -void trap_R_RenderScene( const refdef_t *fd ) { - syscall( UI_R_RENDERSCENE, fd ); -} +void trap_R_RenderScene(const refdef_t *fd) { syscall(UI_R_RENDERSCENE, fd); } -void trap_R_SetColor( const float *rgba ) { - syscall( UI_R_SETCOLOR, rgba ); -} +void trap_R_SetColor(const float *rgba) { syscall(UI_R_SETCOLOR, rgba); } -void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) { - syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); -} +#ifndef MODULE_INTERFACE_11 +void trap_R_SetClipRegion(const float *region) { syscall(UI_R_SETCLIPREGION, region); } +#endif -void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { - syscall( UI_R_MODELBOUNDS, model, mins, maxs ); +void trap_R_DrawStretchPic( + float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader) +{ + syscall(UI_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), + PASSFLOAT(s2), PASSFLOAT(t2), hShader); } -void trap_UpdateScreen( void ) { - syscall( UI_UPDATESCREEN ); -} +void trap_R_ModelBounds(clipHandle_t model, vec3_t mins, vec3_t maxs) { syscall(UI_R_MODELBOUNDS, model, mins, maxs); } -int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) { - return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); -} +void trap_UpdateScreen(void) { syscall(UI_UPDATESCREEN); } -void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { - syscall( UI_S_STARTLOCALSOUND, sfx, channelNum ); +int trap_CM_LerpTag(orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName) +{ + return syscall(UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName); } -sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { - return syscall( UI_S_REGISTERSOUND, sample, compressed ); -} +void trap_S_StartLocalSound(sfxHandle_t sfx, int channelNum) { syscall(UI_S_STARTLOCALSOUND, sfx, channelNum); } -void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { - syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +sfxHandle_t trap_S_RegisterSound(const char *sample, qboolean compressed) +{ + return syscall(UI_S_REGISTERSOUND, sample, compressed); } -void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { - syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen ); +void trap_Key_KeynumToStringBuf(int keynum, char *buf, int buflen) +{ + syscall(UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen); } -void trap_Key_SetBinding( int keynum, const char *binding ) { - syscall( UI_KEY_SETBINDING, keynum, binding ); -} +void trap_Key_GetBindingBuf(int keynum, char *buf, int buflen) { syscall(UI_KEY_GETBINDINGBUF, keynum, buf, buflen); } -qboolean trap_Key_IsDown( int keynum ) { - return syscall( UI_KEY_ISDOWN, keynum ); -} +void trap_Key_SetBinding(int keynum, const char *binding) { syscall(UI_KEY_SETBINDING, keynum, binding); } -qboolean trap_Key_GetOverstrikeMode( void ) { - return syscall( UI_KEY_GETOVERSTRIKEMODE ); -} +qboolean trap_Key_IsDown(int keynum) { return syscall(UI_KEY_ISDOWN, keynum); } -void trap_Key_SetOverstrikeMode( qboolean state ) { - syscall( UI_KEY_SETOVERSTRIKEMODE, state ); -} +qboolean trap_Key_GetOverstrikeMode(void) { return syscall(UI_KEY_GETOVERSTRIKEMODE); } -void trap_Key_ClearStates( void ) { - syscall( UI_KEY_CLEARSTATES ); -} +void trap_Key_SetOverstrikeMode(qboolean state) { syscall(UI_KEY_SETOVERSTRIKEMODE, state); } -int trap_Key_GetCatcher( void ) { - return syscall( UI_KEY_GETCATCHER ); -} +void trap_Key_ClearStates(void) { syscall(UI_KEY_CLEARSTATES); } -void trap_Key_SetCatcher( int catcher ) { - syscall( UI_KEY_SETCATCHER, catcher ); -} +int trap_Key_GetCatcher(void) { return syscall(UI_KEY_GETCATCHER); } -void trap_GetClipboardData( char *buf, int bufsize ) { - syscall( UI_GETCLIPBOARDDATA, buf, bufsize ); -} +void trap_Key_SetCatcher(int catcher) { syscall(UI_KEY_SETCATCHER, catcher); } -void trap_GetClientState( uiClientState_t *state ) { - syscall( UI_GETCLIENTSTATE, state ); -} +void trap_GetClipboardData(char *buf, int bufsize) { syscall(UI_GETCLIPBOARDDATA, buf, bufsize); } -void trap_GetGlconfig( glconfig_t *glconfig ) { - syscall( UI_GETGLCONFIG, glconfig ); -} +void trap_GetClientState(uiClientState_t *state) { syscall(UI_GETCLIENTSTATE, state); } -int trap_GetConfigString( int index, char* buff, int buffsize ) { - return syscall( UI_GETCONFIGSTRING, index, buff, buffsize ); -} +void trap_GetGlconfig(glconfig_t *glconfig) { syscall(UI_GETGLCONFIG, glconfig); } -int trap_LAN_GetServerCount( int source ) { - return syscall( UI_LAN_GETSERVERCOUNT, source ); +int trap_GetConfigString(int index, char *buff, int buffsize) +{ + return syscall(UI_GETCONFIGSTRING, index, buff, buffsize); } -void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { - syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen ); -} +int trap_LAN_GetServerCount(int source) { return syscall(UI_LAN_GETSERVERCOUNT, source); } -void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { - syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen ); +void trap_LAN_GetServerAddressString(int source, int n, char *buf, int buflen) +{ + syscall(UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen); } -int trap_LAN_GetServerPing( int source, int n ) { - return syscall( UI_LAN_GETSERVERPING, source, n ); +void trap_LAN_GetServerInfo(int source, int n, char *buf, int buflen) +{ + syscall(UI_LAN_GETSERVERINFO, source, n, buf, buflen); } -int trap_LAN_GetPingQueueCount( void ) { - return syscall( UI_LAN_GETPINGQUEUECOUNT ); -} +int trap_LAN_GetServerPing(int source, int n) { return syscall(UI_LAN_GETSERVERPING, source, n); } -int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) { - return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen ); -} +int trap_LAN_GetPingQueueCount(void) { return syscall(UI_LAN_GETPINGQUEUECOUNT); } -void trap_LAN_SaveCachedServers( void ) { - syscall( UI_LAN_SAVECACHEDSERVERS ); +int trap_LAN_ServerStatus(const char *serverAddress, char *serverStatus, int maxLen) +{ + return syscall(UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen); } -void trap_LAN_LoadCachedServers( void ) { - syscall( UI_LAN_LOADCACHEDSERVERS ); -} +#ifndef MODULE_INTERFACE_11 +qboolean trap_GetNews(qboolean force) { return syscall(UI_GETNEWS, force); } +#endif -void trap_LAN_ResetPings(int n) { - syscall( UI_LAN_RESETPINGS, n ); -} +void trap_LAN_SaveCachedServers(void) { syscall(UI_LAN_SAVECACHEDSERVERS); } -void trap_LAN_ClearPing( int n ) { - syscall( UI_LAN_CLEARPING, n ); -} +void trap_LAN_LoadCachedServers(void) { syscall(UI_LAN_LOADCACHEDSERVERS); } -void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { - syscall( UI_LAN_GETPING, n, buf, buflen, pingtime ); -} +void trap_LAN_ResetPings(int n) { syscall(UI_LAN_RESETPINGS, n); } -void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) { - syscall( UI_LAN_GETPINGINFO, n, buf, buflen ); -} +void trap_LAN_ClearPing(int n) { syscall(UI_LAN_CLEARPING, n); } -void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) { - syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible ); +void trap_LAN_GetPing(int n, char *buf, int buflen, int *pingtime) +{ + syscall(UI_LAN_GETPING, n, buf, buflen, pingtime); } -int trap_LAN_ServerIsVisible( int source, int n) { - return syscall( UI_LAN_SERVERISVISIBLE, source, n ); -} +void trap_LAN_GetPingInfo(int n, char *buf, int buflen) { syscall(UI_LAN_GETPINGINFO, n, buf, buflen); } -qboolean trap_LAN_UpdateVisiblePings( int source ) { - return syscall( UI_LAN_UPDATEVISIBLEPINGS, source ); +void trap_LAN_MarkServerVisible(int source, int n, qboolean visible) +{ + syscall(UI_LAN_MARKSERVERVISIBLE, source, n, visible); } -int trap_LAN_AddServer(int source, const char *name, const char *addr) { - return syscall( UI_LAN_ADDSERVER, source, name, addr ); -} +int trap_LAN_ServerIsVisible(int source, int n) { return syscall(UI_LAN_SERVERISVISIBLE, source, n); } -void trap_LAN_RemoveServer(int source, const char *addr) { - syscall( UI_LAN_REMOVESERVER, source, addr ); -} +qboolean trap_LAN_UpdateVisiblePings(int source) { return syscall(UI_LAN_UPDATEVISIBLEPINGS, source); } -int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { - return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 ); +int trap_LAN_AddServer(int source, const char *name, const char *addr) +{ + return syscall(UI_LAN_ADDSERVER, source, name, addr); } -int trap_MemoryRemaining( void ) { - return syscall( UI_MEMORY_REMAINING ); -} +void trap_LAN_RemoveServer(int source, const char *addr) { syscall(UI_LAN_REMOVESERVER, source, addr); } -int trap_Parse_AddGlobalDefine( char *define ) { - return syscall( UI_PARSE_ADD_GLOBAL_DEFINE, define ); +int trap_LAN_CompareServers(int source, int sortKey, int sortDir, int s1, int s2) +{ + return syscall(UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2); } -int trap_Parse_LoadSource( const char *filename ) { - return syscall( UI_PARSE_LOAD_SOURCE, filename ); -} +int trap_MemoryRemaining(void) { return syscall(UI_MEMORY_REMAINING); } -int trap_Parse_FreeSource( int handle ) { - return syscall( UI_PARSE_FREE_SOURCE, handle ); -} +int trap_Parse_AddGlobalDefine(char *define) { return syscall(UI_PARSE_ADD_GLOBAL_DEFINE, define); } -int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) { - return syscall( UI_PARSE_READ_TOKEN, handle, pc_token ); -} +int trap_Parse_LoadSource(const char *filename) { return syscall(UI_PARSE_LOAD_SOURCE, filename); } -int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) { - return syscall( UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); -} +int trap_Parse_FreeSource(int handle) { return syscall(UI_PARSE_FREE_SOURCE, handle); } -void trap_S_StopBackgroundTrack( void ) { - syscall( UI_S_STOPBACKGROUNDTRACK ); -} +int trap_Parse_ReadToken(int handle, pc_token_t *pc_token) { return syscall(UI_PARSE_READ_TOKEN, handle, pc_token); } -void trap_S_StartBackgroundTrack( const char *intro, const char *loop) { - syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop ); +int trap_Parse_SourceFileAndLine(int handle, char *filename, int *line) +{ + return syscall(UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line); } -int trap_RealTime(qtime_t *qtime) { - return syscall( UI_REAL_TIME, qtime ); +void trap_S_StopBackgroundTrack(void) { syscall(UI_S_STOPBACKGROUNDTRACK); } + +void trap_S_StartBackgroundTrack(const char *intro, const char *loop) +{ + syscall(UI_S_STARTBACKGROUNDTRACK, intro, loop); } -// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) -int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { - return syscall(UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +int trap_RealTime(qtime_t *qtime) { return syscall(UI_REAL_TIME, qtime); } + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do +// not alter gamestate) +int trap_CIN_PlayCinematic(const char *arg0, int xpos, int ypos, int width, int height, int bits) +{ + return syscall(UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); } // stops playing the cinematic and ends it. should always return FMV_EOF // cinematics must be stopped in reverse order of when they are started -e_status trap_CIN_StopCinematic(int handle) { - return syscall(UI_CIN_STOPCINEMATIC, handle); -} - - -// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. -e_status trap_CIN_RunCinematic (int handle) { - return syscall(UI_CIN_RUNCINEMATIC, handle); -} +e_status trap_CIN_StopCinematic(int handle) { return syscall(UI_CIN_STOPCINEMATIC, handle); } +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been +// reached. +e_status trap_CIN_RunCinematic(int handle) { return syscall(UI_CIN_RUNCINEMATIC, handle); } // draws the current frame -void trap_CIN_DrawCinematic (int handle) { - syscall(UI_CIN_DRAWCINEMATIC, handle); -} - +void trap_CIN_DrawCinematic(int handle) { syscall(UI_CIN_DRAWCINEMATIC, handle); } // allows you to resize the animation dynamically -void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { - syscall(UI_CIN_SETEXTENTS, handle, x, y, w, h); -} +void trap_CIN_SetExtents(int handle, int x, int y, int w, int h) { syscall(UI_CIN_SETEXTENTS, handle, x, y, w, h); } - -void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { - syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +void trap_R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset) +{ + syscall(UI_R_REMAP_SHADER, oldShader, newShader, timeOffset); } -void trap_SetPbClStatus( int status ) { - syscall( UI_SET_PBCLSTATUS, status ); -} +void trap_SetPbClStatus(int status) { syscall(UI_SET_PBCLSTATUS, status); } diff --git a/src/ui/ui_syscalls_11.asm b/src/ui/ui_syscalls_11.asm index 64d2ca3..a7a01f2 100644 --- a/src/ui/ui_syscalls_11.asm +++ b/src/ui/ui_syscalls_11.asm @@ -96,3 +96,5 @@ equ sqrt -107 equ floor -108 equ ceil -109 +equ trap_CheckForUpdate -200 +equ trap_InstallUpdate -201 -- cgit